Showing posts with label common problem. Show all posts
Showing posts with label common problem. Show all posts

Tuesday, January 25, 2011

why wont apk tool recompile?

if you are getting strange errors related to resources. for example:
W: Could not find sources
I: Checking whether resources has changed...
I: Building resources...
C:\someapp\res\values\public.xml:2395: error: Public entry identifier 0x107001d entry index is larger than available symbols (index 29, total symbols 29).
C:\someapp\res\values\public.xml:2395: error: Public symbol array/carrier_properties declared here is not defined.
Exception in thread "main" brut.androlib.AndrolibException: brut.common.BrutException: could not exec command: [aapt, p, -F, C:\Users\Soontir\AppData\Local\Temp\APKTOOL5080969896487671571.tmp, -x, -S, C:\someapp\res, -M, C:\framework\framework-res1\AndroidManifest.xml]
        at brut.androlib.res.AndrolibResources.aaptPackage(Unknown Source)
        at brut.androlib.Androlib.buildResourcesFull(Unknown Source)
        at brut.androlib.Androlib.buildResources(Unknown Source)
        at brut.androlib.Androlib.build(Unknown Source)
        at brut.androlib.Androlib.build(Unknown Source)
        at brut.apktool.Main.cmdBuild(Unknown Source)
        at brut.apktool.Main.main(Unknown Source)
Caused by: brut.common.BrutException: could not exec command: [aapt, p, -F, C:\Users\Soontir\AppData\Local\Temp\APKTOOL5080969896487671571.tmp, -x, -S, C:\framework\framework-res1\res, -M, C:\someapp\AndroidManifest.xml]
        at brut.util.OS.exec(Unknown Source)
        ... 7 more
the problem could be non-standard resource qualifiers. it affects current versions of apktool (1.3.2), but may be fixed in a later version. the solution is to decomile using --keep-broken-res and fix the xml file manually. for an explanation of how to do this, read this issue by the developer himself: http://code.google.com/p/android-apktool/issues/detail?id=128#c1

why wont google maps work after resigning or recompiling?

this is because access to google's stuff requires an api key, stored in xml as a resource or perhaps in code, that is linked with the certificate of the app. if you modified and resigned it, the certificate is different and will not match the api key. you will have to get a valid key for the signature you are using.

you should already have your own certificate you sign everything with. this way if you release an updated version of the app, users of your previous modified version can update without needing to uninstall the old one first. if you don't already have your own certificate, follow the directions here: Signing Your Applications | Android Developers. you will need to use keytool and examples are given in the link.

once you're done or if you already have your own certificate, follow the instructions here: Obtaining a Maps API Key - Google Projects for Android.

when finished with that, and you have your own api key, decompile your apk with apktool so you can modify resources. you are looking for an xml file with a string like:
android:apiKey="3BCknOWFD_vyechU238a1f9YOWyYP2Z91CEfQWw"

or if it is created dynamically in code, you will see the initialization of a mapview object. the constructor takes a string as the second parameter which is the api key. here's an example:
# replace p2 with our api key here:
const-string p2, "3BCknOWFD_vyechU238a1f9YOWyYP2Z91CEfQWw"

# create the mapview object with our modified api key
# this line should already be here and doesn't need to be changed
invoke-direct {p0, p1, p2}, Lcom/google/android/maps/MapView;-><init>(Landroid/content/Context;Ljava/lang/String;)V

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, October 19, 2010

unexplainable problems after recompilation with apktool

something i have seen many times is when a program is recompiled it has strange errors. perhaps a progress bar is not showing up correctly, the layout is somehow distorted or there are strange force closes that happen. in my experience the reason for this is almost always apktool's handling of resources.

if you are having trouble, try simply dumping and building without changing anything with apktool. if there is a problem, then apktool is the culprit. i suggest making an issue on apktool's page here: http://code.google.com/p/android-apktool/issues/entry

you don't have to go into detail about what you're using apktool for. there are lots of legitimate reasons such as adding localization. it should suffice to mention in the report that simply dumping/building results in a distorted .apk.

if you are merely modifying smali code and don't need to modify the resources, try using the --no-res option, or you could just use smali/baksmali. if you are trying to modify an .xml resource then you might have to do it manually.

working with smali means working with the classes.dex file in the apk, and not the apk itself. you will also have to handle resigning and zipaligning (optional) yourself