AdMob SDK reverse or another way to protect your code

    This story began with the news that the Bentley Salon opens in Minsk in the summer. So I realized that the time has come to embed advertising in my second game, otherwise I risk being at the end of the line. I downloaded the latest version of the SDK (6.4.1 at the moment), integrated it into the game, launched it and immediately saw suspicious lines in logcat:

    05-14 15:06:06.312: D/dalvikvm(1379): DexOpt: --- BEGIN 'ads2133480362.jar' (bootstrap=0) ---
    05-14 15:06:06.632: D/dalvikvm(1413): creating instr width table
    05-14 15:06:06.671: D/dalvikvm(1413): DexOpt: load 2ms, verify+opt 18ms
    05-14 15:06:06.703: D/dalvikvm(1379): DexOpt: --- END 'ads2133480362.jar' (success) ---
    05-14 15:06:06.703: D/dalvikvm(1379): DEX prep '/data/data/by.squareroot.kingsquare/cache/ads2133480362.jar': unzip in 0ms, rewrite 391ms
    

    dexopt is a program for checking and optimizing DEX files. It is not clear why she should work, especially after starting the application and with the strange ads2133480362.jar file . Since I had nothing to do with this file, and this has never happened before, all suspicions fell on AdMob. Apparently, the AdMob SDK saves some jar file to the application’s cache directory, loads classes from there and uses them when loading and displaying banners. It remains to be seen what the AdMob SDK developers are hiding so diligently from us.

    Reversim AdMob SDK


    Of course, the classes in the SDK are obfuscated, but this does not greatly complicate our task. In order to find some starting point, let's look at which classes have a call to the Context.getCacheDir () method . They turned out to be few, only two. In one of them, this method is used to set WebSettings.setAppCachePath () , so that there remains only one suspicious class with the already empty name ak.class .
    Personally, I use JD to decompile . Let's look at the part of the method in this class where there is a call to Context.getCacheDir () :

    byte[] arrayOfByte1 = an.a(ao.a());
    byte[] arrayOfByte2 = an.a(arrayOfByte1, ao.b());
    File localFile2 = File.createTempFile("ads", ".jar", paramContext.getCacheDir());
    FileOutputStream localFileOutputStream = new FileOutputStream(localFile2);
    localFileOutputStream.write(arrayOfByte2, 0, arrayOfByte2.length);
    localFileOutputStream.close();
    

    If you remove the curse imposed by the evil proguard and rename the classes and variables to more understandable ones, you get the following code:

    String keyBase64 = Base64Consts.getKeyBase64();
    byte[] keyBytes = Decrypter.decodeKey(keyBase64);
    String classBase64 = Base64Consts.getClassBase64();
    byte[] classBytes = Decrypter.decodeClassBytes(keyBytes, classBase64);
    File classFile = File.createTempFile("ads", ".jar", context.getCacheDir());
    FileOutputStream out = new FileOutputStream(classFile);
    out.write(classBytes, 0, classBytes.length);
    out.close();
    

    Now you can parse in order where the jar file comes from. The Base64Consts class (formerly ao ) contains Base64-encoded strings:

    public class Base64Consts {
    	public static String getKeyBase64()  {
    		return "ARuhFl7nBw/97YxsDjOCIqF0d9D2SpkzcWN42U/KR6Q=";
    	}
    	public static String getClassBase64() {
    		return "SuhNEgGjhJl/XS1FVuhqPkUehkYsZY0198PVH9C0C..."; // эта строка очень длинная, поэтому здесь только ее начало
    	}
    }
    

    The keyBase64 string is turned into a key using the Decrypter.decodeKey () method :

    public static byte[] decodeKey(String keyBase64) {
    	byte[] keyBytes = Base64Util.decode(keyBase64);
    	ByteBuffer byteBuffer = ByteBuffer.wrap(keyBytes, 4, 16);
    	byte[] key128 = new byte[16];
    	byteBuffer.get(key128);
    	for (int i = 0; i < key128.length; i++) {
    		key128[i] = ((byte)(key128[i] ^ 0x44));
    	}
    	return key128;
    }
    

    The method decodes the string into an array of bytes (the AdMob SDK uses its class for these purposes, since android.util.Base64 appeared only in api level 8) and a block of 16 bytes is taken from the resulting array of 32 bytes starting from the 5th . Each byte is xor magic number 0x44. As a result of these manipulations, a 128-bit AES key is obtained.

    The classBase64 string is converted into a byte array, which is a jar file, using the Decrypter.decodeClassBytes () method :

    public static byte[] decodeClassBytes(byte[] keyBytes, String cryptedBytesBase64) {
    	byte[] cryptedBytes = Base64Util.decode(cryptedBytesBase64);
    	ByteBuffer buffer = ByteBuffer.allocate(cryptedBytes.length);
    	buffer.put(cryptedBytes);
    	buffer.flip();
    	byte[] initializationVector = new byte[16];
    	byte[] input = new byte[cryptedBytes.length - 16];
    	buffer.get(initializationVector);
    	buffer.get(input);
    	SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
    	Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    	cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(initializationVector));
    	return cipher.doFinal(input);
    }
    

    The method decodes a string into an array of bytes, the first 16 bytes in this array are the initialization vector, everything else is encrypted data. The output is an array of bytes, which is stored in a jar file and from which classes are loaded dynamically. Finally, you can see what is inside.

    Mysterious ad.jar

    The AdMob SDK uses DexClassLoader to load classes from this jar file . Used internally like this:

    DexClassLoader classLoader = new DexClassLoader(classFile, context.getCacheDir()), null, context.getClassLoader());
    Class clazz = classLoader.loadClass(b(keyBytes, Base64Consts.getClassNameBase64()));
    Method m = clazz.getMethod(b(keyBytes, Base64Consts.getMethodNameBase64()), new Class[0]);
    

    After that, the jar file is deleted. The names of classes and methods are encrypted in the same way as the jar file itself (Base64 + AES), so it will be faster and easier to immediately look inside the jar file.

    It was expected that the classes.dex file turned out to be inside. Having run it through dex2jar, we got another jar-file, this time with classes.

    Thank you Mario! But our princess is in another castle!

    Here disappointment awaited me. Inside were five obfuscated classes that did not represent anything interesting. For example, a class like this:

    public class a {
      public static Long a()  {
        return Long.valueOf(Calendar.getInstance().getTime().getTime() / 1000L);
      }
    }
    

    And like this:

    public class d {
      public static String a() {
        new Build.VERSION();
        return Build.VERSION.RELEASE;
      }
    }
    

    One of the classes takes the value Settings.Secure.ANDROID_ID and considers it an md5 hash. Another considers the SHA-2 hash of the entire apk file. Apparently, these parameters are used in requests sent to the server.
    In general, no secret algorithms, no hidden messages, nothing. Why hide such a trivial code like that is a mystery to me.

    A needle in an egg, an egg in a duck ...


    Although there was nothing interesting inside, AdMob uses an interesting way to protect its code. The code is compiled, assembled into a jar file, the jar file is converted to a dex format, the dex file is packed again into a jar, the jar file is encrypted by AES and finally Base64 encoded. In principle, a good way, especially if you get the key from the server.

    Although it may be that such a tricky way would fall under the definition of Dangerous Products from the Google Play Developer Program Policies :
    An app downloaded from Google Play may not modify, replace or update its own APK binary code using any method other than Google Play's update mechanism.

    In principle, the code changes - a library is formed from the air, from which classes are loaded. But AdMob can definitely do that.

    Also popular now: