Reverse engineering для самых маленьких. Часть первая.

Аллилуйя, я все-таки дозрел до темы Реверс Инжиниринг, потому что, так очень хочется пропатчить какой-нибудь простой КрякМи. В программировании Reverse engineering означает восстановление алгоритма работы программы из исполняемого кода. То есть не зная исходный код мы изучаем МАШИННЫЙ (исполняемый код ), что бы понять как работает эта программа.

Языки программирования делятся на два класса : компилируемые и интерпретируемые.

Компилируемый язык ( например С++ ) с помощью специальной программы компилируется, превращаясь в исполняемый код на машинном языке

( 010110110101 ).

Интерпретируемый ( например Python ) выполняется специальной программой (интерпретатором). Мы с вами будем работать с исполняемыми файлами под архитектуру х86 ( например *.exe) .

Без Assembler ни куда…

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

Ассемблер язык низкого уровня , это совсем не значит что он плохой или какой-то не такой. Это просто означает, что он работает на самом низкому уровне, на прямую с процессором и оперативной памятью. По сути дальше идет только машинный код , те самые 0101010101 .

Регистры…

Первое с чем нам надо познакомиться это регистры. Процессор на самом деле очень глупенький . Он может просто быстро работать с числами и выполнять простые действия (сложение, вычитание, умножение и деление) и больше ничего. Он не имеет понятия о всяких там print() или http. И для работы с числами у него есть блоки ячеек памяти находящиеся в процессоре, размер этого блока 32 бита (4 байта) . Как мы все слышали 32 битная система или 64 битная (архитектура х64 или amd64 ).

Всего нам доступно восемь регистров, которыми могут использоваться программы для хранения данных ( называются регистрами общего назначения (РОН) ) :

  • eax — (Accumulator register) — аккумулятор.Применяется для хранения промежуточных данных.
  • ebx — (Base register) — базовый регистр.
  • ecx — (Count register) — регистр-счетчик. Применяется в командах, производящих некоторые повторяющиеся действия.
  • edx — (Data register) — регистр данных. Так же, как и регистр eax, он хранит промежуточные данные.
  • esi — (Source Index register) — индекс источника.
  • edi — (Destination Index register) — индекс приемника.
  • esp — (Stack Pointer register) — регистр указателя стека. Это важны регистр, который всегда указывает на текущую вершину стека. (Позже разберемся).
  • ebp/bp (Base Pointer register) — регистр указателя базы кадра стека. Предназначен для организации произвольного доступа к данным внутри стека.

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

Память..

В Ассемблере мы работает с памятью опять же практически на прямую . Когда запускается программа ей выделяется какая-то область память в ОЗУ (да , та планка оперативки в вашем системнике) . И делиться на две части : Куча и Стек . Я не уверен что сам понимаю как это работает, но надеюсь в комментах меня поправят если не понятно.

Так вот, Стек — это область памяти, куда помещаются локальные переменные (данные), он как слоеный пирог. Данные идут друг за другом, строго упорядочены и извлекаются из него в обратном порядке. С начало верхний слой , потом следующий и так до конца стека. Это легко представить с типом данных типа string. Процессор не знает что такое стора, он понимает только числа (char) , по этому мы строку кладем в стек по-символьно в обратном порядке World -> dlroW и извлекая обратно из стека начиная с последнего записанного значения (числа) -> World. Самое главное, что мне кажется, где бы вы чтобы не читали, это понять, что стек растет в НИЗ. Это значит, что адрес каждой последующей переменной уменьшается ! То есть вершина стека в низу на адресе 0х0000000 , а начало на 0хFFFFFFFFF

Куча это куча… Область памяти где по конкретному адресу хранятся контрактные значения, думаю самое понятное, это будет определение константы.

Команды….

MOV — эту команду вы будите видеть часто. Пересылка данных. пару примеров :

  • mov ebx eax -> Положить в регистр ebx значение из регистра eax .
  • mov ebx b800h -> Положить в регистр ebx число 0хb800 .
  • mov x, 0 -> Записать в переменную 0 .
  • mov eax [ebx] -> Записать в регистр eax значение из памяти по адресу записанному в регистре ebx .

Стековые операции — PUSH, POP. Команда PUSH размещает значение в стеке, т.е. помещает значение в ячейку памяти, на которую указывает регистр ESP, после этого значение регистра ESP уменьшается на 4. Команда POP извлекает значение из стека, т.е. извлекает значение из ячейки памяти, на которую указывает регистр ESP, после этого увеличивается значение регистра ESP на 4. Значение, помещенное в стек последним, извлекается первым.

  • push aex -> Положить значение регистра aex в стек.
  • pop aex -> Положить в регистр aex значение из стека.

JMP <метка> Эта команда предназначена для выполнения безусловного перехода.

Прямой переход
jmp L 
... 
...
L:
   mov eax, x

Косвенный переход
jmp ebx 

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

Условный переход

JE <метка> Переход если равноop1 = op2ZF = 1
JNE <метка> Переход если не равноop1 ≠ op2ZF = 0

CMP команда для сравнения чисел, изменяет значение флагов.

  • cmp eax, ebx -> Сравниваем eax с ebx .
CYCL:      
     cmp cx,0 ; сравнить регистр CX с 0
     jne CYCL ; условный переход

Арифметическими инструкциями INC (от англ. «INCREMENT») используется для увеличения операнда на единицу . DEC (от англ. «DECREMENT») используется для уменьшения операнда на единицу.

  • INC EBX  -> Выполняем инкремент 32-битного регистра.
  • INC DL  -> Выполняем инкремент 8-битного регистра.
  • INC [count]  -> Выполняем инкремент переменной count.
  • DEC EBX  -> Выполняем инкремент 32-битного регистра и т.д.

Инструкции ADD и SUB используются для выполнения простого сложения/вычитания двоичных данных размером в byte, word и doubleword, то есть для сложения или вычитания 8-битных, 16-битных или 32-битных операндов, соответственно.

  • ADD/SUB    место_назначения, источник

Вызов подпрограмм \ возврат из них — CALL, RET. Команда call передает управление ближней или дальней процедуре с запоминанием в стеке адреса точки возврата. Команда ret возвращает управление из процедуры вызывающей программе, адрес возврата получает из стека. Пример:

    ..код..
        call 0455659
    ..какой-то еще код..    
0455659 add eax, 500
        dec eax
        ret

Думаю этого базового набора нам хватит для первого CrackMe , по этому отправляемся в часть два

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.