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 — это программа для проверки и оптимизации DEX-файлов. Непонятно, с чего бы это ей работать, особенно после запуска приложения и со странным файлом ads2133480362.jar. Так как я к этому файлу никакого отношения не имел и раньше такого не было, все подозрения пали на AdMob. Видимо, AdMob SDK сохраняет какой-то jar-файл в кэш-директорию приложения, подгружает оттуда классы и использует их при загрузке и показе баннеров. Осталось узнать, что же так старательно прячут от нас разработчики AdMob SDK.
Реверсим AdMob SDK
Конечно же классы в SDK обфусцированы, но это не сильно усложняет нам задачу. Для того, чтобы найти какую-то отправную точку, посмотрим в каких классах есть вызов метода Context.getCacheDir(). Их оказалось немного, всего лишь два. В одном из них этот метод используется для установки WebSettings.setAppCachePath(), так что остается только один подозрительный класс с ни о чем уже не говорящим названием ak.class.
Лично я для декомпиляции использую JD. Посмотрим на часть метода в этом классе, где есть вызов 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();
Если снять проклятие, наложенное злым proguard-ом, и переименовать классы и переменные в более понятные, то получится такой код:
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();
Теперь можно разобрать по порядку, откуда же берется jar-файл. Класс Base64Consts (бывший ao) содержит строки в Base64 кодировке:
public class Base64Consts { public static String getKeyBase64() { return "ARuhFl7nBw/97YxsDjOCIqF0d9D2SpkzcWN42U/KR6Q="; } public static String getClassBase64() { return "SuhNEgGjhJl/XS1FVuhqPkUehkYsZY0198PVH9C0C..."; // эта строка очень длинная, поэтому здесь только ее начало } }
Строка keyBase64 превращается в ключ с помощью метода Decrypter.decodeKey():
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; }
Метод декодирует строку в массив байт (AdMob SDK использует свой класс для этих целей, т. к. android.util.Base64 появился только в api level 8) и из получившегося массива длинной в 32 байта берется блок в 16 байт начиная с 5-го. Каждый байт xor-ится волшебным числом 0x44. В результате этих манипуляций получается 128-битный ключ AES.
Строка classBase64 превращается в массив байт, который представляет собой jar-файл, с помощью метода Decrypter.decodeClassBytes():
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); }
Метод декодирует строку в массив байт, первые 16 байт в этом массиве — вектор инициализации, все остальное — зашифрованные данные. На выходе получается массив байт, который сохраняется в jar-файл и из которого динамически подгружаются классы. Наконец, можно посмотреть, что же там внутри.
Таинственный ad.jar
Для загрузки классов из этого jar-файла AdMob SDK использует DexClassLoader. Используется внутри это так:
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]);
После этого jar-файл удаляется. Имена классов и методов зашифрованы таким же способом, как и сам jar-файл (Base64 + AES), поэтому будет быстрее и проще сразу посмотреть внутрь jar-файла.
Вполне ожидаемо внутри оказался файл classes.dex. Прогнав его через dex2jar получился еще один jar-файл, на этот раз с классами.
Thank you Mario! But our princess is in another castle!
Вот тут меня поджидало разочарование. Внутри оказалось пять обфусцированных классов, которые не представляли собой ничего интересного. Например, такой класс:
public class a { public static Long a() { return Long.valueOf(Calendar.getInstance().getTime().getTime() / 1000L); } }
И вот такой:
public class d { public static String a() { new Build.VERSION(); return Build.VERSION.RELEASE; } }
Один из классов берет значение Settings.Secure.ANDROID_ID и считает его md5-хеш. Другой считает SHA-2 хеш всего apk-файла. Видимо, эти параметры используются в запросах, отправляемых на сервер.
В общем, ни секретных алгоритмов, ни скрытых посланий, ничего. Зачем так прятать такой тривиальной код — для меня загадка.
Иголка в яйце, яйцо в утке…
Хоть ничего интересного внутри не оказалось, AdMob использует интересный способ для защиты своего кода. Код компилируется, собирается в jar-файл, jar-файл конвертируется в dex-формат, dex-файл запаковывается снова в jar, jar-файл шифруется AES и наконец кодируется Base64. В принципе, неплохой способ, особенно если получать ключ с сервера.
Хотя может быть, что такой хитрый способ будет попадать под определение Dangerous Products из 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.
В принципе, код меняется — из воздуха образуется библиотека, из которой подгружаются классы. Но AdMob-у так делать точно можно.
ссылка на оригинал статьи http://habrahabr.ru/post/179723/
Добавить комментарий