В первая часть В статье мы использовали 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()
- ставит()
Практически все эти функции используются для работы с файловыми потоками.
В этом нет ничего удивительного — достаточно беглого взгляда на код, чтобы понять, что он последовательно считывает данные из файла (дескриптор которого передается функции в качестве единственного параметра) и сравнивает прочитанные данные с неким двумерным байтом.
множество 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
-
Микро-Орм Своими Руками (Часть Первая)
19 Oct, 24 -
Гугл Проник В Тело
19 Oct, 24 -
Евросеть - Ложка Мёда В Бочке...
19 Oct, 24 -
Измерительная Телеграмма
19 Oct, 24 -
Google Переходит На Html 5
19 Oct, 24 -
Autocad Architecture: Первый Проект
19 Oct, 24