📜 ⬆️ ⬇️

Reverse AdMob SDK or another way to protect your code

This story began with the news that the Bentley salon opens in Minsk in summer. So I realized that it was time to embed advertising in my second game, otherwise I risk to end up in the queue. I downloaded the latest version of the SDK (6.4.1 at the moment), integrated it into the game, launched it and immediately saw the 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 it would work for her, especially after launching the application and with the strange ads2133480362.jar file. Since I had nothing to do with this file and this was not the case before, all suspicions fell on AdMob. Apparently, AdMob SDK saves some jar file to the application cache directory, loads classes from there and uses them when loading and displaying banners. It remains to find out what the AdMob SDK developers are so diligently hiding from us.

Reverse AdMob SDK


Of course, the classes in the SDK are obfuscated, but this does not complicate our task very much. 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 a little, just two. In one of them, this method is used to set WebSettings.setAppCachePath () , so that there remains only one suspicious class with the name of ak.class that no longer says anything .
Personally, I use JD for decompilation. 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 classes and variables into 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 disassemble in order, where does the jar-file come from. The Base64Consts class (formerly ao ) contains strings in Base64 encoding:

 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 a string into an array of bytes (AdMob SDK uses its class for this purpose, since android.util.Base64 appeared only at api level 8) and a block of 16 bytes from the 5th byte is taken from the resulting 32-byte array. . Every byte is a 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 is the initialization vector, and the rest is encrypted data. The output is an array of bytes, which is stored in a jar-file and from which classes are dynamically loaded. Finally, you can see what's inside.

Mysterious ad.jar

To load classes from this jar file, the AdMob SDK uses the DexClassLoader . Used internally is 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 this, the jar file is deleted. The names of the 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 is quite expected that the classes.dex file is inside. Having driven 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 it turned out five obfuscated classes that were not anything interesting. For example, this class:

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

And this:

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

One of the classes takes the value of Settings.Secure.ANDROID_ID and counts it as md5-hash. Another one 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 so hide such a trivial code 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 into a dex-format, the dex-file is packed again into a jar, the jar-file is encrypted with AES and finally encoded with Base64. In principle, a good way, especially if you get the key from the server.

Although it may be that such a clever way will fall under the definition of Dangerous Products from the Google Play Developer Program Policies :
Identical app download code for 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 do that for sure.

Source: https://habr.com/ru/post/179723/


All Articles