Thursday, March 31, 2011

spoof getinstallerpackagename with adb

as i wrote back in another post about anti-cracking technique examples, one method that is often used is getinstallerpackagename(). if the apk is installed from adb, it will be null, but if it's installed from the market it will be com.google.android.feedback. antilvl is well aware of this already, but there is an easier solution for when you're in a hurry. i learned it reading this post at tim's fantastic blog on reversing. he's not affiliated with me and for all i know he's an upstanding white hat who just loves hacking android.

all you need is adb. just give it this command either in a shell or as:
adb install -i com.google.android.feedback com.protected.app
 this will setup com.google.android.feedback as the installer for the com.protected.app. if you're not sure what the app name is for a given apk, just use aapt, from the android-sdk. ex: aapt d --values badging someapk.apk

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

Thursday, March 3, 2011

smali syntax highlighting for vim

i've been doing a lot in linux lately and was working on a smali syntax highlighting file for vim, but vierito5 posted a comment with a link to Jon Larimer, who beat me to it. here's the link:
http://codetastrophe.com/smali.vim
https://sites.google.com/site/lohanplus/files/smali.vim (mirror)

it's vim so there are 20 different ways to get it working. google around if this doesn't work, i will be no help. here is how i did it:
mkdir ~/.vim
echo au BufRead,BufNewFile *.smali set filetype=smali >> ~/.vim/filetype.vim
mkdir ~/.vim/syntax
cp smali.vim ~/.vim/syntax