Продукты «Хакера»

  • Ежеквартальный «Хакер» #3

    Ежеквартальный «Хакер» #3

    Лучшие статьи квартала на бумаге

    2 000 ₽

    Заказать журнал
  • Годовая подписка на Xakep.ru

    Годовая подписка на Xakep.ru

    Полный доступ к статьям и архиву выпусков

    6 000 ₽ в год

    Подписаться
  • «Пентест WEB» для начинающих

    «Пентест WEB» для начинающих

    Совместный курс «Хакера» и лаборатории «Хаксет»

    7 000 ₽

    Купить курс
  • Спецвыпуск «Хакера» #4

    Спецвыпуск «Хакера» #4

    Лучшие статьи за 2021–2022 годы на бумаге

    2 000 ₽

    Заказать журнал
Се­год­ня мы раз­берем на прак­тике, как ревер­сировать неиз­вес­тный бинар­ник C++ и вос­ста­нав­ливать скры­тые струк­туры дан­ных по кос­венным приз­накам. Сов­местим ста­тичес­кий ана­лиз в Ghidra с динами­чес­ким ана­лизом в GDB, про­верим гипоте­зы экспе­римен­тами и раз­берем­ся, как устро­ено хра­нение корот­ких и длин­ных строк в std::string. Заод­но уви­дим, почему в ревер­се важ­но не доверять пер­вой догад­ке и под­тверждать выводы экспе­римен­том.

В этой статье не ста­вит­ся цель охва­тить все воз­можные струк­туры дан­ных, которые могут встре­тить­ся в бинар­никах C++. Моя задача — показать спо­соб рас­сужде­ния, а что­бы не теоре­тизи­ровать, я взял кон­крет­ный при­мер прос­той кряк­ми от авто­ра Mazzotti — Getting started keygen.

Те­бе дос­таточ­но ска­чать иссле­дуемый бинар­ник и уста­новить инс­тру­мен­ты Ghidra и GDB. Я покажу вари­анты их при­мене­ния и про­демонс­три­рую, как сла­бые сто­роны ста­тичес­кого ана­лиза (Ghidra) гар­монич­но исправ­ляют­ся метода­ми динами­чес­кого ана­лиза (GDB) и наобо­рот.

В ревер­се нет единс­твен­ного пра­виль­ного пути, поэто­му я покажу свой: как ухва­тить­ся за нит­ку в виде непонят­ных имен и рас­путать весь клу­бок в струк­туру std::string. На этом пути я буду напоми­нать, что реверс зас­тавля­ет раз­бирать­ся не толь­ко с бай­тами и регис­тра­ми, но и с собс­твен­ным мыш­лени­ем. Мы час­то ста­новим­ся жер­тва­ми ког­нитив­ных иска­жений, хва­таем­ся за пер­вую попав­шуюся под­сказ­ку, ищем толь­ко те дан­ные, которые под­твержда­ют нашу гипоте­зу (пред­взя­тость под­твержде­ния), и стре­мим­ся к иде­аль­ной, но ненуж­ной точ­ности (склон­ность к пер­фекци­ониз­му). По ходу раз­бора кряк­ми я буду отме­чать эти момен­ты как часть про­цес­са ана­лиза.

Мы будем выпол­нять ста­тичес­кий ана­лиз и выд­вигать гипоте­зы, затем про­верять их в динами­ке, кор­ректи­ровать и сно­ва воз­вра­щать­ся к ста­тике. В ито­ге мы напишем скрипт, который авто­мати­зиру­ет рутину и заод­но поз­накомит тебя с методом активно­го зон­дирова­ния.

Пос­ле под­твержде­ния гипотез мы соз­дадим кас­томную струк­туру в Ghidra и при­меним ее к иссле­дуемо­му образцу.

 

Все начинается с переменной

 

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

Пер­вое, что мы сде­лаем, — это пос­мотрим на файл кряк­ми и запус­тим его в изо­лиро­ван­ной сре­де.

Ути­лита file дает общую информа­цию:

$ file getting_started_keygen
getting_started_keygen: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=226fff4aea936ab7426bf11dcd1e334c1e053104, for GNU/Linux 3.2.0, stripped

Файл динами­чес­ки слин­кован (внеш­ние фун­кции под­тягива­ются из раз­деля­емых биб­лиотек) и очи­щен от отла­доч­ных сим­волов: имен фун­кций и перемен­ных нет. Это зна­чит, что Ghidra будет показы­вать нам без­ликие FUN_xxx и local_xx, а мы будем опре­делять их смысл вруч­ную.

Ко­ман­да strings вытас­кива­ет из бинар­ника челове­кочи­таемые стро­ки, к которым мы еще вер­немся:

$ strings getting_started_keygen
...
@0H9
Enter a string of characters (no spaces):
Bro, what are you trying to do?
Enter correct number (no spaces):
OMG! You did it! :3
Send help pls.
9*3$"
...

За­пус­тим бинар­ник в изо­лиро­ван­ной сре­де и пос­мотрим, что он от нас хочет:

$ ./getting_started_keygen
Enter a string of characters (no spaces):
test
Bro, what are you trying to do?

Прог­рамма зап­росила стро­ку, и мы вве­ли сло­во test, но оно не прош­ло про­вер­ку. Поп­робу­ем ввес­ти еще что‑нибудь:

$ ./getting_started_keygen
Enter a string of characters (no spaces):
example
Enter correct number (no spaces):
5
Send help pls.

На этот раз нам повез­ло и прог­рамма зап­росила какое‑то чис­ло. Прав­да, его мы тоже не уга­дали.

 

Находим main в Ghidra

Что­бы не гадать, заг­рузим бинар­ник в Ghidra для ста­тичес­кого ана­лиза. Хоть в прог­рамме и уда­лены отла­доч­ные сим­волы, точ­ка вхо­да (entry) никуда не делась. Ghidra показы­вает ее в окне Symbol TreeFunctionsentry.

Окно Symbol Tree в Ghidra
Ок­но Symbol Tree в Ghidra

Это стан­дар­тная точ­ка вхо­да, которую ком­понов­щик встав­ляет перед фун­кци­ей main. Откры­ваем ее и видим вызов __libc_start_main — стан­дар­тной фун­кции glibc, которая вызыва­ет фун­кцию FUN_001011f0:

void processEntry entry(undefined8 param_1,undefined8 param_2)
{
undefined1 auStack_8 [8];
__libc_start_main(FUN_001011f0,param_2,&stack0x00000008,0,0,param_1,auStack_8);
do {
/* WARNING: Do nothing block with infinite loop */
} while( true );
}

Пер­вый аргу­мент __libc_start_main — это фун­кция main. Сра­зу сме­ним имя FUN_001011f0 на main для удобс­тва. Для это­го нажима­ем пра­вой кноп­кой мыши на имя и выбира­ем пункт Rename Function.

Как переименовать функцию в декомпиляторе Ghidra
Как пере­име­новать фун­кцию в деком­пилято­ре Ghidra

Мы успешно наш­ли фун­кцию main, в которой видим уже зна­комые стро­ки «Enter a string of characters (no spaces):» и «Bro, what are you trying to do?».

undefined8 main(void)
{
int iVar1;
ostream *poVar2;
long in_FS_OFFSET;
int local_7c;
undefined1 *local_78;
long local_70;
undefined1 local_68 [16];
string local_58 [40];
long local_30;
local_30 = *(long *)(in_FS_OFFSET + 0x28);
local_78 = local_68;
local_68[0] = 0;
local_70 = 0;
/* try { // try from 0010123e to 00101327 has its CatchHandler @ 0010132f */
poVar2 = std::operator<<((ostream *)std::cout,"Enter a string of characters (no spaces): ");
FUN_00101430(poVar2);
std::operator>>((istream *)std::cin,(string *)&local_78);
if (5 < local_70 - 5U) {
std::operator<<((ostream *)std::cout,"Bro, what are you trying to do?");
FUN_00101430();
/* WARNING: Subroutine does not return */
exit(0);
}
// Остальной код нас пока не интересует...
}

Мы на пра­виль­ном пути. Оста­лось за что‑нибудь зацепить­ся и про­дол­жить ана­лиз.

 

Что говорят импорты

Преж­де чем изу­чать деком­пилиро­ван­ный код, пос­мотрим на таб­лицу импортов, которая содер­жит не мень­ше под­ска­зок:

  • std::operator>> — опе­ратор вво­да, перег­ружен­ный для раз­ных типов;
  • std::operator<< — опе­ратор вывода;
  • std::string::string — конс­трук­тор std::string;
  • std::string::_M_dispose — метод осво­бож­дения памяти стро­ки.
Внешний вид окна Symbol Tree в Ghidra
Внеш­ний вид окна Symbol Tree в Ghidra

Прог­рамма активно работа­ет со стро­ками. К тому же Ghidra авто­мати­чес­ки соз­дала струк­туру std::string в окне Data Type Manager (вклад­ка Data Typesgetting_started_keygenDemanglerstd).

Ghidra добавила структуру std::string в Data Type Manager
Ghidra добави­ла струк­туру std::string в Data Type Manager

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии