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

Tuesday, December 28, 2010

antilvl 0.9.1

antilvl 0.9.1 is posted and has a few small fixes. namely, when it complains about missing aapt and zipalign.

it seems more people are using it and making suggestions. right now i'm working on figuring out at least three different types of implementations that i have seen. one of them is quite complex and is taking me some time, but it's helping me improve my focus. once i figure them out, i may have to totally rewrite the cracking methods to be much more flexible, which should be fun. the end result is antilvl works with many more apps.

i've also been digging around the android source code, trying to figure out a way to get packageinfo as one does with an android app _outside_ of android. this way antilvl could possibly circumvent signature checking by substituting a string of the original signature whenever it is requested. this would make me very happy as signature checking is the principal anti-cracking mechanism currently, but more importantly, i think would just be clever to do.

Sunday, December 26, 2010

searching inside files

a very common operation while exploring code is searching inside all of the files in a folder for a specific bit. by default, windows xp (and perhaps windows 7) will not search files with unknown extensions (it also hides extensions, which is also annoying). i have read more than one tutorial on android cracking that suggests renaming all .smali files into .txt files for searching. this is not necessary. here's a link describing how to fix it titled (just it case it gets moved) "Using the ``A word of phrase in the file`` search may not work": http://support.microsoft.com/kb/309173

Wednesday, December 22, 2010

what are these empty methods in dalvik?

when i first started playing with dalvik, i would often get stuck at empty methods. something like this:
.method public abstract a()Z
.end method

.method public abstract b()V
.end method

i would trace a program's execution and get to one and not know what to do. maybe the decompiler could not handle the method? is there some trick preventing me from seeing the code? usually i would just backtrack and crack it elsewhere that works most of the time, but sooner or later you may need to know the implementation!

there are at least two reasons. either the method is abstract and is thus part of a super class that a child inherits from or the method is native and the method implementation is buried in a shared library (.so file). let's say you are looking at com/package/Super.smali and it has the two blank methods above, which are abstract. simply search the code for this line:
.implements Lcom/package/Super;

the file in which you find that line should also have a()Z and b()V and possibly some other methods. the code that calls Super;->a()Z or Super;->b()V is most likely initializing the child class before that and then storing it in a variable declared as Super. so all you need to do is correctly identify which child class is being used.


the other possibility is the code has been offloaded to some shared library. this is more common in technically complex apps or games that require fast, low-level code such as c. if you use apktool, look inside the dump directory for a folder called lib. in it there should be at least one .so file. you will need an arm decompiler such as ida pro. there are others but i do not have experience with them. if you do, post a comment. there are multiple variants of the arm instruction set if your decompiler asks which one it should try to use, try running:
aapt d --values badging your_apk_here.apk
aapt comes with apktool and the android-sdk. look near the bottom for the "native-code" portion. should look like this:
native-code: 'armeabi' 'armeabi-v7a'
the instruction set for this apk is armeabi v7 (quite common), so open the .so up in your decompiler and search for a function with a similar name to your empty abstract method. i believe the name will always be of the form Java_PackageName_ClassName_MethodName. if you find your method, hopefully you know arm enough to understand what it is doing. if you are lucky you will not need to modify the library because patching them is a little more difficult than simply editing a .smali file. i plan on including information on patching shared libraries in a later tutorial, but there are several out there.

Tuesday, December 21, 2010

AntiLVL 0.9

 had some more time to work on antilvl. main thing is linux compatibility. you have to change your path until i can find some work-around with apktool trying to run "aapt" instead of "./aapt". more info in the readme.
  • works with linux (Protector wins the prize for asking for it first!)
  • fixed compatibility with smali dumps
    • plan to add full smali/baksmali support in next version
  • changed behavior when CHECK_LICENSE permission cannot be found
    • instead of exiting immediately, just warn the user
  • changed dump path to reflect the package name
  • various smaller improvements
  • updated aapt and zipalign

pick it up here: http://androidcracking.blogspot.com/p/antilvl.html


i have been busy with non-android things lately, so i was happy when a friend (SuRViVe) sent me a link to a tutorial for circumventing an android drm called slidelock. the tutorial was written by Nieylana, who has written some other very nice tutorials, including the first tutorial i could find on android

here's the one on slideme: http://www.accessroot.com/arteam/site/download.php?view.327

and there is some more info on slidelock at the starzzere.com, which is a great blog with lots of information on android: http://strazzere.com/blog/?p=177

it seems like 1.1 and maybe even 2.0 of slidelock are merely wrappers for the main class. it should be fairly easy to look at the androidmanifest.xml and poke around a few files and simply change the launcher activity so the drm isn't executed at all. i really don't like it because it sends far too much information about your phone to their servers. i will look into adding this as feature into antilvl.

Friday, December 17, 2010

antilvl 0.8.4

minor update to antilvl. someone pointed me to some apps that it did not correctly identify the implementation so the incorrect file was modified.

antilvl also now looks for the check_license permission which it uses as a definitive indicator that android lvl is being used. it check_license permission is not found, antilvl will tell you and exit.

http://androidcracking.blogspot.com/p/antilvl.html

Wednesday, December 15, 2010

antilvl 0.8.3

this new version has some support for non-lvl protection detection such as signature and last modified checks. if you have any suggestions for more let me know. in the future it may be possible to automatically disable some of these, but it is somewhat complicated.

also optimized how it scans through files so it should be a little faster. you can get it here:

http://androidcracking.blogspot.com/p/antilvl.html