i've noticed some interest about a file that antilvl sometimes uses when cracking a program. it's called smalihook and it's purpose is to provide "hook" (actually replacement) methods for things like getting device id or signature. it's not really anything special, unless you actually modify the places in the app that make use of certain function calls. there is also a smalihook.java floating around that is actually a badly decompiled, broken version. i'd rather people have the real thing.
the variable strings that start with "%!" (ex: %!AppPackage%) are for antilvl to replace with the actual information when it copies it over.
if you want to use any of the functions here you can simply use antilvl.
if you just want to spoof your android_id or getdeviceid, try this: http://strazzere.com/blog/?p=217
package lohan;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Random;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.telephony.TelephonyManager;
import android.util.Log;
/*
* TODO:
* I wonder if it's possible to check getClasses or getMethods to detect this
* hook
* Hooks:
* PackageManager
* getInstallerPackageName
* getPackageInfo
* getApplicationEnabledSetting
* checkSignatures
* getDeviceID - requires context
* File
* length
* lastModified
*/
public class SmaliHook {
// replace with random var per antilvl run
private static String PrefsFile = "HookSettings";
private static Context myAppContext = null;
// random - always random, permute - unreversible permutation
// session means until app is reinstalled
private static enum DEVICE_ID_SPOOF {
RANDOM, SESSION_RANDOM, SESSION_PERMUTE
};
private static DEVICE_ID_SPOOF myIDSpoof = DEVICE_ID_SPOOF.SESSION_RANDOM;
private static String LOG_TAG = "lohan";
private static boolean DEBUG = true;
private static boolean DUMP_STACK = false;
public static Object invokeHook(Method method, Object receiver,
Object[] args) throws IllegalArgumentException,
IllegalAccessException, InvocationTargetException,
NameNotFoundException {
boolean HookEnabled = true;
String methodClassName = "unknown-static";
String methodName = method.getName();
if ( receiver != null )
methodClassName = receiver.getClass().getName();
else methodClassName = method.getDeclaringClass().getName();
if ( DEBUG ) {
String logStr = "Invoke Hook: " + methodClassName + "."
+ methodName + "(";
if ( args != null ) {
String argStr = "";
for ( Object arg : args )
argStr += arg.getClass().getName() + ":" + arg + ", ";
if ( argStr.length() > 2 )
argStr = argStr.substring(0, argStr.length() - 2);
logStr += argStr;
}
Log(logStr + ")");
}
DumpStackIfWeShould();
if ( !HookEnabled ) return method.invoke(receiver, args);
if ( methodClassName
.equals("android.app.ContextImpl$ApplicationPackageManager")
|| methodClassName
.equals("android.app.ApplicationContext$ApplicationPackageManager")
|| methodClassName.equals("android.content.pm.PackageManager")
|| methodClassName.contains("ApplicationPackageManager") ) {
if ( methodName.equals("getInstallerPackageName") ) {
// Hook get installer package name
return getInstallerPackageName((String) args[0]);
}
else if ( methodName.equals("getPackageInfo") ) {
// Hook get package info for signatures
int flags = (Integer) args[1];
if ( methodClassName
.equals("android.content.pm.PackageManager") )
return SmaliHook.getPackageInfo(
((PackageManager) receiver), (String) args[0],
flags);
// Cannot simply recast receiver to
// ContextImpl.ApplicationPackageManager or we get error
Object result = null;
try {
result = method.invoke(receiver, args);
}
catch (Exception e) {
result = method.invoke(receiver, "%!AppPackage%");
}
if ( (flags & PackageManager.GET_SIGNATURES) == PackageManager.GET_SIGNATURES ) {
Signature[] spoofSigs = SmaliHook.spoofSignatures();
// should only need to spoof the first one
((PackageInfo) result).signatures[0] = spoofSigs[0];
}
return result;
}
else if ( methodName.equals("getApplicationEnabledSetting") ) {
int result = getApplicationEnabledSetting(
(PackageManager) receiver, (String) args[0]);
return (Object) Integer.valueOf(result);
}
else if ( methodName.equals("checkSignatures") ) {
// This could be detected by comparing a known installed package
// that will not match signatures. Will deal with that if it
// ever happens. :D
return checkSignatures((String) args[0], (String) args[1]);
}
}
else if ( methodClassName.equals("java.io.File") ) {
if ( shouldSpoofFileInfo((File) receiver) ) {
if ( methodName.equals("length") ) { return length((File) receiver); }
if ( methodName.equals("lastModified") ) { return lastModified((File) receiver); }
}
}
// No hooks, work as normal
return method.invoke(receiver, args);
}
public static int checkSignatures(String p1, String p2) {
Log("checkSignatures returning SIGNATURE_MATCH");
DumpStackIfWeShould();
return PackageManager.SIGNATURE_MATCH;
}
public static int checkSignatures() {
Log("checkSignatures returning SIGNATURE_MATCH");
DumpStackIfWeShould();
return PackageManager.SIGNATURE_MATCH;
}
public static String getInstallerPackageName(String packageName) {
// LIE and say installed from market :D
String result = "com.google.android.feedback";
Log("getInstallerPackageName returning " + result);
DumpStackIfWeShould();
return result;
}
public static int getApplicationEnabledSetting(PackageManager pm,
String packageName) {
int result;
try {
result = pm.getApplicationEnabledSetting(packageName);
}
catch (IllegalArgumentException ex) {
result = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
// Fake value if it's disabled
if ( result == PackageManager.COMPONENT_ENABLED_STATE_DISABLED )
result = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
Log("enabledSetting returning " + result);
DumpStackIfWeShould();
return result;
}
public static PackageInfo getPackageInfo(PackageManager pm,
String packageName, int flags) throws NameNotFoundException {
// Get regular package info
PackageInfo pi = null;
try {
pi = pm.getPackageInfo(packageName, flags);
}
catch (NameNotFoundException e) {
// Sometimes the app wants to know of other, helper apps are
// installed or if trial / nonfull versions are installed
// Fail normally if it's NOT checking for pro/full version stuff
if ( !(packageName.toLowerCase().contains("pro")
|| packageName.toLowerCase().contains("full")
|| packageName.toLowerCase().contains("donate") || packageName
.toLowerCase().endsWith("key")) )
throw new NameNotFoundException();
// Spoof with this package's info
pi = pm.getPackageInfo("%!AppPackage%", flags);
}
// Populate with fake signatures if flags ask for it
if ( (flags & PackageManager.GET_SIGNATURES) == PackageManager.GET_SIGNATURES ) {
Signature[] spoofSigs = SmaliHook.spoofSignatures();
for ( int i = 0; i < pi.signatures.length; i++ )
pi.signatures[i] = spoofSigs[i];
Log("spoofing signatures for " + packageName);
DumpStackIfWeShould();
}
return pi;
}
public static Signature[] spoofSignatures() {
final int certCount = Integer.parseInt("%!CertCount%");
Signature[] result = new Signature[certCount];
// Usually check signature of package and not individual files
// This will only fool checks of entire package
// Individual files would require a lot of smali generation
String replace = "%!SignatureChars%";
for ( int i = 0; i < certCount; i++ )
result[i] = new Signature(replace);
return result;
}
public static long length(File f) {
long retVal = Long.parseLong("%!OrigFileSize%");
if ( !shouldSpoofFileInfo(f) ) {
retVal = f.length();
Log("spoofing file length of " + f.getName() + " with " + retVal);
DumpStackIfWeShould();
}
return retVal;
}
public static long lastModified(File f) {
// long retVal = 1287850800968L;
long retVal = Long.parseLong("%!OrigLastModified%");
if ( DUMP_STACK ) Thread.dumpStack();
if ( !shouldSpoofFileInfo(f) ) {
retVal = f.lastModified();
Log("spoofing file modified of " + f.getName() + " with " + retVal);
DumpStackIfWeShould();
}
return retVal;
}
public static String getDeviceID() {
if ( myAppContext == null ) {
Log("getDeviceID has no context, can't spoof device id");
return "";
}
// final TelephonyManager tm = (TelephonyManager)
// myAppContext.getSystemService(Context.TELEPHONY_SERVICE);
// Log("this is my device id: " + tm.getDeviceId());
// fallback id
String spoofID = "359881030314356";
if ( myIDSpoof == DEVICE_ID_SPOOF.RANDOM )
spoofID = generateRandomDeviceID();
else {
SharedPreferences settings = myAppContext.getSharedPreferences(
PrefsFile, Context.MODE_PRIVATE);
spoofID = settings.getString("android_id", "");
if ( spoofID.length() == 0 ) {
if ( myIDSpoof == DEVICE_ID_SPOOF.SESSION_RANDOM )
spoofID = generateRandomDeviceID();
else if ( myIDSpoof == DEVICE_ID_SPOOF.SESSION_PERMUTE )
spoofID = getPermutedDeviceID();
SharedPreferences.Editor editor = settings.edit();
editor.putString("android_id", spoofID);
editor.commit();
}
}
Log("spoofing device id: " + spoofID);
return spoofID;
}
private static boolean shouldSpoofFileInfo(File f) {
boolean result = false;
if ( f.exists() ) result = false;
if ( f.getName().contains("%!AppPackage%")
&& f.getName().endsWith(".apk") ) result = true;
return result;
}
public static void SetAppContext(Context c) {
if ( myAppContext == null ) myAppContext = c;
}
private static String getPermutedDeviceID() {
// permute device id
final TelephonyManager tm = (TelephonyManager) myAppContext
.getSystemService(Context.TELEPHONY_SERVICE);
// lazy lazy lazy http://www.random.org/sequences/
// this is a permutation with a loss of information
// prevent anyone from knowing the id even if they knew the mapping
final int[] p = { 12, 2, 10, 2, 13, 8, 0, 3, 14, 3, 6, 9, 5, 1, 12 };
String deviceId = tm.getDeviceId();
String result = "";
if ( deviceId != null ) {
for ( int i : p )
result += deviceId.charAt(i);
}
return result;
}
private static String generateRandomDeviceID() {
// device id is 15 digit number with seemingly no pattern
// only changed by factory reset or with root
// ex: 359881030314356 (emulators is all 0s)
return generateString("0123456789", 15);
}
private static String generateString(String charSet, int length) {
Random rng = new Random();
char[] text = new char[length];
for ( int i = 0; i < length; i++ )
text[i] = charSet.charAt(rng.nextInt(charSet.length()));
return new String(text);
}
public static void Log(Object o) {
if ( !DEBUG ) return;
Log.d(LOG_TAG, String.valueOf(o));
}
public static void DumpStackIfWeShould() {
if ( !DUMP_STACK ) return;
DumpStack();
}
public static void DumpStack() {
StackTraceElement[] ste = Thread.currentThread().getStackTrace();
// skip the first 4, it's just local stuff
String trace = "Stack trace:\n";
for ( int i = 4; i < ste.length; i++ )
trace += " " + ste[i].toString() + "\n";
Log.d(LOG_TAG, trace);
}
public static void Toast(Object o) {
// todo: implement
}
}