Ломаем Простую «Трещину» С Помощью Ghidra. Часть 2.

В первая часть В статье мы использовали Ghidra для автоматического анализа простой программы-взломщика (которую мы скачали с сайта трещины.

one).

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

что делает функция основной() .

В этой части, как я и обещал, мы займемся анализом функции _construct_key() , который, как мы выяснили, отвечает как раз за чтение переданного в программу бинарного файла и проверку прочитанных данных.



Шаг 5. Обзор функции _construct_key()

Давайте кратко рассмотрим полный список этой функции: Листинг функции _construct_key()
  
  
   

char ** __cdecl _construct_key(FILE *param_1) { int iVar1; size_t sVar2; uint uVar3; uint local_3c; byte local_36; char local_35; int local_34; char *local_30 [4]; char *local_20; undefined4 local_19; undefined local_15; char **local_14; int local_10; local_14 = (char **)__prepare_key(); if (local_14 == (char **)0x0) { local_14 = (char **)0x0; } else { local_19 = 0; local_15 = 0; _text(&local_19,1,4,param_1); iVar1 = _text((char *)&local_19,*(char **)local_14[1],4); if (iVar1 == 0) { _text(local_14[1] + 4,2,1,param_1); _text(local_14[1] + 6,2,1,param_1); if ((*(short *)(local_14[1] + 6) == 4) && (*(short *)(local_14[1] + 4) == 5)) { local_30[0] = *local_14; local_30[1] = *local_14 + 0x10c; local_30[2] = *local_14 + 0x218; local_30[3] = *local_14 + 0x324; local_20 = *local_14 + 0x430; local_10 = 0; while (local_10 < 5) { local_35 = 0; _text(&local_35,1,1,param_1); if (*local_30[local_10] != local_35) { _free_key(local_14); return (char **)0x0; } local_36 = 0; _text(&local_36,1,1,param_1); if (local_36 == 0) { _free_key(local_14); return (char **)0x0; } *(uint *)(local_30[local_10] + 0x104) = (uint)local_36; _text(local_30[local_10] + 1,1,*(size_t *)(local_30[local_10] + 0x104),param_1); sVar2 = _text(local_30[local_10] + 1); if (sVar2 != *(size_t *)(local_30[local_10] + 0x104)) { _free_key(local_14); return (char **)0x0; } local_3c = 0; _text(&local_3c,1,1,param_1); local_3c = local_3c + 7; uVar3 = _text(param_1); if (local_3c < uVar3) { _free_key(local_14); return (char **)0x0; } *(uint *)(local_30[local_10] + 0x108) = local_3c; _text(param_1,local_3c,0); local_10 = local_10 + 1; } local_34 = 0; _text(&local_34,4,1,param_1); if (*(int *)(*local_14 + 0x53c) == local_34) { _text("Markers seem to still exist"); } else { _free_key(local_14); local_14 = (char **)0x0; } } else { _free_key(local_14); local_14 = (char **)0x0; } } else { _free_key(local_14); local_14 = (char **)0x0; } } return local_14; }

С этой функцией мы сделаем то же самое, что и раньше, с основной() — Для начала пройдемся по «завуалированным» вызовам функций.

Как и ожидалось, все эти функции взяты из стандартных библиотек C. Я не буду повторно описывать процедуру переименования функций — при необходимости вернитесь к первой части статьи.

В результате переименования «нашли» следующие стандартные функции:

  • читать()
  • стрнкмп()
  • стрлен()
  • ftell()
  • fseek()
  • ставит()
Соответствующие функции-обертки в нашем коде (те, что декомпилятор нагло спрятал за словом _текст ) мы переименовали их, добавив индекс 2 (чтобы избежать путаницы с исходными функциями C).

Практически все эти функции используются для работы с файловыми потоками.

В этом нет ничего удивительного — достаточно беглого взгляда на код, чтобы понять, что он последовательно считывает данные из файла (дескриптор которого передается функции в качестве единственного параметра) и сравнивает прочитанные данные с неким двумерным байтом.

множество local_14 .

Предположим, что этот массив содержит данные для проверки ключа.

Назовем это, скажем, key_array .

Поскольку Гидра позволяет переименовывать не только функции, но и переменные, воспользуемся этим и переименуем непонятное local_14 в более понятный key_array .

Делается это так же, как и для функций: через контекстное меню ( Переименовать локальное ) или ключ л с клавиатуры.

Итак, сразу после объявления локальных переменных вызывается некая функция _prepare_key() :

key_array = (char **)__prepare_key(); if (key_array == (char **)0x0) { key_array = (char **)0x0; }

К _prepare_key() мы вернемся позже, это уже 3-й уровень вложенности в нашей иерархии вызовов: main() -> _construct_key() -> _prepare_key() .

А пока предположим, что он создает и каким-то образом инициализирует этот «тестовый» двумерный массив.

И только если этот массив не пуст, функция продолжает свою работу, о чем свидетельствует блок еще сразу после вышеуказанного условия.

Далее программа считывает первые 4 байта из файла и сравнивает их с соответствующим участком массива.

key_array .

(Приведенный ниже код приведен после переименований, включая переменную local_19 Я переименовал его в first_4bytes .

)

first_4bytes = 0;

Теги: #C++ #обратное проектирование #АНБ #обратное проектирование #Ghidra #хакинг #реверсинг #crackme #nsa #декомпиляция #дизассемблирование #crackme #crackme #crackme #crackmes.de

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.