Скомпилированное приложение является «чёрным ящиком». Чтобы туда заглянуть, восстановить алгоритм работы применяется реверс‑инжиниринг. Это непростой навык с высоким порогом входа. В статье мы попробуем взять дизассемблер, несложную задачку и пойдём в бой. Материал будет полезен тем, кому хочется с чего-то начать и погрузиться в тему реверса.

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

Задача

В качестве примера для разбора мы взяли задачу с платформы Codeby Games из раздела Реверс-инжиниринг – файлы. Называется: "Шифрование или что-то в этом роде, не знаю". Чтобы получить доступ к платформе – нужно зарегистрироваться. К самой платформе мы не имеем никакого отношения.

Решением задачи является строка (флаг), которую будем добывать.
Скачиваем бинарный файл “task3.exe” из раздела “Файлы” и приступаем.

В статье будут применяться бесплатные инструменты:

Первичный запуск

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

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

Рекомендуем анализаторы
  • VirusTotal (Есть бесплатная версия)

  • Hybrid Analysis (Есть бесплатная версия)

  • AnyRun (Есть бесплатная версия)

  • PEStudio (Есть бесплатная версия)

  • Capa (Бесплатно)

Современное вредоносное ПО часто взаимодействует с сетью как для доставки очередной "стадии вредоносности", так и для распространения в локальной сети. Рекомендуем либо целиком отключать виртуальную машину от сети, либо, если для исследуемого приложения требуется подключение в интернет, использовать VPN изнутри виртуальной машины. Это не даст образцу распространиться в сети вашей хостовой машины.

Теперь, когда обо всем предупредили, можно приступать к работе.

Запускаем приложение!

Перед нами классическое консольное приложение, которое запрашивает строку:

Первичный запуск
Первичный запуск

Пробуем ввести флаг и убеждаемся, что удача не на нашей стороне:

Попытка ввода флага
Попытка ввода флага

Но, теперь мы знаем, что проверка флага каким-то образом связана со строками INPUT YOUR FLAG: и INCORRECT!!!.

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

Наличие отладочной информации всегда сильно упрощает дальнейший анализ.

Немного об отладочной информации

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

Существует множество форматов отладочных символов. Чаще всего вам придется сталкиваться с этими:

  • PDB – Program Data Base, используется в продуктах, собранных с использованием компиляторов, разработанных компанией Microsoft.

  • DWARF (Debugging With Arbitrary Record Format), STABS (Symbol Table and String Table), COFF (Common Object File Format), ELF (Executable and Linkable Format) используются в основном в продуктах, собранных с использованием open-source компиляторов.

  • Mach-O – Mach Objecton macOS and iOS, используется только в продуктах, собранных различными компиляторами, для целевых ОС MacOs или iOS.

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

Не стоит пугаться такому списку, потому что суть одна и та же. Разберем чуть подробнее, что хранят в себе DWARF и PDB – самые часто используемые форматы.

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

Основная информация, хранящаяся в файле DWARF:

  • имена файлов исходного кода и номера строк

  • информацию о глобальных и локальных переменных и их типах

  • информацию о функциях и подфункциях

  • граф вызовов программы

Информация PDB обычно также хранится в отдельном файле, который затем связывается с исполняемым файлом.

Основная информация, хранящаяся в файле PDB:

  • информацию об именах и типах переменных

  • информацию о функциях

  • информацию о местоположении строк кода

  • информацию о стеке вызовов

О том, как управлять своей жизнью, и самому включать отладочную информацию на примере отладчика gdb и исполняемого ELF-файла, советуем прочитать эти две статьи:

В Visual Studio отладочная информация включается при наличии флага линковщика /DEBUG. Отключить можно в настройках проекта, поставив полю Linker -> Debugging -> Generate debug info значение NO.

Также в настройках линковщика можно отключить создание pdb файла, очистив поле Linker -> Debugging -> Generate Program Database File.

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

Копаем глубже:

Больше о том, как исследователи используют отладочную информацию можно почитать в статье Utilizing Debugging Information of Applications in Memory Forensics.

Крис Касперски в статье Защита игр от взлома рекомендовал никогда не оставлять отладочную информацию в скомпилированной программе.

Начинаем анализировать

Теперь, когда есть поверхностное знание, что такое отладочная информация начнем анализ. Для этого откроем IDA, с использованием отладочной информации. Перед нами функция main.

Функция main
Функция main

С отладочной информацией дизассемблер сразу понял, что перед нами не набор бессмысленных переменных. По модификаторам <Тип данных> ptrпонимаем, что flag, check и i это указатели на данные определенных типов.

Спускаемся ниже по коду к адресу 401598. По синтаксису команды mov [rbp+check+4], 7ABh видим, что в память по адресам check, check+4, check+8 и т.д. помещаются какие-то числа.

Копаем глубже

Больше о квадратных скобках в операндах команд и вообще о типах адресации можно почитать в статье Операнды в языке ассемблера.

Так как данные помещаются в check+N по регулярному смещению 4, делаем вывод, что check является указателем на массив. Этих команд 25 штук, поэтому можем предположить, что массив хранит в себе 25 элементов длиной 4 байта. Если это верно, то далее, когда массив будет использоваться в циклах, скорее всего операции будут проводиться с двадцатью пятью элементами.

Отвлечемся немного и ответим на пару вопросов.

А почему каждая строка начинается с “.text”?

Исполняемый файл имеет множество участков, называемых секциями. Они имеют различное назначение. Код в исполняемых файлах хранится в секции .text. У этой секции есть специальные права на исполнение. Чтобы в этом убедиться, откроем исполняемый файл в редакторе CFF Explorer.

Загрузив файл в CFF Explorer, видим вкладки в левой части рабочего пространства:

Сначала идут заголовки, потом таблицы: импорта, экспорта и TLS. Ниже различные модули для работы с исполняемыми файлами, включенные в CFF Explorer.

Информация о секциях хранится в заголовке Section Headers. Откроем его:

Копаем глубже

О структуре исполняемых файлов Windows подробне прочитать в статье Часть 1. Обзор PE формата и других статьях цикла.
Статьи покрывают: из чего состоит исполняемый файл Windows, как искать секции, что такое импортируемые и экспортируемые функции и так далее многое другое.
А выжимка есть на хабре PE (Portable Executable): На странных берегах. Для начала ее вполне хватит.

Нас интересует поле Characteristics. Здесь содержится информация о данных, хранящихся в секции. У секции .text Characteristics имеет значение 60500060. Старшая 6 говорит о том, что у секции есть характеристики:

  • IMAGE_SCN_MEM_READ и IMAGE_SCN_MEM_EXECUTE – секция может быть исполнена как код

  • IMAGE_SCN_MEM_READ – секция может быть прочитана

Копаем глубже

Все возможные характеристики можно посмотреть в документации Microsoft.

А почему после .text следует “040…”?

У исполняемого файла есть параметр ImageBase – смещение относительно адреса в памяти, куда была загружена программа. Обычно это 0x40000. Этот параметр хранится в заголовке Optional Header:

Так почему же main() не начинается с 400000? Потому что это адрес, где лежит весь исполняемый файл, в том числе заголовки. Заголовки находятся как раз над сегментом кода. Поэтому нас интересуют еще два поля Optional Header:

  • BaseOfCode – смещение относительно начала загруженного исполняемого файла (ImageBase). Здесь начинается сегмент кода text. В этой части размещается код начальной работы процесса: проверка сигнатуры исполняемого файла, получение аргументов main, StartupInfo для процесса и так далее.

  • AddressOfEntryPoint – смещение относительно ImageBase, откуда начинает исполняться программа. Здесь начинается функция mainCRTStartup, которая выполняется до человеческого main и является точкой входа библиотеки времени выполнения Си (C Runtime). Помимо прочего, здесь инициализируются статические элементы кода, а так же переменные argc, argv и envp. А еще, в этой функции помимо всего остального, инициализируются статические элементы (с пометкой static), если они используются в программе.

В нашем случае AddressOfEntryPoint равен 1510. Посмотрим на него в IDA. В IDA этот адрес будет равен ImageBase+AddressOfEntryPoint, т.е. 401510:

Вызов __tmainCRTStartup
Вызов __tmainCRTStartup

Здесь как раз видим вызов __tmainCRTStartup, находящийся выше AddressOfEntryPoint, в котором инициализируется StartupInfo.

Дважды нажав на эту функцию, перейдем внутрь нее и немного пролистаем до момента, пока не увидим вызов функции main (call main):

Вызов main
Вызов main

Здесь же видим, что в функцию main передаются привычные нам аргументы: argc, argv и envp. Это происходит всегда, даже когда разработчик приложения напрямую никак не взаимодействует с этими переменными в исходном коде.

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

Если отладочной информации нет

Давайте посмотрим, как будет выглядеть код без отладочной информаци. Откроем экземпляр IDA без использования отладочной информации. Увидим функцию main, только в менее понятном виде.

Дизассемблер не заметил, что перед ним массив. Поэтому нам нужно методом пристального взгляда определить границы массива: переменную, начиная с которой переменные размещаются регулярно с каким-то фиксированным смещением. В нашем случае это var_70:

Ищем массив
Ищем массив

Затем укажем дизассемблеру наличие массива. Для этого дважды кликнем по первой переменной, с которой начинается массив. Перед нами откроется окно редактирования переменных стека:

Далее выделим все необходимые переменные и с помощью горячей клавиши '*' создадим массив:

Затем переименуем созданный массив с помощью горячей клавиши 'N':

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

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

Наконец-то реверс

Вернемся к коду с отладочной информацией и продолжим.

Перейдем в раздел Strings (shift+F12) и видим уже знакомую строку, которая приглашает ввести флаг:

Строки
Строки

Дважды кликаем по ней в открывшемся окне и попадаем в секцию, где хранятся инициализированные данные приложения, предназначенные только для чтения – .rdata:

.rdata
.rdata

Чтобы посмотреть, где используется текущий адрес, жмем Ctrl+X и получаем список перекрестных ссылок. Механизм называется XRefs или перекрестные ссылки.

Переходим по единственной перекрестной ссылке:

Перекрестные ссылки
Перекрестные ссылки

Перейдя по ссылке, видим, что строка используется чуть ниже заполнения массива check (адрес 401640) в функции puts (вывод в стандартный поток вывода, в данном случае – консоль):

Ниже (адрес 40165D) замечаем вызов scanf (форматированный ввод из стандартного потока ввода) с параметрами %s и указателем flag. Значит, flag – строка, вводимая пользователем.

Спускаемся еще ниже и видим зануление значения по адресу i (адрес 401662) и на следующей строке безусловный переход на метку loc_4016b2. Метка – название для адреса в секции кода. Переходим по этой метке, дважды нажав на loc_4016b2.

Здесь (адрес 4016B2) мы видим сравнение i с числом 24 (18h), условный переход на метку loc_40166B, который сработает, если i будет меньше или равно 24. Если i будет больше 24, переход не произойдет и будет вызвана функция puts(“GOOD JOB, HACKER”):

Перейдя по метке loc_40166B, видим, что в этом участке кода производятся манипуляции с массивами flag (адрес 401670) и check (адрес 401691):

Первым делом значение i помещается в регистр eax и расширяется до rax командой cdqe (адреса 40166B и 40166E). Физически eax – младшая половина регистра rax:

Схема регистров
Схема регистров
Копаем глубже

Подробнее о регистрах можно узнать в статье Регистры процессора.

Строкой ниже (адрес 401670) в eax помещается значение, находящееся по адресу flag+raxc помощью команды movzx (rax в два раза больше eax, поэтому используется movzx, где zx означается, что данные, находящиеся в eaxдополняются нулями). Сопоставив эту строку с двумя предыдущими, понимаем, что в rax помещается i-ый элемент массива flag.

По адресу 401678 от регистра rax остается только младший байт, и расширяется до знакового четырех байтового числа командой movsx eax, al. Позже будет понятно зачем.

Далее для содержимого eax вызывается функция enc (строки 401682-401687). Параметр функции передается в регистре ecx. Кликнув дважды enc попадаем в ее тело:

Функция enc
Функция enc

Благодаря отладочным символам, видим, что функция возвращает тип int –четырёхбайтовое знаковое число и имеет соглашение о вызовах cdecl, а значит ее результат, возвращаемый в return будет храниться в регистре eax. При этом, параметр letter, передаваемый в функцию – четырёхбайтовое знаковое целое. Именно поэтому ранее мы видели расширение до четырёхбайтового целого.

Копаем глубже

На эту тему на хабре есть статья Соглашения о вызовах.

Расширение до четырех байт нужно для проверки модифицированного flag[i], имеющего однобайтовый тип с некоторым четырёхбайтовым элементом check.

В функции видим, что ecx сохраняется в переменную letter (адрес 401574), а затем, строкой ниже, значение переменной сохраняется в eax.

Следующей строкой со значением регистра eax производят операцию xor eax, 0BADh – побитового исключающего ИЛИ с числом 0x0BAD.

На этом тело функции заканчивается, значит, результатом ее исполнения является:

xor(flag[i], 0x0BAD)

Вернемся к коду, вызывающему эту функцию:

После вызова функции enc видим (адрес 401682), что результат ее выполнения, хранящийся в eax, увеличивается на 0x24 и для полученного значения снова вызывается функция enc.

В результате второго выполнения enc со значением flag[i]произведено действие:

xor(xor(flag[i], 0x0BAD+0x24),0x0BAD)

Разберемся далее построчно:

  1. 40168C: результат второго выполнения enc сохраняется в edx

  2. 40168E и 401691: значение i помещается в rax.

  3. 401693: в eax сохраняется значение check[rax*4], что означает сохранение в eax check[i*4]

  4. 401697-401699: полученное значение для i-го элемента флага сравнивается с check[i*4], и если значения равны, происходит переход на метку loc_4016AE

Переходим на метку и видим (адрес 4016AE) увеличение значения i на единицу:

После увеличения значения i снова попадаем на сравнение с 24. Так мы понимаем, что попали в цикл.

Подтвердить это предположение можно перейдя в графическое представление кода, нажав на пробел. Метка, на которой мы сейчас находимся на скриншоте подсвечена желтым:

Цикл в IDA
Цикл в IDA

В IDA зеленые стрелки означают верный результат при сравнении, красные – неверный, синие – безусловный переход (может быть как признаком команды jmp, так и вовсе отсутствия команды j*).

Обратив внимание на условный переход jle на метку loc_40166B, понимаем – чтобы на нее не переходить и добиться вывода GOOD JOB, HACKER, необходимо пройти целиком весь цикл.

Алгоритм проверки флага

В результате обратной разработки функции проверки введенного флага, имеем уравнение, в котором неизвестным являетсяflag[i]:

(flag[i] ^ 0x0BAD + 36) ^ 0x0BAD = check[i]

Так как xor является обратимой функцией, получим flag[i] из check[i]:

int flag[i] = (check[i] ^ 0x0BAD – 36) ^ 0x0BAD

Далее остается только взять младший байт от flag[i]. Возьмем значения элементов массива из дизассемблированного кода и напишем простую программу на C:

int main()
{
	
	int check[25];
	char flag[25];
	int i;
	check[0] = 1983;
	check[1] = 1963;
	check[2] = 1952;
	check[3] = 1953;
	check[4] = 1982;
	check[5] = 1973;
	check[6] = 87;
	check[7] = 70;
	check[8] = 1969;
	check[9] = 1967;
	check[10] = 80;
	check[11] = 1979;
	check[12] = 93;
	check[13] = 1979;
	check[14] = 1967;
	check[15] = 109;
	check[16] = 73;
	check[17] = 1964;
	check[18] = 72;
	check[19] = 111;
	check[20] = 1979;
	check[21] = 84;
	check[22] = 108;
	check[23] = 1966;
	check[24] = 89;
	for (int i = 0; i <= 24; i++)
	{
		flag[i] = (char)(((check[i] ^ 0x0BAD) - 36) ^ 0x0BAD);
	}
	printf(flag);
}

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

В заключение скажем, что можно было так не страдать, сразу нажать волшебную кнопку F5, декомпилировать код и получить что-то вроде этого:

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

Заключение

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

Комментарии (18)


  1. Redvirg
    00.00.0000 00:00
    +7

    Keygen - это все-таки задачка не для самых маленьких. Отладка как один из способов реверса была бы более показательна. Показать, что в регистры попадает. Думаю, первое это должен быть патч (забить nop' пами, поменять jmp на свой адрес, поменять jne на je и др.)


    1. FunnyWhale
      00.00.0000 00:00

      Согласен. Хотелось взять реальную задачку с чужим кодом, при этом не нарушая никакие законы) Для этого как раз хорошо подходят таски с CTF, где сейчас с патчингом встречаешься не очень часто. Можно, конечно, сделать программу самому и разбирать ее, но на мой взгляд - скучновато. Спасибо за комментарий, выпустим еще что-то про патчинг обязательно.


      1. anetto
        00.00.0000 00:00

        Вышло очень хорошо


      1. Orbit67
        00.00.0000 00:00

        Вот если бы вы вагонами воровали, тогда ничего страшного. А так конечно, патчить файлы это страшное преступление.


      1. danzalux
        00.00.0000 00:00
        +1

        Поддерживаю @Redvirg ,

        И ещё хотелось бы увидеть примеры работы с OllyDbg.

        @FunnyWhale , огромное спасибо за статью!


  1. Hisoka
    00.00.0000 00:00
    +2

    Ох, по жести начинать так. Тем более ida freeware... с гидры щас начинать(и продолжать) надо, т.к. там есть почти всё необходимое и при этом бесплатно. А переучиться на иду, если для работы(ибо дорогая, да и ТАКИМ варезом неудобно пользоваться, т.к. палки постоянно в колёса пихают) нужна будет - запросто.

    По моему мнению, у гидры сильно больший потенциал чем у иды. Особенно в скриптинге, где есть доступ почти ко всему в гидре. Редактор структур удобнее, отмена случайных действий почти всегда есть, возможность создания структуры из выделенного куска памяти( когда объект в глобальной памяти объявлен) и одна из самых кайфовых вещей - проект, в котором можно создать архив типов и структур и шарить его между несколькими exe/dll. Ну и свой сервер с версиями, благодаря чему я больше не путаюсь в десятках версий файлов, как было в ida, где делаешь копию idb почти каждый раз перед работой, т.к. случайно не тот размер структуры "фигакнешь" и всё, конец тому что разметил и типы задал.


    1. FunnyWhale
      00.00.0000 00:00
      +1

      Конечно, Ghidra местами удобнее IDA, однако прямо сейчас динозавров со знанием IDA в разы больше. Думаю, в любом универе, где в каком-либо виде преподают реверс, дают его именно с использованием IDA, да и статей все еще больше именно с этим дизассемблером (особенно русскоязычных). Поэтому, личное мнение - сейчас начинать учиться надо именно с IDA (материала для изучения больше), а там уже если приглянется, можно (и иногда нужно) использовать гидру. Тут уже на вкус и цвет. Со временем может и перейдем все на что-то другое :)


      1. ShadF0x
        00.00.0000 00:00
        +1

        Конечно, Java местами удобнее C, однако прямо сейчас динозавров со знанием C в разы больше. Думаю, в любом универе, где в каком-либо виде преподают программирование, дают его именно с использованием C, да и статей все еще больше именно с этим языком (особенно русскоязычных). Поэтому, личное мнение - сейчас начинать учиться надо именно с C (материала для изучения больше), а там уже если приглянется, можно (и иногда нужно) использовать Java. Тут уже на вкус и цвет. Со временем может и перейдем все на что-то другое :)


        1. anetto
          00.00.0000 00:00
          +4

          Неудачный пример, на мой вкус

          И по Java, и по Си мануалов полно, легко найти сообщество или интересный канал, типа канала автора DevFM по питону

          По какому-нибудь Go мануалов меньше, коммьюнити меньше, и в качестве первого языка его выбирать – ну, такое, может оказаться не лучшим решением. Потому что уткнешься в проблему и засядешь, так как помочь некому

          При это начать с Си вполне себе годно. С точки зрения базовых концепций там есть всё нужное. Берешь классику типа Язык Си Кернигана и Ритчи. Там просто и понятно тебе объяснят переменные, типы данных, циклы, – почти всё, что нужно. Потом с Си на любой язык можно перейти и комфортно себя чувствовать


        1. FunnyWhale
          00.00.0000 00:00
          +10

          Кроме шуток, для изучения, C/C++ действительно хороши как первые языки, хоть и выглядят сложными. Их изучение даст многое: от понимания организации памяти, внутреннего устройства структур вроде деревьев, связных списков, хэш-мапов и т.д. до глубокого понимания классов. Если познал C/C++, со всем остальным дальше будет максимально просто, и скорее всего не будет каких-то фундаментальных непониманий. Если мне предложат на выбор два работника, один из которых знает только Java, а другой - C++, я выберу второго)


    1. VelocidadAbsurda
      00.00.0000 00:00

      В IDA откат действия как бы не в 7.6 ещё появился (текущая - 8.2), причём, откатывает действия хоть плагинов, хоть скриптов.


      1. Hisoka
        00.00.0000 00:00

        Ну вот, а я не "дотерпел", в районе 7 уже пошёл в сторону IDA, т.к. силы кончились бодаться с многими "особенностями". При этом можно сказать что это скорее заслуга гидры, т.к. в иде пришлось всё это делать, чтоб быть в паритете с гидрой. И сильно подтягивать, как я вижу сейчас на их сайте, даже free версию, а иначе на иду забили бы совсем все.

        Будем надеяться что ида не загнётся и будет стремиться добавить "новые фишки", которые потом также будут и в гидре реализовывать.

        Кстати, что там с классами и vtable в гидре? Не сделали ещё все обвязки или до сих пор "по старинке" структурами и юнионами vtable фигачить приходится? Ну и как щас, можно пустую структуру, наконец, нужного размера и с удобным редактированием как в гидре, или всё и осталось так - либо в окне, где структуры все на одном экране и в "развернутом виде", но хотя бы можно точно по смещениям попасть или в другом окне, где С-подобный вид? Просто в гидре это очень сильно упрощено и удобно (задал размер и потом спокойно по нужным смещениям назначай типы данных + не даст случайно сломать уже готовые поля)


        1. VelocidadAbsurda
          00.00.0000 00:00

          Надо сказать, появление конкурентов явно пошло Иде на пользу, зашевелились они активнее. Классовые типы и vtable вроде добавили несколько версий назад в рамках прикручивания Clang-парсера (С++-бинарники - не совсем моя область, не пробовал, что вышло), по скорости серьёзные оптимизации проводились (не страшно открыть какой-нибудь ELF на 64МБ и сразу броситься по нему бегать. У Гидры с этим, слышал, погрустнее?). Но да, всякого противного legacy пока по-прежнему хватает - костыльное деление на "классические" и С-подобные типы осталось, Python API и дальше абсолютно не в стиле Python, паранойя автора не прошла итд. Будем надеятся, новые хозяева (они в конце того года продались!) повернут эту избушку к клиенту передом.


  1. IsKaropki
    00.00.0000 00:00

    Ого, как шикарно проработан материал.

    Спасибо.


  1. mobilkip
    00.00.0000 00:00
    +2

    Спасибо автору, было интересно!

    Есть вопросы от человека которому это интересно, но экспертиза которого заканчивается краской кнопок в вебе

    В каких профессиях это используется

    Где можно подобное изучить

    Сколько времени займет это изучить хотя бы на начальном уровне, если есть возможность тратить каждый день 2-3 часа.

    Спасибо заранее.

    P.S. писать комментарии с телефона на Хабре есть боль.


    1. FunnyWhale
      00.00.0000 00:00
      +2

      В первую очередь - вирусная аналитика и кибер криминалистика.

      Иногда восстановление алгоритма работы "мертвых" программ, поддержка которых уже прекратилась.

      Также существует отдельный пласт специалистов, которые создают копии софта и железа.

      Кто-то дорабатывает готовые программы своими патчами для расширения функционала или для добычи полной версии.

      Вариантов применения много.

      По обучению:

      Для начала почитать про ассемблеры: регистры, команды, адресацию, соглашения о вызовах. Остальное довольно быстро узнается на практике. Прогать на ассемблерах не обязательно, но будет хорошим бонусом к скиллам.

      Далее советую писать собственные программы на C/C++ и смотреть на них в дизасемблере:

      Для начала нужно потыкать в различную память. Посмотреть, как переменные размещаются на стеке и в куче. Тут же можно попробовать попатчить значения переменных в памяти во время отладки. Посмотреть, как выглядят строки и массивы.

      Написать несколько программ с условным выполнением: if, switch. Попробовать запатчить код с условных переходов на безусловные (Тут почитать про опкоды команд), попробовать запатчить nop-ами, чтобы вообще убрать кусок кода. Попробовать модифицировать регистры флагов для изменения хода выполнения во время отладки

      Затем попробуй написать программу со структурами и классами: поймешь, как оно все выглядит в памяти, и как работает thiscall.

      Дальше нужно написать несколько программ с использованием WinAPI: работа с реестром, с файловой системой, библиотеками и т.д.

      После этого научиться отлаживать dll.

      В принципе, основные знания на этом наверное и заканчиваются, дальше все придет с опытом. На этом моменте можно уже уверенно смотреть какие-то crackme, чтобы набраться опыта. Сайтов с ними достаточно.

      После этого будет полезно посмотреть на реверс .NET приложений. Тут сразу рекомендую взяться за dnSpy, там все супер просто, разобраться будет несложно.

      Чтобы повеселиться и приблизиться к миру реверса, чуть ли не сразу же можно взять классический пример с патчингом WinRar (вроде как не очень законно), материалов достаточно. Можно поиграться с CheatEngine и minesweeper, статьи тоже можно найти.

      Удачи в познании :)


      1. Alexey2005
        00.00.0000 00:00
        +2

        В принципе, основные знания на этом наверное и заканчиваются

        Наоборот только начинаются. Далее надо:

        • Разобраться, как работают упаковщики (начать с простейшего - UPX) и как это распаковывать. Как дампят код, как исправляют точку входа и таблицы импорта. Упакованные бинарники даже сейчас встречаются прискорбно часто.

        • Ознакомиться с основным набором антиотладочных приёмов и способов задетектить отладчик, и как этому противодействовать.

        • Обфускаторы. Не только .NET-программы, но и написанное на C++ может быть превращено в кашу. Особенно весело, когда эта каша ещё и самомодифицирующаяся. IDA просто покажет вам гору мусора, потому что оно собирается в рабочую прогу только в рантайме, и даже там код состоит главным образом из мусорных JMP. Рассматривать удобно на примере ExeCryptor - он самый простой из всех и защищённое старыми его версиями реверсится проще всего.

        • Что такое HASP-ключ, как это используется для защиты программы и соответственно как декриптовать. Заодно придётся ещё глубже ознакомиться со структурой exe-файла (в частности, как двигать секции, как добавлять новые со своим кодом), а также как происходит работа с внешними устройствами и взаимодействие с драйверами.

        • Что делать, если автор программы написал собственную виртуальную машину (или взял готовую), а сам код программы выполняется в ней и соответственно для IDA - просто набор байтов. Чтобы это пощупать, пишем программу, где ценный алгоритм написан на Lua и выполняется с помощью влинкованной в нашу программу LuaJit. Научиться опознавать, что у нас тут используется VM и определять, что именно за технология задействована.


        1. FunnyWhale
          00.00.0000 00:00

          Крутой комментарий, спасибо) Мне кажется, это уже как раз наживное. Все это довольно часто встречается, что само по себе уже заставит в этом разобраться. Знать это нужно, согласен.