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

26 comments :

  1. Amazing! I was looking for it!
    I have some doubt:

    If an apk returns more the one certificate? What is the really certificate that signs the apk? Do you already code something for this?

    Thank you very much!

    ReplyDelete
  2. Célio,
    if multiple certificates are returned then the apk was signed with multiple certificates. they are all valid. you can read a little more about that here:
    http://stackoverflow.com/questions/2682105/sign-application-with-several-certificates

    the above code, which is borrowed mainly from the PackageParser class in android, has a loop at around line 97 which handles the possibility of multiple certificates.

    ReplyDelete
  3. we both learned something. i had wondered about this before, but never bothered looking into it. your question helped me learn, so thanks for asking. :D

    ReplyDelete
  4. Hello?
    How to extract the signature of the APK?
    And how to re-sign the APK on the extracted signatures?
    Would you approve of this tell you?

    ReplyDelete
  5. Anonymous, your question does not make sense and, forgive me for presuming, reveals a lack of understanding on what, exactly, signatures are and how they work. To get the signature of an Apk, use the code I posted above. You cannot sign an Apk with that signature unless you have the private key.

    ReplyDelete
  6. Great!!
    but... can i have a question?
    In the last line of this post, i don't know what 'in dalvik' points.
    is it a app? or dalvik machine itself??
    plz give me more example....

    ReplyDelete
  7. Hello,

    Thanks For This Wonderful Article!
    Ok So Now, We Got The Hash Code Of An APK File, I Can't Get How Can We Spoof It After That If I Edited An APK?

    ReplyDelete
  8. That's great!
    But.. I have a question.
    Can I calculate the signature depends on the files in META-INF folder instead of using PackageManager in android?

    Thanks

    ReplyDelete
  9. your question does not compute. you ask for what the source code above does. please correct if i am wrong.

    ReplyDelete
  10. I am really confused, the name of the game is the `key` file: how does this get me the `key` file from an apk? I am not a newbie but java is not my main (in my usage) my "java" is built for me basically by eclipse, that's all I have ever used: wham bam thank you mam. So how do I run this DOS> "java -jar" code windows style (i.e. how does it ratify all those import statements etc etc) similarly how do I parse the dos printed output to `key` a file?

    ReplyDelete
    Replies
    1. Cryptographic signatures are specifically designed such that you can't get the private key. All this does is get the signature hash from outside of Android, which you need to compute if you want to spoof it later, after you've resigned the apk.

      If you're using Eclipse, it's easy. Copy the java code. Create a java project. Edit -> Paste, and it will automagically create a new class with the code. Run that class with the arguments you want, or make a jar from there.

      Delete
  11. Just use apkmanager v6 it can doo soo much! Just google for it

    ReplyDelete
  12. i not understand step step to use this. is really i can get again my key? for now i forget my key. so i can not to update my aplication on google play store because forget key. please can you release step by step to get my key?

    ReplyDelete
    Replies
    1. This won't get your key. It will get a hash of the signature which was generated from your private key. Sorry for your loss.

      Delete
  13. Great find! First to all asking, you can not get the certificate file with this (and hopefully any other method) except maybe brute forcing random certificates until one gives you same hashcode but this is next to impossible. To the writer of the article, could you please explain or link to some answer on how do you do "circumvent signature checking" part ?

    ReplyDelete
    Replies
    1. I thought it would be too obvious. You just add the code to provide the spoofed value. What's the hold up? antilvl does this already.

      Delete
  14. Maybe a dumb question but lets say the smali codes looks like this:

    invoke-virtual (v3), Landroid/content/pm/Signature;->toByteArray()B
    move-result-object v4

    How would the result in v4 have to be spoofed?

    ReplyDelete
    Replies
    1. What I would do is write java with a public static method that returns the byte array I want (i.e. the original). Then compile the java to smali with javac and dx. Then add that method somewhere, and call it in place of Signature;->toByteArray()B. You could also have a public static field and access that. It's just code.

      Delete
    2. This comment has been removed by the author.

      Delete
  15. Hi Lohan,

    I uploaded an app and now lost the original key. I want to update the app now. Can your trick work for me? Can i now trick google into thinking this APK has the correct signature. Can we just replace the hash code/fingerprint from old keystore to new keystore. I found the fingerprint from CERT.RSA file.

    Please help. Thanks

    ReplyDelete
  16. where can i get GetAndroidsig.jar :|
    sorry but i cant understand where

    ReplyDelete
    Replies
    1. there's no jar file available. read the post, take the code, compile it yourself.

      Delete
  17. Lohan, I'd like to spoof a signature in an app. The app creates a signature from a string using the strings below. I already patched the smali code and removed the signature check programmatically (works well), but I'd prefer to spoof the signature in string format, because it is easier to patch a string with automated patching tools.

    Question:
    How can I convert my own signature to that string format? I did a Google research but could not find any hints to get my signature to that format.

    Thanks for any help!


    The lazy string seems to be a substring of the main signature

    String signatureLayz = "0\u0082\u0002\u00a70\u0082\u0002e\u00a0\u0003\u0002\u0001\u0002\u0002\u0004P\u0005|B0\u000b\u0006\u0007*\u0086";

    String signature = "0\u0082\u0002\u00a70\u0082\u0002e\u00a0\u0003\u0002\u0001\u0002\u0002\u0004P\u0005|B0\u000b\u0006\u0007*\u0086H\u00ce8\u0004\u0003\u0005\u0000071\u000b0\t\u0006\u0003U\u0004\u0006\u0013\u0002US1\u00100\u000e\u0006\u0003U\u0004\n\u0013\u0007Android1\u00160\u0014\u0006\u0003U\u0004\u0003\u0013\rAndroid Debug0\u001e\u0017\r120717145250Z\u0017\r220715145250Z071\u000b0\t\u0006\u0003U\u0004\u0006\u0013\u0002US1\u00100\u000e\u0006\u0003U\u0004\n\u0013\u0007Android1\u00160\u0014\u0006\u0003U\u0004\u0003\u0013\rAndroid Debug0\u0082\u0001\u00b70\u0082\u0001,\u0006\u0007*\u0086H\u00ce8\u0004\u00010\u0082\u0001\u001f\u0002\u0081\u0081\u0000\u00fd\u007fS\u0081\u001du\u0012)R\u00dfJ\u009c.\u00ec\u00e4\u00e7\u00f6\u0011\u00b7R<\u00efD\u0000\u00c3\u001e?\u0080\u00b6Q&iE]@\"Q\u00fbY=\u008dX\u00fa\u00bf\u00c5\u00f5\u00ba0\u00f6\u00cb\u009bUl\u00d7\u0081;\u0080\u001d4o\u00f2f`\u00b7k\u0099P\u00a5\u00a4\u009f\u009f\u00e8\u0004{\u0010\"\u00c2O\u00bb\u00a9\u00d7\u00fe\u00b7\u00c6\u001b\u00f8;W\u00e7\u00c6\u00a8\u00a6\u0015\u000f\u0004\u00fb\u0083\u00f6\u00d3\u00c5\u001e\u00c3\u00025T\u0013Z\u0016\u00912\u00f6u\u00f3\u00ae+a\u00d7*\u00ef\u00f2\"\u0003\u0019\u009d\u00d1H\u0001\u00c7\u0002\u0015\u0000\u0097`P\u008f\u0015#\u000b\u00cc\u00b2\u0092\u00b9\u0082\u00a2\u00eb\u0084\u000b\u00f0X\u001c\u00f5\u0002\u0081\u0081\u0000\u00f7\u00e1\u00a0\u0085\u00d6\u009b=\u00de\u00cb\u00bc\u00ab\\6\u00b8W\u00b9y\u0094\u00af\u00bb\u00fa:\u00ea\u0082\u00f9WL\u000b=\u0007\u0082gQYW\u008e\u00ba\u00d4YO\u00e6q\u0007\u0010\u0081\u0080\u00b4I\u0016q#\u00e8L(\u0016\u0013\u00b7\u00cf\t2\u008c\u00c8\u00a6\u00e1<\u0016z\u008bT|\u008d(\u00e0\u00a3\u00ae\u001e+\u00b3\u00a6u\u0091n\u00a3\u007f\u000b\u00fa!5b\u00f1\u00fbbz\u0001$;\u00cc\u00a4\u00f1\u00be\u00a8Q\u0090\u0089\u00a8\u0083\u00df\u00e1Z\u00e5\u009f\u0006\u0092\u008bf^\u0080{U%d\u0001L;\u00fe\u00cfI*\u0003\u0081\u0084\u0000\u0002\u0081\u0080j\u00d1\u001b\u00d7\u00d5f\u00d2z\u00f49\u00c0.Ah\u00ac\u00fdE\u00b4\u00be\u0085\u00bc\u0099\u008c{\u009b\u008e\u001cwTi?\u008c\rB\u008a\u00a4\u00fc\u00e1\u0010\u0084\u00818BO\u00a6\u008c\u00d10RN\u00ef\u00f6\u00f178c\u0082/\u00a67)\u008b\u00feMF\u00a0\u00b8fe\u00ee\u00f0A\u00179\u0001\u0003[\u001c\u0080j\u00a3\u0018\u0018\r0:\u00a8\u00cc\u009eY#\u00e0jo\u00ab\u00fauh<E;\u00b2\u0007w|\u00f2\u00fd\u00e7\u00cf\u00b1\u009b\u001408\u0014\u00aa\u001d\u00f7\u00b4=[\"+W\u0006\u00b4\u008b\u00940\u000b\u0006\u0007*\u0086H\u00ce8\u0004\u0003\u0005\u0000\u0003/\u00000,\u0002\u0014\t\u00d2\u00d1\u00b0G\u0002)\u00b5\u00be\u00d2\u0090&a\u00d1\u0012\u00f2p\u00c5\u00e6\u001d\u0002\u0014gP\u0002\u0006\u00a7\u0080P\u00bax\u00ae\u00c7\u0017O\u0016\u0004\u007f\u0084\u00ea\u00a2\u00f7";

    ReplyDelete
  18. @lohan+, can you bypass signature just one apk for me? PLEASE !!!!!

    ReplyDelete
  19. Thanks for this info. Keep up the neat work. I'll be returning often thanks for sharing...
    Voot For IPhone
    Bigg Boss 10 Live Feed
    Google Play Store For PC
    Jioplay APK For IOS

    ReplyDelete

Do NOT post about or link to specific apps!