Решил Android-таск на реверс от Delivery Club. Делюсь райтапом.

Первичный осмотр

Перед нами Android-приложение с единственной кнопкой на экране. Запускаем в эмуляторе — при нажатии на кнопку приложение крашится. Внутри java-части приложения тоже ничего интересного: кнопка вызывает нативный метод collectMetrics и в зависимости от возвращаемого значения выводит одно из двух сообщений.

Нативная библиотека

При беглом просмотре нативной части приложения в IDA Pro становится понятно, что код хорошо пообфусцирован техниками Control Flow Flattening, Opaque Predicate и многими другими. Идём в JNI_OnLoad — функцию, которую дёргает JVM непосредственно при загрузке нативной библиотеки через System.loadLibrary. Сразу проставляем тип JNIEnv* везде, где есть indirect call'ы, так как обычно в них скрываются вызовы JNI. Из интересного там есть только JNIEnv->RegisterNatives, который регистрирует нативный обработчик для Java-метода collectMetrics (он вызывается при нажатии на кнопку), поэтому, судя по всему, вся логика проверок должна быть там.

Ищем проверки окружения

Да, определённо это какие-то проверки на эмулятор

Да, определённо это какие-то проверки на эмулятор

Так как из кода ничего не понятно, а писать деобфускатор пока что не хочется, будем ковырять приложение динамическими методами. Набрасываем первый вариант скрипта для DBI-фреймворка Frida. С помощью фриды я анализирую вызовы методов внутри приложения и, если требуется, могу изменить их поведение. В скрипте ниже я изменил реакцию приложения на нажатие кнопки и отредактировал поля класса android.os.Build, чтобы приложение не могло по ним понять, что оно запускается в эмуляторе:

var hooked = false;

Java.perform(function() {
    let MainActivity_a = Java.use("com.example.dc_challenge.MainActivity$a");
    MainActivity_a.onClick.implementation = function (view) {
        if (!hooked) {
            disarmBuildChecks();
            hooked = true;
        }
        this.onClick(view);
    }
});

function disarmBuildChecks() {
    var config = {
        BOARD: "prada",
        BOOTLOADER: "unknown",
        BRAND: "Xiaomi",
        DEVICE: "prada",
        DISPLAY: "MMB29M",
        FINGERPRINT: "Xiaomi/prada/prada:6.0.1/MMB29M/v8.0.3.0.0.MCECNDG:user/release-keys",
        HARDWARE: "qcom",
        HOST: "c3-miui-ota-bd20",
        ID: "MMB29M",
        MANUFACTURER: "Xiaomi",
        MODEL: "Redmi 4",
        PRODUCT: "prada",
        RADIO: "unknown",
        SERIAL: "17fc681d",
        TAGS: "release-keys",
        TIME: 1476359370000,
        TYPE: "user",
        USER: "builder",
    };

    var Build = Java.use('android.os.Build');

    Object.keys(config).map(function (key) {
        Build[key].value = config[key];
    });
}

Пробуем нажать кнопку под фридой — и снова падение. Определённо, проверками build-параметров приложение не ограничивается. Копаем дальше.

В списке импортов нативной библиотеки ну очень много разных функций, и наиболее интересными видятся функции работы со строками. Возможно, какие-то из них используются для проверок окружения.

Untitled

Пишем вспомогательную функцию для быстрого трейсинга функций работы со строками:

function hook(name, count) {
    Interceptor.attach(Module.findExportByName("libc.so", name), {
        onEnter: function(args) {
            let bt = DebugSymbol.fromAddress(Thread.backtrace(this.context, Backtracer.ACCURATE)[0]);
            let arg = [];
            for (var i = 0; i < count; i++){
                try {
                    arg.push(Memory.readCString(args[i]));
                } catch (e) {}
            }
            if (bt.moduleName.indexOf("libchallenge.so") !== -1) {
                console.log(name + '("' + arg.join('", "') + '") ' + bt);
            }
        }
    });
}

И трейсим всё что нашли в импортах:

function makeHooks() {
    hook("strcmp", 2);
    hook("strncmp", 2);
    hook("strncpy", 2);
    hook("strcat", 2);
    hook("strchr", 1);
    hook("strcspn", 2);
    hook("strcpy", 2);
    hook("strlen", 1);
    hook("strcasecmp", 2);
    hook("snprintf", 8);
    hook("strdup", 1);
    hook("strncasecmp", 2);
    hook("strrchr", 1);
    hook("strspn", 2);
    hook("strstr", 2);
    hook("strtol", 1);
    hook("strtoul", 1);
}