Препарируем Viber. Мини-гид по анализу приложений для Android. Часть 1

Препарируем Viber. Мини-гид по анализу приложений для Android. Часть 1

https://t.me/xakep_1

saruman9

Как‑то раз при устрой­стве на работу мне дали весь­ма инте­рес­ное задание — про­ана­лизи­ровать Android-при­ложе­ние Viber. В нем сле­дова­ло най­ти уяз­вимос­ти с пос­леду­ющей экс­плу­ата­цией. На этом при­мере рас­ска­жу о под­ходе к ана­лизу реаль­ного при­ложе­ния с получе­нием кон­крет­ных резуль­татов за корот­кий срок. Если прой­ти все эти эта­пы, то впол­не воз­можно, что тебе удас­тся най­ти в Viber 0-day 

ЦЕЛЬ

Ис­кать ошиб­ки в при­ложе­нии для Android мож­но по‑раз­ному, так же как и выбирать мес­та для это­го самого поис­ка. В рам­ках этой статьи мы выберем цель пониже уров­нем — раз­деля­емые биб­лиоте­ки, то есть ори­енти­ровать­ся будем на баги memory corruption. Код на Java рас­смот­рим толь­ко в слу­чае, ког­да необ­ходимо выяс­нить его связь с раз­деля­емы­ми биб­лиоте­ками.


Для ана­лиза APK исполь­зовались стан­дар­тные инс­тру­мен­ты из лю­бого пуб­лично­го awesome list, поэто­му я не буду заос­трять вни­мание на их наз­вании, если того не тре­бует кон­текст.

Це­левой APK был получен с од­ного из mirror-сай­тов. К выбору источни­ка APK сто­ит отно­сить­ся серь­езно, пос­коль­ку неред­ко сайт может хра­нить:

  • уже уста­рев­шие вер­сии;
  • толь­ко вер­сии для ненуж­ных плат­форм и архи­тек­тур;
  • мо­дифи­циро­ван­ные при­ложе­ния (воз­можно, с содер­жани­ем мал­вари).

На дан­ном эта­пе име­ет смысл про­вес­ти реког­носци­ров­ку цели: изу­чить CVE, а затем про­вес­ти binary diffing ана­лиз 1-day-уяз­вимос­тей.

 

РАЗДЕЛЯЕМЫЕ БИБЛИОТЕКИ

Пос­ле рас­паков­ки APK в дирек­тории lib мож­но обна­ружить фай­лы биб­лиотек: они не запако­ваны, не зашиф­рованы, не обфусци­рова­ны, не нак­рыты про­тек­тором, что облегча­ет нашу задачу в нес­коль­ко раз. В защиту Viber могу ска­зать, что обыч­но мес­сен­дже­ры не пыта­ются при­менять пас­сивные меры защиты от ана­лиза биб­лиотек, у WhatsApp лишь исполь­зует­ся кас­томный упа­ков­щик, но и то — не ради защиты. Для ана­лиза я выб­рал вер­сии биб­лиотек для архи­тек­туры x86_64 по сле­дующим при­чинам:


  • боль­шее количес­тво инс­тру­мен­тов для этой архи­тек­туры;
  • луч­ше деком­пиляция (это, конеч­но, спор­но, так как мно­гое зависит от выбора инс­тру­мен­та);
  • воз­можность эму­ляции на более высоких ско­рос­тях (моя хост‑машина име­ет архи­тек­туру x86_64);
  • воз­можность час­тично­го ана­лиза на хост‑машине в обход эму­лято­ра;
  • на дан­ном эта­пе нет задачи писать кон­крет­ный экс­пло­ит для пок­рытия боль­шего чис­ла целей, соот­ветс­твен­но, ARM-архи­тек­туры мож­но отбро­сить, если это пот­ребу­ется.

По­нача­лу для повер­хностно­го ана­лиза исполь­зовались такие инс­тру­мен­ты, как IDA Pro, Binary Ninja и rizin (Ghidra не взял, потому что задачу сле­дова­ло решить быс­тро): заг­ружа­ешь биб­лиоте­ку, смот­ришь экспор­тирован­ные сим­волы, находишь стро­ки, нем­ного чита­ешь код. Но затем я перешел к oneline-коман­де — по сути, боль­шего мне и не тре­бова­лось: readelf -W --demangle --symbols $(LIBRARY_SO) | tail -n +4 | sort -k 7 | less.

Пос­ле иден­тифика­ции JNI-фун­кций из биб­лиотек про­хожусь rg/grep по smali-коду и нахожу фай­лы, где содер­жится объ­явле­ние native-фун­кций:


$ readelf -W --demangle --symbols libnativehttp.so | tail -n +4 | sort -k 7 | rg "FUNC.Java_." | less

33: 0000000000001bb5 55 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_HttpEngine_nativeCreateHttp

34: 0000000000001bec 15 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_HttpEngine_nativeDelete

38: 0000000000001bfb 622 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_HttpEngine_nativeTest

44: 00000000000018c3 109 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_NativeDownloader_nativeOnConnected

39: 00000000000015e8 366 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_NativeDownloader_nativeOnData

35: 0000000000001b0c 40 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_NativeDownloader_nativeOnDisconnected

40: 0000000000001930 476 FUNC GLOBAL DEFAULT 13 Java_com_viber_libnativehttp_NativeDownloader_nativeOnHead


$ rg "native.*nativeCreateHttp"

app/src/main/java/com/viber/libnativehttp/HttpEngine.java

9: public static native long nativeCreateHttp();


Даль­не нужен повер­хностный ана­лиз, что­бы выявить, во‑пер­вых, биб­лиоте­ку‑цель, во‑вто­рых, ком­понен­ты open source, иссле­дова­ние которых пред­сто­ит сде­лать поз­днее. Ана­лизи­ровать мож­но с помощью того же readelf или в Rizin либо Binary Ninja: гуг­лим имя экспор­тирован­ного сим­вола и про­водим повер­хностный реверс‑инжи­ниринг, что­бы вос­создать общую кар­тину фун­кций биб­лиоте­ки.

 

Предварительные результаты анализа

Ни­же пред­став­лен спи­сок раз­деля­емых биб­лиотек с крат­ким опи­сани­ем фун­кций или ссыл­кой на про­ект с откры­тыми исходни­ками.


  • libc++_shared.so — C++ standard library.
  • libcrashlytics-common.solibcrashlytics-handler.solibcrashlytics-trampoline.solibcrashlytics.so — Firebase Crashlytics.
  • libreactnativeblob.solibreactnativejni.solibglog_init.solibjscexecutor.solibjsijniprofiler.solibjsinspector.so — React Native.
  • libfb.solibfbjni.so — fbjni.
  • libfolly_futures.so — Folly: Facebook Open-source Library.
  • libfolly_json.so — Folly: Facebook Open-source Librarydouble-conversion.
  • libglog.so — Google Logging Library.
  • libhermes-executor-release.solibhermes.so — Hermes JS Engine.
  • libicuBinder.so — ICU extension for SQLite.
  • libimage_processing_util_jni.so — androidx.camera.core.
  • libimagepipeline.solibnative-filters.solibnative-imagetranscoder.so — Fresco.
  • libgifimage.so — The GIFLIB projectFresco.
  • libjingle_peerconnection_so.so — ста­рый ком­понент (libjingle) из WebRTC.
  • libmux.so — FFmpeg из сос­тава fftools.
  • libpl_droidsonroids_gif.so — android-gif-drawable.
  • librenderscript-toolkit.so — RenderScript.
  • libsigner.so — Adjust SDK for Android.
  • libspeexjni.so — Speex.
  • libsqliteX.so — SQLite for Android.
  • libtensorflowlite_gpu_jni.solibtensorflowlite_jni.so — TensorFlow.
  • libyoga.so — Yoga.
  • libCrossUnblocker.solibFlatBuffersParser.soliblinkparser.solibnativehttp.solibsvg.solibViberRTC.solibvideoconvert.solibVoipEngineNative.so — самопис­ные биб­лиоте­ки c исполь­зовани­ем open source кода.

В ито­ге у нас появ­ляет­ся спи­сок инте­рес­ных биб­лиотек. Сос­тавлял­ся он исхо­дя все­го из одно­го усло­вия: как мож­но боль­ше самопис­ного кода, мень­ше ком­понен­тов open source. Вот этот спи­сок:

  • libCrossUnblocker.so;
  • libFlatBuffersParser.so;
  • liblinkparser.so;
  • libnativehttp.so;
  • libsvg.so;
  • libViberRTC.so;
  • libvideoconvert.so;
  • libVoipEngineNative.so.

 

ФУНКЦИИ

Преж­де чем ана­лизи­ровать какую‑либо фун­кцию, сна­чала нуж­но выяс­нить, может ли ата­кующий до нее доб­рать­ся. Для это­го отсорти­руем по при­ори­тету все биб­лиоте­ки и фун­кции, а затем про­верим, отку­да вызыва­ются пос­ледние. Для ана­лиза Java-кода я буду исполь­зовать связ­ку jadx (деком­пиляция) + Android Studio (рефак­торинг) + Understand (ана­лиз гра­фов свя­зей перемен­ных, фун­кций и дан­ных).


До­пол­нитель­но про­верим в каж­дой биб­лиоте­ке наличие JNI-фун­кций. Может быть, есть те, что исполь­зуют­ся с помощью RegisterNatives.

 

libFlatBuffersParser.so

При более деталь­ном ана­лизе выяс­няет­ся, что это open source биб­лиоте­ка FlatBuffers. Оставля­ем ее ана­лиз на потом.


 

libsvg.so

Поль­зуем­ся ути­литой strings и Ghidra, что­бы получить стро­ки из бинар­ного фай­ла. По ним мы понима­ем, что код написан на C++:


[...]

_ZTVN10__cxxabiv121__vmi_class_type_infoE

_ZNSt6__ndk119__shared_weak_countD2Ev

_ZTINSt6__ndk119__shared_weak_countE

__android_log_print

_ZNKSt6__ndk16locale9has_facetERNS0_2idE

_ZNKSt6__ndk16locale9use_facetERNS0_2idE

[...]

Про­верим еще и наличие RTTI-информа­ции — в нашем слу­чае уда­ча бла­гово­лит нам, таковая име­ется. С помощью пла­гина для Ghidra Ghidra C++ Class and Run Time Type Information Analyzer вос­ста­новим струк­туру клас­сов кода C++.

Ре­зуль­тат вос­ста­нов­ления RTTI-информа­ции — клас­сы C++ и их методы

На пер­вый взгляд, здесь исполь­зует­ся собс­твен­ная SVG-биб­лиоте­ка, что хорошо. В ходе даль­нейше­го ана­лиза Java-кода (ана­лиз дерева вызовов) выяс­няет­ся, что фун­кции биб­лиоте­ки задей­ство­ваны в основном для заг­рузки ассе­тов при­ложе­ния (дирек­тория ./assets/svg в APK-фай­ле), а это нам не под­ходит. Одна­ко же цепоч­ка вызовов натив­ной фун­кции nativeParseFd → parseFile исполь­зует­ся для пар­синга сти­керов. А вот это уже инте­рес­но! Но я решил оста­вить эту фун­кцию на потом, пос­коль­ку моя инту­иция под­ска­зала: бес­смыс­ленно писать пар­сер SVG с нуля. Соот­ветс­твен­но, исполь­зует­ся что‑то из open source.

Граф вызова фун­кций SVG native в Understand

 

libnativehttp.so

В ходе ана­лиза воз­ника­ет воп­рос, с какой целью соз­давалась эта биб­лиоте­ка, ведь кажет­ся, что ее воз­можнос­ти не очень широки: обер­тки над фун­кци­ями обра­бот­ки сетевых дан­ных. Обра­бот­чики при воз­никно­вении событий вызыва­ют все тот же Java-код, а не что‑то натив­ное. Может быть, здесь ког­да‑то были какие‑то фун­кци­ональ­ные воз­можнос­ти, ну или пред­полага­лись? Подоб­ное я уже видел в мес­сен­дже­ре Telegram: legacy-кода хоть отбавляй, от такого боль­шого attack surface понача­лу чешут­ся руки, но пос­ле ана­лиза все вста­ет на свои мес­та. Поэто­му, ува­жаемый читатель, при­нимай во вни­мание, что иног­да могут встре­чать­ся не толь­ко dead code, но и dead libraries, на ана­лиз которых мож­но впус­тую пот­ратить кучу цен­ного вре­мени.


Здесь же со вре­менем (читай — с более глу­боким ана­лизом) все ста­новит­ся понят­но. Эта биб­лиоте­ка исполь­зует­ся дру­гими биб­лиоте­ками, то есть фун­кции, отве­чающие за обра­бот­ку сетевых дан­ных, дол­жны быть реали­зова­ны в дру­гом мес­те. По сво­ей сути это interceptor, который может исполь­зовать­ся, нап­ример, для счет­чика сетево­го тра­фика или для сбо­ра ана­лити­ки.

 

Другие библиотеки

Ни­же опи­саны мои дей­ствия, свя­зан­ные все­го с одной биб­лиоте­кой. Я нашел ее инте­рес­ной с точ­ки зре­ния поис­ка low-hanging fruits, поэто­му оста­вил на потом ана­лиз осталь­ных биб­лиотек:


  • libCrossUnblocker.so;
  • libvideoconvert.so;
  • libViberRTC.so;
  • libVoipEngineNative.so.

Но сто­ит приз­нать, что имен­но в этих биб­лиоте­ках могут быть най­дены инте­рес­ные уяз­вимос­ти, пос­коль­ку в них зак­лючен набор фун­кций VoIP, в которых дос­таточ­но мест для ошиб­ки.

 

АНАЛИЗ ЦЕЛИ

В качес­тве подопыт­ной я выб­рал биб­лиоте­ку liblinkparser.so по при­чине, опи­сан­ной выше.


Ана­лиз этой биб­лиоте­ки начал­ся с деталь­ного реверс‑инжи­нирин­га. Нас­толь­ко деталь­ного, что я пот­ратил на это два дня и забыл про­верить на прак­тике, исполь­зуют­ся ли вооб­ще фун­кции из этой биб­лиоте­ки и могут ли быть выз­ваны ата­кующим с его вход­ными дан­ными. Я затянул с реверс‑инжи­нирин­гом, потому что фун­кци­ональ­ные воз­можнос­ти показа­лись мне весь­ма инте­рес­ными с точ­ки зре­ния поис­ка уяз­вимос­тей.

В качес­тве основно­го инс­тру­мен­та для реверс‑инжи­нирин­га я выб­рал Binary Ninja, пред­варитель­но срав­нив все име­ющиеся инс­тру­мен­ты. Дав­ным‑дав­но я делал срав­нение с IDA Pro, по боль­шей час­ти оно до сих пор акту­аль­но, толь­ко у BN ста­ло гораз­до боль­ше плю­сов. Я рекомен­дую так пос­тупать каж­дый раз, потому что с исполь­зовани­ем раз­ных инс­тру­мен­тов удоб­нее ревер­сить раз­ные тар­геты. Этот воп­рос сто­ит осо­бен­но остро, если вре­мя на ана­лиз огра­ничен­но.

Но мес­тами я исполь­зовал IDA Pro, ког­да появ­лялась пот­ребность про­водить отладку биб­лиотек: делать это с помощью одно­го GDB/LLDB ока­залось, мяг­ко ска­жем, неудоб­но (у BN отладчик еще сырой, а пла­гины‑связ­ки не всег­да хорошо работа­ют). Будь это реаль­ный слу­чай, а не тес­товый, ско­рее все­го, я бы выб­рал дру­гой инс­тру­мент в качес­тве основно­го. В даль­нейшем мне пот­ребу­ется авто­мати­зация про­цес­сов реверс‑инжи­нирин­га, удоб­ное написа­ние экс­пло­ита, перенос уже име­ющих­ся дан­ных меж­ду вер­сиями биб­лиотек, отладка сра­зу нес­коль­ких модулей и тому подоб­ное. Все это делать я боль­ше при­вык в Ghidra, но сей­час, как я уже ска­зал, важ­на ско­рость.

Ре­комен­дую прис­мотреть­ся к Binary Ninja

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

 

Досягаемость функции

В качес­тве стен­да я выб­рал эму­лятор на QEMU (Android Virtual Device) архи­тек­туры x86_64. Пос­ле тес­тового запус­ка Viber на эму­лято­ре ста­ло понят­но, что какие‑либо механиз­мы защиты отсутс­тву­ют, и это не мог­ло не радовать. Заг­рузил туда сер­вер Frida, JS-скрип­ты брал пря­мо из jadx, что сыг­рало в ито­ге со мной злую шут­ку (об этом ниже).


Пер­вой фун­кци­ей для ана­лиза (спой­лер: и пос­ледней) я выб­рал nativeGeneratePreview:

let LinkParser = Java.use("com.viber.liblinkparser.LinkParser");

LinkParser["nativeGeneratePreview"].implementation = function (url, http) {

console.log(`LinkParser.nativeGeneratePreview is called: url=${url}, http=${http}`);

let result = this["nativeGeneratePreview"](url, http);

console.log(`LinkParser.nativeGeneratePreview result=${result}`);

return result;

};

Скрипт, к сожале­нию, не обра­баты­вал­ся: Frida утвер­жда­ла, что дан­ный класс не най­ден. Я про­бовал изме­нить кон­фигура­ции запус­ка Frida, про­извел трас­сиров­ку натив­ных фун­кций с помощью frida-trace — все работа­ло. Здесь мож­но было бы и оста­новить­ся, пос­коль­ку факт того, что фун­кция вызыва­ется, доказан, но мне ста­ло инте­рес­но, почему не работа­ет Frida, видимых на то при­чин я не находил. Я про­верил наличие заг­ружен­ных клас­сов:

Java.enumerateLoadedClasses({

onMatch: function(className) {

console.log(className);

},

});

Класс не был заг­ружен (что, в прин­ципе, логич­но, потому что фун­кции при­ложе­ния я еще не вызывал). Тог­да я решил поп­робовать вруч­ную заг­рузить класс (может, внут­ри работа­ет кас­томный заг­рузчик?):

Java.enumerateClassLoaders({

onMatch: function(loader){

Java.classFactory.loader = loader;

var LinkParserClass;

try{

LinkParserClass = Java.use("com.viber.liblinkparser.LinkParser");

LinkParserClass.nativeGeneratePreview.implementation = function(){

console.log("[+] Inside nativeGeneratePreview method");

}

}catch(error){

if(error.message.includes("ClassNotFoundException")){

console.log("Class not found");

}

}

}

});

Это не дало никако­го резуль­тата, ни положи­тель­ного, ни отри­цатель­ного. И тут меня осе­нило! Я забыл Java.perform, без него код не работа­ет. В прош­лом я исполь­зовал Frida не для Android, поэто­му сов­сем упус­тил из вида столь важ­ную деталь, а jadx мне о ней не напом­нил. В ито­ге все пре­дыду­щие скрип­ты успешно отра­баты­вали.

Пе­рех­ват вызова натив­ной фун­кции `nativeGeneratePreview`


Report Page