Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Thursday, March 24, 2011

original smalihook java source

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
 }
}

Wednesday, December 29, 2010

getting apk signature outside of android

one way to circumvent signature checking in an android app is to spoof with the correct signature (usually a hashcode but sometimes a tochar representation). but how do we get the apk signature? well, we could write a program in android to do it, which is pretty simple:
public void WriteSignature(String packageName)
{
 // all of this is fairly well documented
 // if it doesn't work, just search around.
 
 PackageManager pm = this.getPackageManager();
 PackageInfo pi = null;
 try {
  pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
 } catch (NameNotFoundException e1) {
  e1.printStackTrace();
 }
 Signature[] s = pi.signatures;
 
 // you can use toChars or get the hashcode, whatever
 String sig = new String(s[0].toChars());

 try {
  File root = Environment.getExternalStorageDirectory();
  if ( root.canWrite() )
  {
   // toChars is long, so i write it to a file on the external storage
   File f = new File(root, "signature.txt");
   FileWriter fw = new FileWriter(f);
   BufferedWriter out = new BufferedWriter(fw);
   out.write(packageName + "\nSignature: " + sig);
   out.close();
   fw.close();
  }
 } catch (IOException e) {
  e.printStackTrace();
 }
}

but i wanted to be able to get the signature from my computer, and google wasn't helping. so i dug into the android code. then i found this: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.0_r1/android/content/pm/PackageParser.java#442

the packageparser code was exactly what i needed. nothing about it was particularly magical and i could have probably figured it out on my own if i knew java.security better, but here is what i came up with:
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.security.Signature;
import java.security.cert.*;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main {

 private static final Object mSync = new Object();
 private static WeakReference<byte[]> mReadBuffer;

 public static void main(String[] args) {
  if (args.length < 1) {
   System.out.println("Usage: java -jar GetAndroidSig.jar <apk/jar>");
   System.exit(-1);
  }

  System.out.println(args[0]);

  String mArchiveSourcePath = args[0];

  WeakReference<byte[]> readBufferRef;
  byte[] readBuffer = null;
  synchronized (mSync) {
   readBufferRef = mReadBuffer;
   if (readBufferRef != null) {
    mReadBuffer = null;
    readBuffer = readBufferRef.get();
   }
   if (readBuffer == null) {
    readBuffer = new byte[8192];
    readBufferRef = new WeakReference<byte[]>(readBuffer);
   }
  }

  try {
   JarFile jarFile = new JarFile(mArchiveSourcePath);
   java.security.cert.Certificate[] certs = null;

   Enumeration entries = jarFile.entries();
   while (entries.hasMoreElements()) {
    JarEntry je = (JarEntry) entries.nextElement();
    if (je.isDirectory()) {
     continue;
    }
    if (je.getName().startsWith("META-INF/")) {
     continue;
    }
    java.security.cert.Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
    if (false) {
     System.out.println("File " + mArchiveSourcePath + " entry " + je.getName()
         + ": certs=" + certs + " ("
         + (certs != null ? certs.length : 0) + ")");
    }
    if (localCerts == null) {
     System.err.println("Package has no certificates at entry "
         + je.getName() + "; ignoring!");
     jarFile.close();
     return;
    } else if (certs == null) {
     certs = localCerts;
    } else {
     // Ensure all certificates match.
     for (int i = 0; i < certs.length; i++) {
      boolean found = false;
      for (int j = 0; j < localCerts.length; j++) {
       if (certs[i] != null
           && certs[i].equals(localCerts[j])) {
        found = true;
        break;
       }
      }
      if (!found || certs.length != localCerts.length) {
       System.err.println("Package has mismatched certificates at entry "
           + je.getName() + "; ignoring!");
       jarFile.close();
       return; // false
      }
     }
    }
   }

   jarFile.close();

   synchronized (mSync) {
    mReadBuffer = readBufferRef;
   }

   if (certs != null && certs.length > 0) {
    final int N = certs.length;
    
    for (int i = 0; i < N; i++) {
     String charSig = new String(toChars(certs[i].getEncoded()));
     System.out.println("Cert#: " + i + "  Type:" + certs[i].getType()
      + "\nPublic key: " + certs[i].getPublicKey()
      + "\nHash code: " + certs[i].hashCode()
       + " / 0x" + Integer.toHexString(certs[i].hashCode())
      + "\nTo char: " + charSig);
    }
   } else {
    System.err.println("Package has no certificates; ignoring!");
    return;
   }
  } catch (CertificateEncodingException ex) {
   Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
  } catch (IOException e) {
   System.err.println("Exception reading " + mArchiveSourcePath + "\n" + e);
   return;
  } catch (RuntimeException e) {
   System.err.println("Exception reading " + mArchiveSourcePath + "\n" + e);
   return;
  }
 }

 private static char[] toChars(byte[] mSignature) {
    byte[] sig = mSignature;
    final int N = sig.length;
    final int N2 = N*2;
    char[] text = new char[N2];

    for (int j=0; j<N; j++) {
      byte v = sig[j];
      int d = (v>>4)&0xf;
      text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
      d = v&0xf;
      text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
    }

    return text;
    }

 private static java.security.cert.Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) {
  try {
   // We must read the stream for the JarEntry to retrieve
   // its certificates.
   InputStream is = jarFile.getInputStream(je);
   while (is.read(readBuffer, 0, readBuffer.length) != -1) {
    // not using
   }
   is.close();

   return (java.security.cert.Certificate[]) (je != null ? je.getCertificates() : null);
  } catch (IOException e) {
   System.err.println("Exception reading " + je.getName() + " in "
       + jarFile.getName() + ": " + e);
  }
  return null;
 }
}

running it on crackme0 from way of the android cracker 0, produces this:



using it, in dalvik, would look something like this:
# get signatures array
iget-object v0, v0, Landroid/content/pm/PackageInfo;->signatures:[Landroid/content/pm/Signature;

# get element at index v10 in array v0 and store in v0
aget-object v0, v0, v10
 
# call Signature.hashCode() on v0, returns an integer
invoke-virtual {v0}, Landroid/content/pm/Signature;->hashCode()I
move-result v9

# spoof original signature hashcode. grin smugly.
const/16 v9, 0x58404