За семью замками. Защищаем приложение для Android от отладчиков, эмуляторов и Frida

Когда задумываешься о защите приложения от реверса, в первую очередь на ум приходят такие слова, как обфускация и шифрование. Но это только часть решения проблемы. Вторая половина — это детект и защита от самих инструментов реверса: отладчиков, эмуляторов, Frida и так далее. В этой статье мы рассмотрим техники, которые мобильный софт и зловреды используют, чтобы спрятаться от этих инструментов.
warning
Не стоит воспринимать приведенную в статье информацию как рецепт абсолютной защиты. Такого рецепта нет. Мы всего лишь даем себе отсрочку, затормаживаем исследование, но не делаем его невозможным. Все это — бесконечная игра в кошки‑мышки, когда исследователь взламывает очередную защиту, а разработчик придумывает ей более изощренную замену.
Важный момент: я приведу множество разных техник защиты, и у тебя может возникнуть соблазн запихнуть их все в один класс (или нативную библиотеку) и с удобством для себя запускать один раз при старте приложения. Так делать не стоит, механизмы защиты должны быть разбросаны по приложению и стартовать в разное время. Так ты существенно усложнишь жизнь взломщику, который в противном случае мог бы определить назначение класса/библиотеки и целиком заменить его на одну большую заглушку.
Root
Права root — один из главных инструментов реверсера. Root позволяет запускать Frida без патчинга приложений, использовать модули Xposed для изменения поведения приложения и трейсинга приложений, менять низкоуровневые параметры системы. В целом наличие root четко говорит о том, что окружению исполнения доверять нельзя. Но как его обнаружить?
Самый простой вариант — поискать исполняемый файл su в одном из системных каталогов:
- /sbin/su
- /system/bin/su
- /system/bin/failsafe/su
- /system/xbin/su
- /system/sd/xbin/su
- /data/local/su
- /data/local/xbin/su
- /data/local/bin/su
Бинарник su всегда присутствует на рутованном устройстве, ведь именно с его помощью приложения получают права root. Найти его можно с помощью примитивного кода на Java:
private static boolean findSu() {
String[] paths = { "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su" };
for (String path : paths) {
if (new File(path).exists()) return true;
}
return false;
}
Либо использовать такую функцию, позаимствованную из приложения rootinspector:
jboolean Java_com_example_statfile(JNIEnv * env, jobject this, jstring filepath) {
jboolean fileExists = 0;
jboolean isCopy;
const char * path = (*env)->GetStringUTFChars(env, filepath, &isCopy);
struct stat fileattrib;
if (stat(path, &fileattrib) < 0) {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NATIVE: stat error: [%s]", strerror(errno));
} else
{
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "NATIVE: stat success, access perms: [%d]", fileattrib.st_mode);
return 1;
}
return 0;
}
Еще один вариант — попробовать не просто найти, а запустить бинарник su:
try {
Runtime.getRuntime().exec("su");
} catch (IOException e) {
// Телефон не рутован
}
Если его нет, система выдаст IOException. Но здесь нужно быть осторожным: если устройство все‑таки имеет права root, пользователь увидит на экране запрос этих самых прав.
Еще один вариант — найти среди установленных на устройство приложений менеджер прав root. Он как раз и отвечает за диалог предоставления прав:
- com.thirdparty.superuser
- eu.chainfire.supersu
- com.noshufou.android.su
- com.koushikdutta.superuser
- com.zachspong.temprootremovejb
- com.ramdroid.appquarantine
- com.topjohnwu.magisk
Для поиска можно использовать такой метод:
private static boolean isPackageInstalled(String packagename, Context context) {
PackageManager pm = context.getPackageManager();
try {
pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
return true;
} catch (NameNotFoundException e) {
return false;
}
}
Искать можно и по косвенным признакам. Например, SuperSU, некогда популярное решение для получения прав root, имеет несколько файлов в файловой системе:
- /system/etc/init.d/99SuperSUDaemon
- /system/xbin/daemonsu — SuperSU
Еще один косвенный признак — прошивка, подписанная тестовыми ключами. Это не всегда подтверждает наличие root, но точно говорит о том, что на устройстве установлен кастом:
private boolean isTestKeyBuild() {
String buildTags = android.os.Build.TAGS;
return buildTags != null && buildTags.contains("test-keys");
}
Magisk
Все эти методы детекта root отлично работают до тех пор, пока ты не столкнешься с устройством, рутованным с помощью Magisk. Это так называемый systemless-метод рутинга, когда вместо размещения компонентов для root-доступа в файловой системе поверх нее подключают другую файловую систему (оверлей), содержащую эти компоненты.
Читать новость в источнике Xakep