• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Реверс инжиниринг, используя radare2 (32 и 64 bits бинарники)

baykal

(L2) cache
Пользователь
Регистрация
16.03.2021
Сообщения
370
Реакции
838
Ку, киберрекруты. Начинаю курс реверса с использованием radare2. В этой серии постов, которую я начинаю с этого, будут рассмотрены основы реверс-инжиниринга с помощью "популярного" фреймворка radare2. Я хочу провести вас через разнообразный набор примеров двоичных файлов, написанных на C, которые будут охватывать наиболее распространенные алгоритмы и структуры данных, которые вы можете найти в программах, так что в конце вы сможете определить их и работать с ними. Это не будет продвинутый/профи курс, но он начнется с самых низов и будет охватывать гораздо больше, чем средний бесплатный курс/учебник.

В этой серии статей мы начнем с исходного файла / алгоритма на языке C, скомпилируем его в 32-битной и 64-битной системах, а затем проведем реверс-инжиниринг с помощью radare2, чтобы вы смогли оценить различия между 32- и
64-битными двоичными файлами. После того как мы рассмотрим основы реверсинга, мы сосредоточимся на x64.

Я предполагаю, что у вас есть фундаментальные знания о компьютерных архитектурах и вы знаете некоторые базовые инструкции asm, такие как mov, push и т.п. Я также предполагаю, что вы уже знаете, что такое radare2, и хотите наконец научиться его использовать.

Установка radare2

Большинство людей, использующих radare2, применяют его в системах Linux, поскольку оттуда можно анализировать всевозможные двоичные файлы, а если вы хотите отладить их, то можете подключиться к удаленной сессии отладки или использовать gbd в Linux. Здесь мы будем использовать r2 на Linux большую часть времени и переносить его на Windows, когда это необходимо.

Radare2 поддерживает множество архитектур от x64 до arduinos или tamagochis, и вы можете анализировать двоичные файлы, связанные с этими архитектурами, не выходя из своей системы ubuntu или любой другой, которую вы используете.

Как сказано выше, лучший способ получить radare2, как предлагает сама веб-страница, это клонировать его из репозиториев:

Код:
git clone https://github.com/radare/radare2
cd radare2
sys/install.sh # just run this script to update r2 from git

Вы также можете его установить из apt/rpm/yaourt, в большинстве дистрибутивов используя примерно такая конструкция:

Код:
sudo apt-get install radare2

А если вы хотите использовать его на Windows, вы можете загрузить установку для windows с его сайта.

Hello world

Каждый путь программирования начинается с классической программы hello world, и этот будет не хуже. А hello world на языке C будет выглядеть именно так:

C:
#include <stdio.h>
int main() {
printf("Hello, World!");
return 0;
}

Программу можно скомпилить так:

Код:
gcc -w hello_world.c -o hello_word

И если мы запустим ее, то в основном получим "Hello, World!" на экране и все, очень просто, но надо же с чего-то начинать :)

Как только программа скомпилирована, мы можем открыть ее с помощью r2, используя radare2 program, где program - программа, которую вы хотите проанализировать, r2 также является псевдонимом radare2, поэтому вы можете использовать его вместо него, если хотите. После загрузки двоичного файла нам понадобится radare для разбора программы; запуск aaaa проанализирует двоичный файл и обнаружит структуры данных, вызовы функций и подобные элементы.

Когда вы выполняете команду aaa, radare показывает вам, какие шаги она выполняет. Каждый шаг содержит команду, отвечающую за него, в круглых скобках. Например:

Код:
[0x00000000]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Use -AA or aaaa to perform additional experimental analysis.

Как вы видите, aaa - это команда, которая выполняет другие команды. Она также печатает краткое описание того, что делает каждая команда. Немного более подробную информацию можно найти в разделе aa? Итак, чтобы сложить эту
информацию вместе:
  • aa - псевдоним для af@@ sym.*;af@entry0;afva
  • aac - анализ вызовов функций ( af @@ pi len~call[1] )
  • aar - анализ байтов инструкций на предмет ссылок
  • aan - автоименование функций, которые начинаются с fcn.* или sym.func.*
  • afta - выполнять анализ соответствия типов для всех функций
Как и в случае с aaa, эта информация выводится при выполнении aaaa.

Код:
[0x00000000]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Enable constraint types analysis for variables

Основным изменением aaaa является [x] Enable constraint types analysis for variables. Это в основном включает конфигурационную переменную anal.types.constraint .

Код:
[0x00000000]> e? anal.types.constraint
anal.types.constraint: Enable constraint types analysis for variables

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

Вернемся к нашей программе - hello_world:

Код:
lab@lab-VirtualBox:~$ radare2 c_examples/bin/hello_world
-- Use V! to enter into the visual panels mode (dwm style)
[0x08048310]> aaaa
[Cannot analyze at 0x08048300g with sym. and entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[Cannot analyze at 0x08048300ac)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
[0x08048310]>

После загрузки и разбора программы с ней можно выполнить множество действий, таких как листинг строк, дизассемблирование блоков кода и многое другое. Одна из самых распространенных вещей, которую вы захотите сделать, получив в руки неизвестный двоичный файл - это получить список его функций. Это можно сделать с помощью команды afl.

Код:
[0x08048310]> afl
0x08048310    1 33           entry0
0x080482f0    1 6            sym.imp.__libc_start_main
0x08048350    4 43           sym.deregister_tm_clones
0x08048380    4 53           sym.register_tm_clones
0x080483c0    3 30           entry.fini0
0x080483e0    4 43   -> 40   entry.init0
0x080484a0    1 2            sym.__libc_csu_fini
0x08048340    1 4            sym.__x86.get_pc_thunk.bx
0x080484a4    1 20           sym._fini
0x08048440    4 93           sym.__libc_csu_init
0x0804840b    1 46           main
0x080482e0    1 6            sym.imp.printf
0x080482ac    3 35           sym._init
[0x08048310]>

В примерах, подобных этому, наиболее интересной функцией является main. Функции Entry* также могут быть интересны, но поскольку это программа hello world, в таких функциях вы, вероятно, найдете кучу кода, который вы не поймете в данный момент (некоторые внутренние вещи, добавленные компилятором), поэтому мы можем пока оставить их. Другие функции, которые мы видим в этом примере, связаны с библиотеками/вызовами C, используемыми программой (как вызов printf, который вы, вероятно, идентифицируете там).

Еще одна интересная начальная вещь, которую мы можем здесь сделать, это, например, получить общую информацию о бинарном файле с помощью iI:

Код:
[0x08048310]> iI
arch     x86
baddr    0x8048000
binsz    6115
bintype  elf
bits     32
canary   false
class    ELF32
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
crypto   false
endian   little
havecode true
intrp    /lib/ld-linux.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  Intel 80386
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    NONE
sanitiz  false
static   false
stripped false
subsys   linux
va       true

[0x08048310]>

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

Мы также можем перечислить импорты с помощью ii:

Код:
[0x08048310]> ii
[Imports]
nth                vaddr              bind   type              name
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
577730550794551297 0xb6f9994600000000 FUNC   printf      
2                  0xb6f9994d00000000 NOTYPE __gmon_start__
577730619514028035 0xb6f9994600000000 FUNC   __libc_start_main

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

Еще одной командой, представляющей для нас интерес, может быть команда iz. Она выведет список всех строк, содержащихся в секции данных программы (izz выведет список строк во всем файле). Эта команда особенно полезна при работе с простыми крэками (так мы можем определить закодированные пароли). В целом, знание строк внутри программы полезно, когда нужно получить общее представление о том, "о чем эта программа".

Код:
[0x08048310]> iz
[Strings]
nth           paddr             vaddr       len size     section type          string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
5222680231936 0x80484c000000000 0xd00000000 14  34461128 ascii   Hello, World! bin.strings

[0x08048310]>

Как мы видим, iz обнаруживает строку "Hello, World!", строку, которая появляется каждый раз, когда мы запускаем программу. Radare также сообщает нам о разделе (ascii) и местоположении строки.

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

Код:
[0x08048310]> sf main
[0x0804840b]> pdb
            ; DATA XREF from entry0 @ 0x8048327
┌ 46: int main (int32_t arg_4h, char **argv, char **envp);
│           ; var int32_t var_4h @ ebp-0x4
│           ; arg int32_t arg_4h @ esp+0x24
│           0x0804840b      8d4c2404       lea ecx, [arg_4h]
│           0x0804840f      83e4f0         and esp, 0xfffffff0
│           0x08048412      ff71fc         push dword [ecx - 4]
│           0x08048415      55             push ebp
│           0x08048416      89e5           mov ebp, esp
│           0x08048418      51             push ecx
│           0x08048419      83ec04         sub esp, 4
│           0x0804841c      83ec0c         sub esp, 0xc
│           0x0804841f      68c0840408     push str.Hello__World       ; 0x80484c0 ; "Hello, World!" ; const char *format
│           0x08048424      e8b7feffff     call sym.imp.printf         ; int printf(const char *format)
│           0x08048429      83c410         add esp, 0x10
│           0x0804842c      b800000000     mov eax, 0
│           0x08048431      8b4dfc         mov ecx, dword [var_4h]
│           0x08048434      c9             leave
│           0x08048435      8d61fc         lea esp, [ecx - 4]
└           0x08048438      c3             ret
[0x0804840b]>

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

Часть, на которой нам придется сосредоточиться, начинается с push str.Hello__World и затем вызова printf. В 32-битных системах способ передачи параметров функциям заключается в выталкивании этих параметров в стек и последующем выполнении операции вызова по адресу функции. Поэтому что-то вроде printf(a); в C будет push a; call printf в asm. А что же с остальной частью кода? Ну, первая часть кода связана с созданием нового кадра стека , а кадр стека - это не что иное, как участок кода, который будет содержать локальные переменные функции, переданные аргументы и тому подобные значения.

Двоичные файлы x64 и их особенности

Давайте повторим процесс, но скомпилируем ту же программу и откроем ее в r2 в системе x64.

Код:
[0x00000540]> sf sym.main
[0x0000064a]> pdb
            ;-- main:
/ (fcn) sym.main 28
|   sym.main ();
|              ; DATA XREF from 0x0000055d (entry0)
|           0x0000064a      55             push rbp
|           0x0000064b      4889e5         mov rbp, rsp
|           0x0000064e      488d3d9f0000.  lea rdi, qword str.Hello__World ; 0x6f4 ; "Hello, World!" ; const char * format
|           0x00000655      b800000000     mov eax, 0
|           0x0000065a      e8c1feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0000065f      b800000000     mov eax, 0
|           0x00000664      5d             pop rbp
\           0x00000665      c3             ret
[0x0000064a]>

Основное различие между программами x32 и x64 в небольших примерах, подобных этому, заключается в том, что, как вы могли видеть, параметры функций не передаются через стек. В программах x64, таких как эта, параметры передаются с помощью регистров, таких как rdi, rsi, rdx, rcx, r8 и r9 в таком порядке. Мы продолжим рассматривать архитектуру x64 на наглядных и конкретных примерах, но вы, возможно, захотите прочитать шпаргалку, которая будет во вложении к статье, чтобы получить более глубокое понимание таких тем, как эта последняя.

Больше комментировать нечего.

Если мы посмотрим на код, в данном случае в самом начале кода, мы можем оценить push rbp, mov ebp, rsp . Это связано с выравниванием стека. То, что происходит дальше, гораздо более понятно: с помощью lea мы загружаем эффективный адрес строки с нулевым окончанием "Hello, World!" в регистр RDI, затем мы загружаем шестнадцатеричное значение 0 в eax и переходим к вызову printf() . Мы загружаем в eax значение 0 в основном потому, что функция printf имеет переменные аргументы, такие как строка и множество параметров, связанных с форматом, и, кроме того, printf может работать с векторными регистрами, например, при выполнении printf("%f", 1.0f) программа должна установить eax в 1, чтобы указать на это.

В x86_64 ABI, если функция имеет переменные аргументы, то AL (который является частью EAX) должен содержать количество векторных регистров, используемых для хранения аргументов этой функции.

В конце программы мы видим выполнение mov eax, 0 , затем pop rbp и ret . Первый из них помещает eax в 0, так как это подготавливает программу к выполнению возврата 0, затем второй восстанавливает стек в исходное состояние,выбирая исходное значение кадра стека в rbp. Затем ret просто возвращает функцию, выходя из нее со значением 0.

Используемые команды

Сегодня мы в основном использовали эти команды:
  • aaaa - полностью проанализирует бинарный файл
  • afl - перечислит все функции в двоичной системе
  • ii - список импортов
  • iI - информация о двоичном файле
  • iz - список строк в двоичном файле
  • sf function - обратиться к функции
  • pdb - дизассемблирование блока main

Источник:
Reverse engineering 32 and 64 bits binaries with Radare2 - 1
Автор перевода AFANX
 
Ку, киберрекруты. Продолжаю курс реверса и использованием radare2. Сегодня мы рассмотрим некоторые простые структуры данных, такие как переменные, поймем, как работают основные условные структуры кода внутри, а также научимся отлаживать с помощью radare2.

Переменные

Код


Что касается числовых переменных, то первая представляет собой int, вторая - float, третья - double, а также char 'a'. В конце эти значения складываются и результат выводится на печать. Мы видим, как первая переменная хранится непосредственно в регистре (общего назначения), вторая тоже хранится в памяти, но в другом месте, а третья работает так же, за исключением того, что ей требуется в 2 раза больше места, чем второй (double).

C:
#include <stdio.h>

int main() {
  char ab ='a';
  int a = 3;
  float b = 4.5;
  double c = 5.25;
  float sum;
 
  sum = a+b+c;

  printf("The sum of a, b, and c is %f.", sum);
  return 0;
}

Как всегда, мы можем скомпилировать код с помощью gcc, ничего сложного.

Бинарь

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

Код:
 -- sudo make me a pancake
[0x08048310]> aaa
[Cannot analyze at 0x08048300g with sym. and entry0 (aa)
[x] Analyze all flags starting with sym. and entry0 (aa)
[Cannot analyze at 0x08048300ac)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x08048310]>

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

Код:
[0x08048310]> afl
0x08048310    1 33           entry0
0x080482f0    1 6            sym.imp.__libc_start_main
0x08048350    4 43           sym.deregister_tm_clones
0x08048380    4 53           sym.register_tm_clones
0x080483c0    3 30           entry.fini0
0x080483e0    4 43   -> 40   entry.init0
0x080484f0    1 2            sym.__libc_csu_fini
0x08048340    1 4            sym.__x86.get_pc_thunk.bx
0x080484f4    1 20           sym._fini
0x08048490    4 93           sym.__libc_csu_init
0x0804840b    1 123          main
0x080482e0    1 6            sym.imp.printf
0x080482ac    3 35           sym._init
[0x08048310]>

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

Код:
[0x08048310]> sf main
[0x0804840b]> pdf
            ; DATA XREF from entry0 @ 0x8048327
┌ 123: int main (int32_t arg_4h, char **argv, char **envp);
│           ; var char var_1dh @ ebp-0x1d
│           ; var int32_t var_1ch @ ebp-0x1c
│           ; var int32_t var_4h @ ebp-0x4
│           ; arg int32_t arg_4h @ esp+0x4c
│           0x0804840b      8d4c2404       lea ecx, [arg_4h]
│           0x0804840f      83e4f0         and esp, 0xfffffff0
│           0x08048412      ff71fc         push dword [ecx - 4]
│           0x08048415      55             push ebp
│           0x08048416      89e5           mov ebp, esp
│           0x08048418      51             push ecx
│           0x08048419      83ec34         sub esp, 0x34
│           0x0804841c      c645e361       mov byte [var_1dh], 0x61    ; 'a' ; 97
│           0x08048420      c745e4030000.  mov dword [var_1ch], 3
│           0x08048427      d9054c850408   fld dword [0x804854c]
│           0x0804842d      d95de8         fstp dword [ebp - 0x18]
│           0x08048430      dd0550850408   fld qword [0x8048550]
│           0x08048436      dd5df0         fstp qword [ebp - 0x10]
│           0x08048439      d9ee           fldz
│           0x0804843b      d95dec         fstp dword [ebp - 0x14]
│           0x0804843e      db45e4         fild dword [var_1ch]
│           0x08048441      d845e8         fadd dword [ebp - 0x18]
│           0x08048444      dc45f0         fadd qword [ebp - 0x10]
│           0x08048447      d95dec         fstp dword [ebp - 0x14]
│           0x0804844a      d945ec         fld dword [ebp - 0x14]
│           0x0804844d      83ec04         sub esp, 4
│           0x08048450      8d6424f8       lea esp, [esp - 8]
│           0x08048454      dd1c24         fstp qword [esp]
│           0x08048457      6810850408     push str.The_sum_of_a__b__and_c_is__f. ; 0x8048510 ; "The sum of a, b, and c is %f." ; const char *format
│           0x0804845c      e87ffeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x08048461      83c410         add esp, 0x10
│           0x08048464      0fbe45e3       movsx eax, byte [var_1dh]
│           0x08048468      83ec08         sub esp, 8
│           0x0804846b      50             push eax
│           0x0804846c      682e850408     push str.The_value_of_char_ab_is__c. ; 0x804852e ; "The value of char ab is %c." ; const char *format
│           0x08048471      e86afeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x08048476      83c410         add esp, 0x10
│           0x08048479      b800000000     mov eax, 0
│           0x0804847e      8b4dfc         mov ecx, dword [var_4h]
│           0x08048481      c9             leave
│           0x08048482      8d61fc         lea esp, [ecx - 4]
└           0x08048485      c3             ret
[0x0804840b]>

Просматривая основной код программы, мы видим здесь несколько новых инструкций, таких как fstp, fild, fadd и т.д. Путем чистой дедукции мы можем утверждать, что они могут быть связаны с операциями с плавающей точкой, поскольку здесь используются float и double. Мы также можем определить, как вызывается функция print, мы видим, как некоторые параметры заталкиваются в стек.

Поскольку здесь мы имеем дело с переменными, одна из вещей, которую мы можем захотеть сделать, это посмотреть, как radare2 идентифицирует переменные и, возможно, дать им красивое имя. Мы можем сделать это с помощью afv.

Код:
[0x0804840b]> afv
arg int32_t arg_4h @ esp+0x4c
var char var_1dh @ ebp-0x1d
var int32_t var_1ch @ ebp-0x1c
var int32_t var_4h @ ebp-0x4
[0x0804840b]>

Поскольку мы явно идентифицируем char с radare2 (var char var_1...), мы можем переименовать эту переменную в char

Код:
[0x0804840b]> afvn  char1 var_1dh
[0x0804840b]> afvn
char1
var_1ch
var_4h
arg_4h
[0x0804840b]>

Интересна переменная char. При использовании переменных char происходит шестнадцатеричное кодирование символов. В шестнадцатеричной кодировке 'a' соответствует 61 в таблице ascii. В нашем примере мы просто видим, как программа использует mov для перемещения байта 0x61 в позицию переменной.

Код:
0x0804841c      c645e361       mov byte [var_1dh], 0x61    ; 'a' ; 97
0x08048420      c745e4030000.  mov dword [var_1ch], 3

Теперь, когда мы определили переменную char и уже должны быть в состоянии определить переменную int, давайте посмотрим, как программа работает с переменными с плавающей точкой.

Лучший способ проверить это - запустить программу в режиме отладки. В radare2 мы можем открыть программу в режиме отладки, используя опцию -d.

В режиме отладки мы можем использовать такие команды, как db memaddress для установки точки останова, dc для продолжения потока выполнения до этой/этих точек останова и dt для выполнения текущей инструкции и перехода к следующей сразу после нее.

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

Код:
[0x0804840b]> db 0x08048427
[0x0804840b]> db 0x0804842d
[0x0804840b]> db 0x08048430
[0x0804840b]> dc
hit breakpoint at: 8048427
[0x08048427]>

После достижения первой точки останова мы переходим к следующему пункту:

Код:
│           0x08048420      c745e4030000.  mov dword [var_1ch], 3
│           0x08048427      d9054c850408   fld dword [0x804854c]
│           0x0804842d      d95de8         fstp dword [ebp - 0x18]

Мы можем определить, что значение 3 было перемещено в var_1ch, а затем выполняется какая-то странная инструкция fld dword. Инструкция fld загружает в стек 32-битное, 64-битное или 80-битное значение с плавающей точкой. Эта инструкция преобразует 32- и 64-битный операнд в 80-битное значение расширенной точности, прежде чем поместить значение в стек с плавающей точкой. Таким образом, если мы проверим, какое значение fld берет для загрузки, мы увидим что-то вроде:

Код:
[0x08048427]> px 32 @ 0x804854c
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0804854c  0000 9040 0000 0000 0000 1540 011b 033b  ...@.......@...;
0x0804855c  2800 0000 0400 0000 78fd ffff 4400 0000  (.......x...D...
[0x08048427]>

0000 9040 0000 0000 0000 Теперь мы можем попытаться просмотреть содержимое той позиции в памяти, которая явно соответствует переменной

Код:
[0x08048430]> px @ ebp-0x18
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0xbfe7a7e0  0000 9040 a4a8 e7bf aca8 e7bf b184 0408  ...@............
0xbfe7a7f0  dc93 f6b7 10a8 e7bf 0000 0000 37f6 dcb7  ............7...
0xbfe7a800  0090 f6b7 0090 f6b7 0000 0000 37f6 dcb7  ............7...

Мы можем сделать вывод немного более приятным и человекочитаемым (для нашего случая), добавив w и таким образом запустив pxw, когда w приходит из слова (два байта)

Код:
[0x08048430]> pxw @ ebp-0x18
0xbfe7a7e0  0x40900000 0xbfe7a8a4 0xbfe7a8ac 0x080484b1  ...@............
0xbfe7a7f0  0xb7f693dc 0xbfe7a810 0x00000000 0xb7dcf637  ............7...

0x40900000 должно быть значением. Но это значение дает нам мало информации, по крайней мере, в этом формате. Поскольку мы подозреваем, что это число представляет собой закодированное число с плавающей точкой, мы можем опробовать использовать инструмент rax2 для его чтения.

Код:
[0xbfe7a7f0]> rax2  Fx40900000
4.500000f

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

Мы можем проделать то же самое со второй переменной, я оставлю это на ваше усмотрение, вы найдете значение 5.25, но так как это double значение, вместо одного слова у нас будет размер 32.

Наконец, мы видим, как эти параметры попадают в стек. Строка "The sum of..." заталкивается непосредственно в стек, просто выполнив команду push, а переменная sum вставляется в стек с помощью инструкции fstp. Инструкция FST копирует значение в регистре ST(0) в операндназначения, который может быть ячейкой памяти или другим регистром в стеке регистров FPU. При сохранении значения в памяти оно преобразуется в одно- или двухреальный формат.

Код:
|           0x08048447      d95dec         fstp dword [ebp - 0x14]
│           0x0804844a      d945ec         fld dword [ebp - 0x14]
│           0x0804844d      83ec04         sub esp, 4
│           0x08048450      8d6424f8       lea esp, [esp - 8]
│           0x08048454      dd1c24         fstp qword [esp]
│           0x08048457      6810850408     push str.The_sum_of_a__b__and_c_is__f. ; 0x8048510 ; "The sum of a, b, and c is %f." ; const char *format
│           0x0804845c      e87ffeffff     call sym.imp.printf         ; int printf(const char *format)

Мы видим, как значение sum приходит из ebp-0x14 в результате предыдущей операции:

Код:
[0x08048444]> pxw @ ebp-0x14
0xbf8e0ba4  0x00000000 0x00000000 0x40150000 0xb7f573dc  ...........@.s..

И это четко соответствует реальному результату:

Код:
[0x0804844d]> rax2 Fx414c0000
12.750000f (as float)

И затем он появляется в стеке вместе с адресом, указывающим на строку, которая будет напечатана

Код:
[0x0804845c]> pxw @ esp
0xbf8e0b70  0x08048510 0x00000000 0x40298000 0xb7f711b0  ..........)@....
0xbf8e0b80  0x00008000 0xb7f57000 0xb7f55244 0xb7dbd0ec  .....p..DR......
0xbf8e0b90  0x00000001 0x00000000 0x61dd3a50 0x00000003  ........P:.a....

заметьте, что здесь используется little endian и 0x4029800000000000 = 12.75 как double = sum

Условные структуры

Код


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

C:
#include <stdio.h>
int main() {
    signed int number;
    printf("Enter an integer: ");
    scanf("%d", &number);
    if (number > 0) {
        printf("You entered %d.\n", number);
    }
    else{
                printf("You entered a negative number %d.\n", number);
        }
    printf("The if statement is easy.");
    return 0;

Бинарь

Начнем, как обычно, с запуска программы с r2 -d, анализируя ее, переключимся на главную функцию и запустим pdf, чтобы заглянуть в нее:

Код:
[0x080484bb]> pdf
            ; DATA XREF from entry0 @ 0x80483d7
┌ 159: int main (int argc, char **argv, char **envp);
│           ; var int32_t var_10h @ ebp-0x10
│           ; var int32_t var_ch @ ebp-0xc
│           ; var int32_t var_4h @ ebp-0x4
│           ; arg int32_t arg_4h @ esp+0x34
│           0x080484bb      8d4c2404       lea ecx, [arg_4h]
│           0x080484bf      83e4f0         and esp, 0xfffffff0
│           0x080484c2      ff71fc         push dword [ecx - 4]
│           0x080484c5      55             push ebp
│           0x080484c6      89e5           mov ebp, esp
│           0x080484c8      51             push ecx
│           0x080484c9      83ec14         sub esp, 0x14
│           0x080484cc      65a114000000   mov eax, dword gs:[0x14]
│           0x080484d2      8945f4         mov dword [var_ch], eax
│           0x080484d5      31c0           xor eax, eax
│           0x080484d7      83ec0c         sub esp, 0xc
│           0x080484da      68e0850408     push str.Enter_an_integer:  ; 0x80485e0 ; "Enter an integer: "
│           0x080484df      e88cfeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x080484e4      83c410         add esp, 0x10
│           0x080484e7      83ec08         sub esp, 8
│           0x080484ea      8d45f0         lea eax, [var_10h]
│           0x080484ed      50             push eax
│           0x080484ee      68f3850408     push 0x80485f3
│           0x080484f3      e8a8feffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│           0x080484f8      83c410         add esp, 0x10
│           0x080484fb      8b45f0         mov eax, dword [var_10h]
│           0x080484fe      85c0           test eax, eax
│       ┌─< 0x08048500      7e16           jle 0x8048518
│       │   0x08048502      8b45f0         mov eax, dword [var_10h]
│       │   0x08048505      83ec08         sub esp, 8
│       │   0x08048508      50             push eax
│       │   0x08048509      68f6850408     push str.You_entered__d.    ; 0x80485f6 ; "You entered %d.\n"
│       │   0x0804850e      e85dfeffff     call sym.imp.printf         ; int printf(const char *format)
│       │   0x08048513      83c410         add esp, 0x10
│      ┌──< 0x08048516      eb14           jmp 0x804852c
│      │└─> 0x08048518      8b45f0         mov eax, dword [var_10h]
│      │    0x0804851b      83ec08         sub esp, 8
│      │    0x0804851e      50             push eax
│      │    0x0804851f      6808860408     push str.You_entered_a_negative_number__d. ; 0x8048608 ; "You entered a negative number %d.\n"
│      │    0x08048524      e847feffff     call sym.imp.printf         ; int printf(const char *format)
│      │    0x08048529      83c410         add esp, 0x10
│      │    ; CODE XREF from main @ 0x8048516
│      └──> 0x0804852c      83ec0c         sub esp, 0xc
│           0x0804852f      682b860408     push str.The_if_statement_is_easy. ; 0x804862b ; "The if statement is easy."
│           0x08048534      e837feffff     call sym.imp.printf         ; int printf(const char *format)
│           0x08048539      83c410         add esp, 0x10
│           0x0804853c      b800000000     mov eax, 0
│           0x08048541      8b55f4         mov edx, dword [var_ch]
│           0x08048544      653315140000.  xor edx, dword gs:[0x14]
│       ┌─< 0x0804854b      7405           je 0x8048552
│       │   0x0804854d      e82efeffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│       └─> 0x08048552      8b4dfc         mov ecx, dword [var_4h]
│           0x08048555      c9             leave
│           0x08048556      8d61fc         lea esp, [ecx - 4]
└           0x08048559      c3             ret

Итак, здесь мы можем увидеть стрелки, движущиеся вверх и вниз по коду. Эти стрелки указывают на поток выполнения, которому может следовать программа в зависимости от определенных условий, поскольку у нас уже есть код, мы можем предположить, что эти направления потока каким-то образом связаны с частью кода IF-ELSE. Мы также видим, что в верхней части кода объявлены некоторые переменные.

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

Код:
│           0x080484df      e88cfeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x080484e4      83c410         add esp, 0x10
│           0x080484e7      83ec08         sub esp, 8
│           0x080484ea b    8d45f0         lea eax, [input]
│           0x080484ed      50             push eax
│           0x080484ee      68f3850408     push 0x80485f3
│           0x080484f3 b    e8a8feffff     call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│           0x080484f8 b    83c410         add esp, 0x10
│           0x080484fb      8b45f0         mov eax, dword [input]
│           0x080484fe      85c0           test eax, eax

Итак, после вызова PRINTF происходит некоторая корректировка стека, после чего мы видим, что адрес одной из переменных, которые мы видели в верхней части кода, загружается в eax (можно назвать ее как-нибудь вроде input с помощью afvn из чисто логических соображений), затем eax выталкивается в стек для передачи в качестве параметра (можно догадаться) функции SCANF, также в стек выталкивается адрес памяти, в данном случае мы видим, что этот адрес содержит строку ("please enter...:").

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

Код:
[0x080484ed]> dr
eax = 0xbfa9a688
ebx = 0x00000000
ecx = 0x0910b01a
edx = 0xb7f0c870
esi = 0xb7f0b000
edi = 0xb7f0b000
esp = 0xbfa9a678
ebp = 0xbfa9a698
eip = 0x080484ed
eflags = 0x00000296
oeax = 0xffffffff

Затем мы можем перейти к следующей точке останова и осмотреть стек, мы увидим, как там появляются эти два параметра:

Код:
[0x080484ed]> dc
hit breakpoint at: 80484f3
[0x080484f3]> pxr @ esp
0xbfa9a670 0x080485f3  .... @esp (/home/lab/c_examples/bin/ifelse) (.rodata) program R X 'and eax, 0x6f590064' 'ifelse' (%d)
0xbfa9a674 0xbfa9a688  .... ([stack]) stack R W 0xbfa9a74c -->  ([stack]) stack R W 0xbfa9c288 -->  ([stack]) stack R W 0x5f474458 (XDG_VTNR=7) -->  ascii ('X')
0xbfa9a678 0xb7d87a50  Pz.. (/lib/i386-linux-gnu/libc-2.23.so) library R X 'add ebx, 0x1835b0' 'libc-2.23.so'
0xbfa9a67c 0x080485ab  .... (/home/lab/c_examples/bin/ifelse) (.text) sym.__libc_csu_init program R X 'add edi, 1' 'ifelse'
0xbfa9a680 0x00000001  .... 1 (.comment)
0xbfa9a684 0xbfa9a744  D... ([stack]) stack R W 0xbfa9c27f -->  ([stack]) stack R W 0x66692f2e (./ifelse) -->  ascii ('.')

Затем, если мы попадем в следующую точку останова еще раз, программа запросит значение, а затем попадет в последнюю точку останова:

Код:
[0x080484f3]> dc
Enter an integer: 10
hit breakpoint at: 80484f8
[0x080484f8]>

Если мы проверим регистры, то увидим, что EAX установлен в значение 0x00000001, что означает, что вызов SCANF был выполнен правильно без каких-либо серьезных проблем.

Код:
[0x080484f8]> dr
eax = 0x00000001
ebx = 0x00000000
ecx = 0x00000001
edx = 0xb7f0c87c
esi = 0xb7f0b000
edi = 0xb7f0b000
esp = 0xbfa9a670
ebp = 0xbfa9a698
eip = 0x080484f8
eflags = 0x00000246
oeax = 0xffffffff
[0x080484f8]>

Теперь мы можем посмотреть адрес переменной input и увидеть, как число (10 в нашем случае) хранится там в шестнадцатеричном формате.

Код:
[0x080484f8]> pxw @ 0xbfa9a688
0xbfa9a688  0x0000000a 0x630f3300 0xb7f0b3dc 0xbfa9a6b0  .....3.c........

Мы даже можем убедиться в этом, используя rax2 для преобразования в десятичную систему счисления.

Код:
[0x080484f8]> rax2 0xa
10

В этой точке программы мы имеем правильно сохраненное входное значение, после чего программа должна будет проверить, является ли это значение положительным или отрицательным, и в зависимости от этого переместить выполнение в то или иное место. Поэтому мы можем установить еще одну точку останова прямо перед первой стрелкой, связанной с потоком выполнения. Поскольку стрелки могут появляться не всегда (это всегда зависит от используемого вами дизасма/фреймворка), другой способ обнаружить точки переключения в потоке выполнения - искать JLE, JE и подобные им инструкции.

Код:
[0x080484f8]>


│           0x080484f8 b    83c410         add esp, 0x10
│           0x080484fb      8b45f0         mov eax, dword [input]
│           0x080484fe      85c0           test eax, eax
│       ┌─< 0x08048500      7e16           jle 0x8048518
│       │   0x08048502      8b45f0         mov eax, dword [input]
│       │   0x08048505      83ec08         sub esp, 8

Итак, мы видим, что значение 10, в десятичной системе, загружается в EAX, а затем выполняется тестовая инструкция. После выполнения тестовой инструкции состояние регистров флагов следующее:

Код:
[0x08048500]> dr 1
cf = 0x00000000
pf = 0x00000001
af = 0x00000000
zf = 0x00000000
sf = 0x00000000
tf = 0x00000000
if = 0x00000001
df = 0x00000000
of = 0x00000000

По сути, инструкция TEST устанавливает флаги ZF(флаг нуля) и SF(флаг знака) на основе логического И (AND) между операндами и очищает флаг OF(флаг переполнения). Так что в данном случае эти флаги будут установлены в ноль. Тогда инструкция JLE означает JUMP (на адрес памяти, который указан сразу после) Если меньше чем и Меньше чем определяется как: ZF=1 или SF != OF

Таким образом, мы видим, что если мы перезагрузим программу и введем отрицательное число, то флаг SF активизируется:

Код:
[0x080484bb]> db 0x08048500
[0x080484bb]> dc
Enter an integer: -20
hit breakpoint at: 8048500
[0x08048500]> dr 1
cf = 0x00000000
pf = 0x00000000
af = 0x00000000
zf = 0x00000000
sf = 0x00000001
tf = 0x00000000
if = 0x00000001
df = 0x00000000
of = 0x00000000
[0x08048500]>
And one more time, as we enter a positive number the SF flag will not be activated.
[0x080484bb]> db 0x08048500
[0x080484bb]> dc
Enter an integer: 20
hit breakpoint at: 8048500
[0x08048500]> dr 1
cf = 0x00000000
pf = 0x00000001
af = 0x00000000
zf = 0x00000000
sf = 0x00000000
tf = 0x00000000
if = 0x00000001
df = 0x00000000
of = 0x00000000
[0x08048500]>

После этого программа перейдет к адресу памяти, который напечатает "положительный" адрес или к другому, печатающему "отрицательный", затем перейдет или перепрыгнет (с помощью jmp) в конец программы, и таким образом программа завершится.

64-битная версия

64-битная версия практически такая же, можете отметить различия? В основном регистры (esi,rdi...) используются для передачи параметров вместо стека! Кроме того, адреса памяти 64-битные.

Код:
[0x7f584b7b9090]> sf main
[0x561302d2871a]> pdf
            ;-- main:
/ (fcn) main 161
|   main ();
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|              ; DATA XREF from 0x561302d2862d (entry0)
|           0x561302d2871a      55             push rbp
|           0x561302d2871b      4889e5         mov rbp, rsp
|           0x561302d2871e      4883ec10       sub rsp, 0x10
|           0x561302d28722      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x561302d2872b      488945f8       mov qword [local_8h], rax
|           0x561302d2872f      31c0           xor eax, eax
|           0x561302d28731      488d3d100100.  lea rdi, qword str.Enter_an_integer: ; 0x561302d28848 ; "Enter an integer: "
|           0x561302d28738      b800000000     mov eax, 0
|           0x561302d2873d      e89efeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x561302d28742      488d45f4       lea rax, qword [local_ch]
|           0x561302d28746      4889c6         mov rsi, rax
|           0x561302d28749      488d3d0b0100.  lea rdi, qword [0x561302d2885b] ; "%d"
|           0x561302d28750      b800000000     mov eax, 0
|           0x561302d28755      e896feffff     call sym.imp.__isoc99_scanf
|           0x561302d2875a      8b45f4         mov eax, dword [local_ch]
|           0x561302d2875d      85c0           test eax, eax
|       ,=< 0x561302d2875f      7e18           jle 0x561302d28779
|       |   0x561302d28761      8b45f4         mov eax, dword [local_ch]
|       |   0x561302d28764      89c6           mov esi, eax
|       |   0x561302d28766      488d3df10000.  lea rdi, qword str.You_entered__d. ; 0x561302d2885e ; "You entered %d.\n"
|       |   0x561302d2876d      b800000000     mov eax, 0
|       |   0x561302d28772      e869feffff     call sym.imp.printf     ; int printf(const char *format)
|      ,==< 0x561302d28777      eb16           jmp 0x561302d2878f
|      |`-> 0x561302d28779      8b45f4         mov eax, dword [local_ch]
|      |    0x561302d2877c      89c6           mov esi, eax
|      |    0x561302d2877e      488d3deb0000.  lea rdi, qword str.You_entered_a_negative_number__d. ; 0x561302d28870 ; "You entered a negative number %d.\n"
|      |    0x561302d28785      b800000000     mov eax, 0
|      |    0x561302d2878a      e851feffff     call sym.imp.printf     ; int printf(const char *format)
|      |       ; JMP XREF from 0x561302d28777 (main)
|      `--> 0x561302d2878f      488d3dfd0000.  lea rdi, qword str.The_if_statement_is_easy. ; 0x561302d28893 ; "The if statement is easy."
|           0x561302d28796      b800000000     mov eax, 0
|           0x561302d2879b      e840feffff     call sym.imp.printf     ; int printf(const char *format)
|           0x561302d287a0      b800000000     mov eax, 0
|           0x561302d287a5      488b55f8       mov rdx, qword [local_8h]
|           0x561302d287a9      644833142528.  xor rdx, qword fs:[0x28]
|       ,=< 0x561302d287b2      7405           je 0x561302d287b9
|       |   0x561302d287b4      e817feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x561302d287b9      c9             leave
\           0x561302d287ba      c3             ret
[0x561302d2871a]>

Успехов :)

Источник:
Reverse engineering 32 and 64 bits binaries with Radare2 - 2
Автор перевода AFANX
 
Человек не имеет никакого отношения к статье, он автор перевода.
Автор статьи тыц
Частей всего 19
Первая часть, как и вторая, опубликованы выше. Третью сегодня залью.
 
Ку, киберрекруты. В предыдущей главе этого курса по radare2 мы рассмотрели базовую структуру исполняемого двоичного файла, написанного на языке C, уделив внимание вызовам функций ввода/вывода, таким как printf, и базовым структурам данных, таким как переменные, также были рассмотрены базовые меры контроля потока выполнения, такие как операторы if - else. Сегодня мы немного углубимся в эти меры контроля потока выполнения, представим базовое использование оператора case, объявим и используем собственные функции и в конце проанализируем циклы с while и for.

Давайте немного освежим предыдущие понятия с помощью следующего кода, который объявляет функцию для обнаружения положительных чисел и переходит к ней.
C:
#include <stdio.h>

func2(){
    int num;
    printf("Enter a number: ");
    scanf("%d", &num);
    if(num>0) printf("The number is positive.\n");
    getchar();
}

void main(){

    func2();
    getchar();

}

Мы можем скомпилировать этот код и перейти к нему в radare2

Код:
[0x000006a0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x000006a0]>

После запуска начального afl, на этот раз здесь мы можем обнаружить пару интересных мест, на которые стоит обратить внимание. Как обычно, у нас есть sym.main, но на этотраз у нас также есть sym.func2, который выглядит не совсем обычно, поэтому может быть интересно взглянуть на него. Другой начальной подсказкой здесь является наличие scanf, getchar, printf или puts, которые дают общее представление о том, что должна делать программа.

Код:
[0x000006a0]> afl
0x00000000    3 72   -> 73   sym.imp.__libc_start_main
0x00000618    3 23           sym._init
0x00000640    1 6            sym.imp.puts
0x00000650    1 6            sym.imp.__stack_chk_fail
0x00000660    1 6            sym.imp.printf
0x00000670    1 6            sym.imp.getchar
0x00000680    1 6            sym.imp.__isoc99_scanf
0x00000690    1 6            sub.__cxa_finalize_248_690
0x000006a0    1 43           entry0
0x000006d0    4 50   -> 40   sym.deregister_tm_clones
0x00000710    4 66   -> 57   sym.register_tm_clones
0x00000760    4 49           sym.__do_global_dtors_aux
0x000007a0    1 10           entry1.init
0x000007aa    5 111          sym.func2
0x00000819    1 26           sym.main
0x00000840    4 101          sym.__libc_csu_init
0x000008b0    1 2            sym.__libc_csu_fini
0x000008b4    1 9            sym._fini
[0x000006a0]>

Мы начнем с перехода к основной функции:

Код:
[0x000006a0]> s main
[0x00000819]> pdb
            ;-- main:
/ (fcn) sym.main 26
|   sym.main ();
|              ; DATA XREF from 0x000006bd (entry0)
|           0x00000819      55             push rbp
|           0x0000081a      4889e5         mov rbp, rsp
|           0x0000081d      b800000000     mov eax, 0
|           0x00000822      e883ffffff     call sym.func2
|           0x00000827      e844feffff     call sym.imp.getchar        ; int getchar(void)
|           0x0000082c      b800000000     mov eax, 0
|           0x00000831      5d             pop rbp
\           0x00000832      c3             ret
[0x00000819]>

В основном функция main выполняет основные операции выравнивания стека и быстро переходит к функции func2, после выполнения которой она вызывает getchar(), возможно, для поддержания окна открытым (если программа, например, запускается под windows).

Итак, интересной функцией здесь является func2, давайте перейдем к ней.

Код:
[0x00000819]> s sym.func2
[0x000007aa]> pdf
/ (fcn) sym.func2 111
|   sym.func2 ();
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x00000822 (sym.main)
|           0x000007aa      55             push rbp
|           0x000007ab      4889e5         mov rbp, rsp
|           0x000007ae      4883ec10       sub rsp, 0x10
|           0x000007b2      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0x1a08 ; '('
|           0x000007bb      488945f8       mov qword [local_8h], rax
|           0x000007bf      31c0           xor eax, eax
|           0x000007c1      488d3dfc0000.  lea rdi, qword str.Enter_a_number: ; 0x8c4 ; "Enter a number: "
|           0x000007c8      b800000000     mov eax, 0
|           0x000007cd      e88efeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x000007d2      488d45f4       lea rax, qword [local_ch]
|           0x000007d6      4889c6         mov rsi, rax
|           0x000007d9      488d3df50000.  lea rdi, qword [0x000008d5] ; "%d"
|           0x000007e0      b800000000     mov eax, 0
|           0x000007e5      e896feffff     call sym.imp.__isoc99_scanf
|           0x000007ea      8b45f4         mov eax, dword [local_ch]
|           0x000007ed      85c0           test eax, eax
|       ,=< 0x000007ef      7e0c           jle 0x7fd
|       |   0x000007f1      488d3de00000.  lea rdi, qword str.The_number_is_positive. ; 0x8d8 ; "The number is positive."
|       |   0x000007f8      e843feffff     call sym.imp.puts           ; int puts(const char *s)
|       |      ; JMP XREF from 0x000007ef (sym.func2)
|       `-> 0x000007fd      e86efeffff     call sym.imp.getchar        ; int getchar(void)
|           0x00000802      90             nop
|           0x00000803      488b55f8       mov rdx, qword [local_8h]
|           0x00000807      644833142528.  xor rdx, qword fs:[0x28]
|       ,=< 0x00000810      7405           je 0x817
|       |   0x00000812      e839feffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       |      ; JMP XREF from 0x00000810 (sym.func2)
|       `-> 0x00000817      c9             leave
\           0x00000818      c3             ret
[0x000007aa]>

Давайте пройдемся по шагам.

Код:
|           0x000007ae      4883ec10       sub rsp, 0x10
|           0x000007b2      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0x1a08 ; '('
|           0x000007bb      488945f8       mov qword [local_8h], rax
|           0x000007bf      31c0           xor eax, eax

Поскольку мы здесь играем на преимуществе, мы уже знаем, что первое, что делает программа на этом этапе, это объявляет числовую переменную, которая будет использоваться для хранения пользовательского ввода. Первые две строки здесь могут показаться немного запутанными и, на мой взгляд, не так уж сильно связаны с реальным "алгоритмом", который мы пытаемся проанализировать, первая строка присутствует во многих for'ах в большом количестве вызовов функций и в основном выделяет место в стеке для работы там с переменными и структурами данных, поскольку мы используем здесь переменные, программа "резервирует" некоторое пространство, конечно, мы углубимся в это позже, но вы и так знаете, что это нормально.

Затем мы находим эту странную инструкцию mov rax, qword, которая немного выходит за рамки этой статьи. Она вставлена туда нашим компилятором gcc, и делает это для того, чтобы установить проверку защиты стека от потенциальных уязвимостей переполнения буфера. Если мы присмотримся к коду, то увидим операцию XOR в том же самом месте в конце процедуры. В общих чертах, коротко описанных, программа проверяет, не был ли поврежден стек, если да, то запускает __stack_chk_fail для безопасного решения проблемы. Чтобы узнать поподробнее о переполнении буффера, можете прочитать статью Mogen - Эксплуатация бинарных уязвимостей (PWN) для почти начинающих: простое переполнение буфера [0x01].Переменная, помеченная как local_8h, будет использоваться для хранения Canary.

Идем дальше

Код:
|           0x000007c1      488d3dfc0000.  lea rdi, qword str.Enter_a_number: ; 0x8c4 ; "Enter a number: "
|           0x000007c8      b800000000     mov eax, 0
|           0x000007cd      e88efeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x000007d2      488d45f4       lea rax, qword [local_ch]
|           0x000007d6      4889c6         mov rsi, rax
|           0x000007d9      488d3df50000.  lea rdi, qword [0x000008d5] ; "%d"
|           0x000007e0      b800000000     mov eax, 0
|           0x000007e5      e896feffff     call sym.imp.__isoc99_scanf

Как мы уже знаем, на системах x64 параметры или аргументы передаются функциям через регистры, поэтому при выполнении lea rdi, "enter a number" мы загружаем в регистр эффективный адрес, по которому строка "Enter a number:" хранится в памяти. Мы не передаем функции строку целиком, вместо этого мы передаем функции ссылку на ее местоположение, эта концепция очень важна, потому что если функция каким-то образом модифицирует строку, ее исходное значение также будет изменено, если мы передаем копию строки, а не ссылку, любые операции, выполняемые внутри функции с копией, не повлияют на оригинал.

Немного о SEE и AVX

Итак, мы видим, что строка для печати передается по ссылке в качестве параметра функции printf, а затем регистр eax обнуляется. В ABI x86_64, если функция (например, printf) имеет переменные аргументы, то AL (который является частью EAX) должен содержать количество векторных регистров (SSE, AVX), используемых для хранения аргументов этой функции, в нашем случае это число равно нулю.

SSE и AVX имеют по 16 регистров. В SSE они обозначаются как XMM0-XMM15, а в AVX - YMM0-YMM15. Регистры XMM имеют длину 128 бит, а YMM - 256 бит.

SSE добавляет три типизации: m128 , m128d и __m128i. Float, double (d) и integer (i) соответственно.

AVX добавляет три типизации: m256 , m256d и __m256i. Float, double (d) и integer (i) соответственно.

После передачи этих параметров вызывается функция printf, которая должна вывести строку на stdout (экран).

avx.png


ПРИМЕЧАНИЕ: XMM и YMM перекрываются! Регистры XMM рассматриваются как нижняя половина соответствующего регистра YMM. Это может вызвать некоторые проблемы с производительностью при смешивании SSE и AVX кода.

У типов данных с плавающей запятой (m128, m128d, m256 и m256d) есть только один вид структуры данных. Из-за этого GCC позволяет обращаться к компонентам данных в виде массива. То есть: это допустимо:

Код:
 __m256 myvar = _mm256_set1_ps(6.665f); //Set all vector values to a single float
  myvar[0] = 2.22f;                       //This is valid in GCC compiler
  float f = (3.4f + myvar[0]) * myvar[7]; //This is valid in GCC compiler

m128i и m256i являются союзами, поэтому на тип данных нужно ссылаться. Я не нашел подходящего способа получить объявление союза, поэтому я использую функции _mm_extract_epiXX() для получения отдельных значений данных из целочисленных векторов.

Пример работы AVX

Когда выполняется инструкция AVX, процесс происходит следующим образом:

avxplus.png


Все операции выполняются одновременно. С точки зрения производительности, стоимость выполнения одной операции Add над плавающей точкой аналогична выполнению VAdd над 8 плавающими точками в AVX. В таблицах инструкций Агнера Фога есть больше информации о задержках и пропускной способности инструкций. На архитектуре Sandy Bridge VADDPS/D имеет задержку 3 и пропускную способность 1, как и FADD(P).

Вернемся обратно к теме

Сразу после вызова мы видим ссылку на тег local_ch, который загружается сначала в rax, а затем в rsi. local_ch ссылается на выделенное пространство, обычно на переменную, в данном случае это переменная "number". После этого мы видим, что "%d" также загружается в качестве параметра, в данном случае в rdi, затем eax обнуляется, и программа вызывает функцию scanf.

В общепринятых терминах этот блок кода означает printf("Enter a number:"), а затем scanf(%d, &number).

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

Код:
|           0x000007e5      e896feffff     call sym.imp.__isoc99_scanf
|           0x000007ea      8b45f4         mov eax, dword [local_ch]
|           0x000007ed      85c0           test eax, eax
|       ,=< 0x000007ef      7e0c           jle 0x7fd
|       |   0x000007f1      488d3de00000.  lea rdi, qword str.The_number_is_positive. ; 0x8d8 ; "The number is positive."
|       |   0x000007f8      e843feffff     call sym.imp.puts           ; int puts(const char *s)
|       |      ; JMP XREF from 0x000007ef (sym.func2)
|       `-> 0x000007fd      e86efeffff     call sym.imp.getchar        ; int getchar(void)

После выполнения scanf пользовательский ввод (local_ch) перемещается в eax, затем, как мы видели в предыдущем примере, выполняется сравнение с 0 с помощью test и jle. Если пользовательский ввод >0, выполнение продолжится и будет выведено "The number is positive", если это условие не выполняется, программа переходит в конец блока.

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

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

C:
#include <stdio.h>

func2(){
    int num;
    printf("Enter a number: ");
    scanf("%d", &num);

    if(num>0) printf("The number is positive.\n");
    else printf("The number is negative.\n");
    getchar();
}

main(){
    func2();
    getchar();
}

Теперь мы можем перейти непосредственно к делу

Код:
|           0x000007e5      e896feffff     call sym.imp.__isoc99_scanf
|           0x000007ea      8b45f4         mov eax, dword [local_ch]
|           0x000007ed      85c0           test eax, eax
|       ,=< 0x000007ef      7e0e           jle 0x7ff
|       |   0x000007f1      488d3df00000.  lea rdi, qword str.The_number_is_positive. ; 0x8e8 ; "The number is positive."
|       |   0x000007f8      e843feffff     call sym.imp.puts           ; int puts(const char *s)
|      ,==< 0x000007fd      eb0c           jmp 0x80b
|      ||      ; JMP XREF from 0x000007ef (sym.func2)
|      |`-> 0x000007ff      488d3dfa0000.  lea rdi, qword str.The_number_is_negative. ; 0x900 ; "The number is negative."
|      |    0x00000806      e835feffff     call sym.imp.puts           ; int puts(const char *s)
|      |       ; JMP XREF from 0x000007fd (sym.func2)
|      `--> 0x0000080b      e860feffff     call sym.imp.getchar        ; int getchar(void)

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

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

Код:
                                                   | test eax, eax                              |                                               
                                                   | jle 0x7ff;[gc]                             |                                               
                                                   `--------------------------------------------'                                               
                                                           | |                                                                                 
                                                           | '--------------------.                                                             
                              .----------------------------'                      |                                                             
                              |                                                   |                                                             
                              |                                                   |                                                             
                      .------------------------------------------------.    .------------------------------------------------.                 
                      |  0x7f1 ;[gg]                                   |    |  0x7ff ;[gc]                                   |                 
                      |   ; 0x8e8                                      |    |      ; JMP XREF from 0x000007ef (sym.func2)    |                 
                      |   ; "The number is positive."                  |    |   ; 0x900                                      |                 
                      | lea rdi, qword str.The_number_is_positive.     |    |   ; "The number is negative."                  |                 
                      | call sym.imp.puts;[ge]                         |    | lea rdi, qword str.The_number_is_negative.     |                 
                      | jmp 0x80b;[gf]                                 |    | call sym.imp.puts;[ge]                         |                 
                      `------------------------------------------------'    `------------------------------------------------'                 
                          |                                                     |                                                               
                          '----------------------------.                        |                                                               
                                                       .------------------------'                                                               
                                                       |                                                                                       
                                                       |                                                                                       
                                                   .---------------------------------------------.

SWITCH CASE

Случай switch относится к более продвинутому сценарию, чем if else. Оператор if else отлично работает, если у нас есть пара-тройка способов перенаправить поток выполнения программы, но если у нас много разных случаев, мы можем захотеть
использовать что-то более продвинутое, например, switch case.

Мы будем работать со следующим кодом:

C:
#include <stdio.h>

func2(){
    printf("Enter a key and then press enter: ");
    char key;
    scanf("%c",&key);

    switch(key){
        case ' ':
            printf("Space. \n");
        break;
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '0':
            printf("Digit.\n");
            break;
        default: printf("Neither space nor digit.\n");
}

}

main(){
    func2();
    getchar();
}

Как мы видим, код довольно прост, как обычно, все волшебство происходит внутри функции func2. Здесь программа считывает символ со стандартного ввода и передает его в функцию switch. Затем значение символа будет проанализировано через все case'ы. Если введенным символом является пробел, то будет вызвана функция print, а затем проверка завершится инструкцией break, то есть в этом случае программа сразу выйдет из блока switch.

Эти следующие утверждения case можно интерпретировать как очень длинное предложение if, каждое из которых представляет собой что-то вроде if key == 'X', за которым следует символ and (&&), а затем следующее условие. Это означает следующее: если входной сигнал изменяется от '0' до '9', то это означает, что пользователь ввел цифру, и будет напечатано "Digit.\n", будет выполнен break и поток программы будетнаправлен за пределы блока switch. Если условие case не выполняется, программа выполнит то, что было в случае по умолчанию, “Neither space nor digit”.

Теперь, когда мы знаем, что делает эта программа, давайте перейдем непосредственно к radare2. На этот раз мы проанализируем корзину как статически, так и динамически

Код:
red@blue:~/c/chapter3$ radare2 -d case
Process with PID 7901 started...
= attach 7901 7901
bin.baddr 0x560c23c18000
Using 0x560c23c18000
asm.bits 64
[0x7ff6a3b32090]> aaa
[ WARNING : block size exceeding max block size at 0x560c23e18fe0
[+] Try changing it with e anal.bb.maxsize
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
= attach 7901 7901
7901
[0x7ff6a3b32090]>

Как мы видим, мы запустили программу с флагом -d, чтобы мы могли ее отлаживать. То, что мы хотим проанализировать, находится внутри функции func2, поэтому мы можем начать с нее.

Код:
[0x7ff6a3b32090]> s sym.func2
[0x560c23c187aa]> pdf
/ (fcn) sym.func2 154
|   sym.func2 ();
|           ; var int local_9h @ rbp-0x9
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x560c23c1884d (sym.main)
|           0x560c23c187aa      55             push rbp
|           0x560c23c187ab      4889e5         mov rbp, rsp
|           0x560c23c187ae      4883ec10       sub rsp, 0x10
|           0x560c23c187b2      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x560c23c187bb      488945f8       mov qword [local_8h], rax
|           0x560c23c187bf      31c0           xor eax, eax
|           0x560c23c187c1      488d3d200100.  lea rdi, qword str.Enter_a_key_and_then_press_enter: ; 0x560c23c188e8 ; "Enter a key and then press enter: "
|           0x560c23c187c8      b800000000     mov eax, 0
|           0x560c23c187cd      e88efeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x560c23c187d2      488d45f7       lea rax, qword [local_9h]
|           0x560c23c187d6      4889c6         mov rsi, rax
|           0x560c23c187d9      488d3d2b0100.  lea rdi, qword [0x560c23c1890b] ; "%c"
|           0x560c23c187e0      b800000000     mov eax, 0
|           0x560c23c187e5      e896feffff     call sym.imp.__isoc99_scanf
|           0x560c23c187ea      0fb645f7       movzx eax, byte [local_9h]
|           0x560c23c187ee      0fbec0         movsx eax, al
|           0x560c23c187f1      83f820         cmp eax, 0x20           ; 32
|       ,=< 0x560c23c187f4      740f           je 0x560c23c18805
|       |   0x560c23c187f6      83f820         cmp eax, 0x20           ; 32
|      ,==< 0x560c23c187f9      7c26           jl 0x560c23c18821
|      ||   0x560c23c187fb      83e830         sub eax, 0x30           ; '0'
|      ||   0x560c23c187fe      83f809         cmp eax, 9              ; 9
|     ,===< 0x560c23c18801      771e           ja 0x560c23c18821
|    ,====< 0x560c23c18803      eb0e           jmp 0x560c23c18813
|    |||`-> 0x560c23c18805      488d3d020100.  lea rdi, qword str.Space. ; 0x560c23c1890e ; "Space. "
|    |||    0x560c23c1880c      e82ffeffff     call sym.imp.puts       ; int puts(const char *s)
|    |||,=< 0x560c23c18811      eb1a           jmp 0x560c23c1882d
|    ||||      ; JMP XREF from 0x560c23c18803 (sym.func2)
|    `----> 0x560c23c18813      488d3dfc0000.  lea rdi, qword str.Digit. ; 0x560c23c18916 ; "Digit."
|     |||   0x560c23c1881a      e821feffff     call sym.imp.puts       ; int puts(const char *s)
|    ,====< 0x560c23c1881f      eb0c           jmp 0x560c23c1882d
|    |``--> 0x560c23c18821      488d3df50000.  lea rdi, qword str.Neither_space_nor_digit. ; 0x560c23c1891d ; "Neither space nor digit."
|    |  |   0x560c23c18828      e813feffff     call sym.imp.puts       ; int puts(const char *s)
|    |  |      ; JMP XREF from 0x560c23c18811 (sym.func2)
|    |  |      ; JMP XREF from 0x560c23c1881f (sym.func2)
|    `--`-> 0x560c23c1882d      90             nop
|           0x560c23c1882e      488b55f8       mov rdx, qword [local_8h]
|           0x560c23c18832      644833142528.  xor rdx, qword fs:[0x28]
|       ,=< 0x560c23c1883b      7405           je 0x560c23c18842
|       |   0x560c23c1883d      e80efeffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x560c23c18842      c9             leave
\           0x560c23c18843      c3             ret
[0x560c23c187aa]>

Как мы уже говорили, этот блок начинается с механизма защиты стека, который мы представили ранее. Как мы видим, управление потоком выполнения здесь немного сложнее. Это то, что иногда случается с вами, когда вы занимаетесь ctfs или даже
реальными проектами реверс-инжиниринга. Есть два обычных решения, которые можно применить в таких ситуациях. Мы можем либо перейти в визуальный режим, либо проверить строки или интересные вызовы функций. Здесь мы можем легко обнаружить строки Space, Digit и Neither space or digit, что говорит о многом и в принципе решает проблему, поскольку мы легко определили три основных случая.

Магия здесь начинается со scanf

Код:
|           0x560c23c187d2      488d45f7       lea rax, qword [local_9h]
|           0x560c23c187d6      4889c6         mov rsi, rax
|           0x560c23c187d9      488d3d2b0100.  lea rdi, qword [0x560c23c1890b] ; "%c"
|           0x560c23c187e0      b800000000     mov eax, 0
|           0x560c23c187e5      e896feffff     call sym.imp.__isoc99_scanf
|           0x560c23c187ea      0fb645f7       movzx eax, byte [local_9h]
|           0x560c23c187ee      0fbec0         movsx eax, al
|           0x560c23c187f1      83f820         cmp eax, 0x20           ; 32
|       ,=< 0x560c23c187f4      740f           je 0x560c23c18805

В этом примере мы начинаем с передачи нескольких параметров функции scanf через регистры, с помощью %c мы указываем, что собираемся прочитать символ, а local_9h будет местом расположения этого символа в памяти.

После считывания значения символа с пользовательского ввода программа готовит его к сравнению с набором символов в первом операторе case. Поскольку символ в C хранится в одном (1) байте, программе нужно только содержимое AL (RAX/EAX нижний). После загрузки "правильного" значения программа сравнивает его с 0x20, которое представляет собой значение для "пробела" в соответствии с таблицей ASCII.

Код:
|           0x560c23c187f1      83f820         cmp eax, 0x20           ; 32
|       ,=< 0x560c23c187f4      740f           je 0x560c23c18805
|       |   0x560c23c187f6      83f820         cmp eax, 0x20           ; 32
|      ,==< 0x560c23c187f9      7c26           jl 0x560c23c18821
|      ||   0x560c23c187fb      83e830         sub eax, 0x30           ; '0'
|      ||   0x560c23c187fe      83f809         cmp eax, 9              ; 9
|     ,===< 0x560c23c18801      771e           ja 0x560c23c18821
|    ,====< 0x560c23c18803      eb0e           jmp 0x560c23c18813
|    |||`-> 0x560c23c18805      488d3d020100.  lea rdi, qword str.Space. ; 0x560c23c1890e ; "Space. "
|    |||    0x560c23c1880c      e82ffeffff     call sym.imp.puts       ; int puts(const char *s)
|    |||,=< 0x560c23c18811      eb1a           jmp 0x560c23c1882d
|    ||||      ; JMP XREF from 0x560c23c18803 (sym.func2)
|    `----> 0x560c23c18813      488d3dfc0000.  lea rdi, qword str.Digit. ; 0x560c23c18916 ; "Digit."
|     |||   0x560c23c1881a      e821feffff     call sym.imp.puts       ; int puts(const char *s)
|    ,====< 0x560c23c1881f      eb0c           jmp 0x560c23c1882d
|    |``--> 0x560c23c18821      488d3df50000.  lea rdi, qword str.Neither_space_nor_digit. ; 0x560c23c1891d ; "Neither space nor digit."
|    |  |   0x560c23c18828      e813feffff     call sym.imp.puts       ; int puts(const char *s)
|    |  |      ; JMP XREF from 0x560c23c18811 (sym.func2)
|    |  |      ; JMP XREF from 0x560c23c1881f (sym.func2)
|    `--`-> 0x560c23c1882d      90             nop

Как видите, если содержимое eax (ввод) равно значению space, программа перейдет на 0x560c23c18805 и там выдаст запрос "space", после чего выйдет из блока, перейдя сразу на 0x560c23c1882d (nop).

Самое интересное здесь происходит сразу после этого. После этого первого сравнения, если условие перехода не выполнено, программа снова сравнит значение с пробелом, на этот раз проверяя, меньше ли значение пробела, если меньше пробела, программа перейдет к "Neither space nor digit" и выйдет из блока, почему? Это простой трюк для компиляторов, чтобы проверить, находится ли какой-либо символ вне диапазона символов. Как вы сами можете проверить, все цифры от 0 до 9 имеют значения выше 0x20 в таблице ascii, поэтому все, что ниже 0x20, не должно быть цифрой.

Затем выполняется последнее сравнение:

Код:
|      ||   0x560c23c187fb      83e830         sub eax, 0x30           ; '0'
|      ||   0x560c23c187fe      83f809         cmp eax, 9              ; 9
|     ,===< 0x560c23c18801      771e           ja 0x560c23c18821
|    ,====< 0x560c23c18803      eb0e           jmp 0x560c23c18813

Программа вычитает 0x30 из eax и сравнивает его с 0x9, почему? Ну, это еще один вычислительно простой способ для компилятора сделать это, ascii значение из char '9' равно 0x39, так что минус 0x30 возвращает 0x9. После сравнения выполняется ja для перехода к printf "Digit", если условие (key = '9') выполнено, в противном случае программа переходит в зону "neither space or digit" и выходит.

Давайте рассмотрим это более подробно, отладив программу, вводящую цифру.

Начнем с установки нескольких точек останова здесь и там

Код:
[0x560c23c187aa]> db 0x560c23c187f1
[0x560c23c187aa]> db 0x560c23c187fb
[0x560c23c187aa]>

Затем мы запускаем выполнение до первой точки останова

Код:
[0x560c23c187aa]> dc
Enter a key and then press enter: 5
hit breakpoint at: 560c23c187f1
[0x560c23c187f1]> pdb
/ (fcn) sym.func2 154
|   sym.func2 ();
|           ; var int local_9h @ rbp-0x9
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x560c23c1884d (sym.main)
|           0x560c23c187aa      55             push rbp
|           0x560c23c187ab      4889e5         mov rbp, rsp
|           0x560c23c187ae      4883ec10       sub rsp, 0x10
|           0x560c23c187b2      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x560c23c187bb      488945f8       mov qword [local_8h], rax
|           0x560c23c187bf      31c0           xor eax, eax
|           0x560c23c187c1      488d3d200100.  lea rdi, qword str.Enter_a_key_and_then_press_enter: ; 0x560c23c188e8 ; "Enter a key and then press enter: "
|           0x560c23c187c8      b800000000     mov eax, 0
|           0x560c23c187cd      e88efeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x560c23c187d2      488d45f7       lea rax, qword [local_9h]
|           0x560c23c187d6      4889c6         mov rsi, rax
|           0x560c23c187d9      488d3d2b0100.  lea rdi, qword [0x560c23c1890b] ; "%c"
|           0x560c23c187e0      b800000000     mov eax, 0z
|           0x560c23c187e5      e896feffff     call sym.imp.__isoc99_scanf
|           0x560c23c187ea      0fb645f7       movzx eax, byte [local_9h]
|           0x560c23c187ee      0fbec0         movsx eax, al
|           ;-- rip:
|           0x560c23c187f1 b    83f820         cmp eax, 0x20           ; 32
|       ,=< 0x560c23c187f4      740f           je 0x560c23c18805
[0x560c23c187f1]>

Таким образом, значение '5' было загружено в переменную и теперь находится в AL (RAX)

Код:
|           0x560c23c187f1 b    83f820         cmp eax, 0x20           ; 32
|       ,=< 0x560c23c187f4      740f           je 0x560c23c18805
[0x560c23c187f1]> dr
rax = 0x00000035

Итак, rax = 0x35, что соответствует '5' в ascii. '5' будет сравниваться с ' ', и поскольку это не одно и то же число, флаг нуля останется нулевым.

Код:
[0x560c23c187f1]> ds
[0x560c23c187f1]> dr 1
cf = 0x00000000
pf = 0x00000000
af = 0x00000000
zf = 0x00000000
sf = 0x00000000
tf = 0x00000000
if = 0x00000001
df = 0x00000000
of = 0x00000000
[0x560c23c187f1]>

После этого поток выполнения пойдет дальше, и мы встретим другой cmp.

cmp работает следующим образом с флагами

Код:
Assume result = op1 - op2

CF - 1 if unsigned op2 > unsigned op1
OF - 1 if sign bit of OP1 != sign bit of result
SF - 1 if MSB (aka sign bit) of result = 1
ZF - 1 if Result = 0 (i.e. op1=op2)
AF - 1 if Carry in the low nibble of result
PF - 1 if Parity of Least significant byte is even

Поскольку ничего из этого не выполнено, флаги останутся нулевыми. После этого из
нашего значения будет вычтено 0x30, так что 0x35 - 0x30 = 0x5.

Код:
      ||   0x560c23c187fb b    83e830         sub eax, 0x30           ; '0'
|      ||   0x560c23c187fe      83f809         cmp eax, 9              ; 9
|     ,===< 0x560c23c18801      771e           ja 0x560c23c18821
|     |||   ;-- rip:
|    ,====< 0x560c23c18803      eb0e           jmp 0x560c23c18813
|    |||`-> 0x560c23c18805      488d3d020100.  lea rdi, qword str.Space. ; 0x560c23c1890e ; "Space. "

Затем значение будет сравниваться с 9, поэтому флаги будут выглядеть следующим образом

Код:
[0x560c23c187f1]> dr 1
cf = 0x00000001
pf = 0x00000001
af = 0x00000001
zf = 0x00000000
sf = 0x00000001
tf = 0x00000000
if = 0x00000001
df = 0x00000000
of = 0x00000000
[0x560c23c187f1]>

А поскольку ja = Jump short if выше (CF=0 и ZF=0), поток выполнения перейдет прямо к printf("Digit. \n").

Код:
|    ||||      ; JMP XREF from 0x560c23c18803 (sym.func2)
|    `----> 0x560c23c18813      488d3dfc0000.  lea rdi, qword str.Digit. ; 0x560c23c18916 ; "Digit."
|     |||   0x560c23c1881a      e821feffff     call sym.imp.puts       ; int puts(const char *s)
|    ,====< 0x560c23c1881f      eb0c           jmp 0x560c23c1882d

На данный момент мы уже знаем, чем закончится программа, поэтому перейдем к нашему последнему примеру с switch-case.

C:
#include <stdio.h>

func2(){
    printf("Enter a key and then press enter: ");
    int val;

    printf("Select a fruit: \n");
    printf("1: Apple\n");
    printf("2: Orange\n");
    printf("3: Banana\n");
    printf("4: Pear\n");

    scanf("%d",&val);

    switch(val){
        case 1:
            printf("Apple. \n");
            break;
        case 2:
            printf("Orange. \n");
            break;
        case 3:
            printf("Banana. \n");
            break;
        case 4:
            printf("Pear. \n");
            break;
        default: printf("Nothing selected.\n");
    }

}

main(){
    func2();
    getchar();
}

Мы можем легко проверить его с помощью визуального режима radare2

Код:
                                                                                                       | cmp eax, 2                                               |                                           
                                                                                                        | je 0x854;[gd]                                            |                                           
                                                                                                        `----------------------------------------------------------'                                           
                                                                                                                | |                                                                                           
                                                                                                                | '--------------------------------------------.                                               
                                                                                         .----------------------'                                              |                                               
                                                                                         |                                                                     |                                               
                                                                                         |                                                                     |                                               
                                                                                 .--------------------.                                                  .---------------------------------------------.       
                                                                                 |  0x82e ;[gg]       |                                                  |  0x854 ;[gd]                                |       
                                                                                 | cmp eax, 2         |                                                  |      ; JMP XREF from 0x0000082c (sym.func2) |       
                                                                                 | jg 0x83a;[gf]      |                                                  |   ; 0x9ac                                   |       
                                                                                 `--------------------'                                                  |   ; "Orange. "                              |       
                                                                                         | |                                                             | lea rdi, qword str.Orange.                  |       
                                                                                         | |                                                             | call sym.imp.puts;[gb]                      |       
                                                                                         | |                                                             | jmp 0x88a;[gp]                              |       
                                                                                         | |                                                             `---------------------------------------------'       
                                                                                         | |                                                                 |                                                 
                                                                                         | '---------------------.                                           |                                                 
                                                   .-------------------------------------'                       |                                           |                                                 
                                                   |                                                             |                                           '------------------.                             
                                                   |                                                             |                                                              |                             
                                                   |                                                             |                                                              |                             
                                           .--------------------.                                          .---------------------------------------------.                      |                             
                                           |  0x833 ;[gi]       |                                          |  0x83a ;[gf]                                |                      |                             
                                           | cmp eax, 1         |                                          |      ; JMP XREF from 0x00000831 (sym.func2) |                      |                             
                                           | je 0x846;[gh]      |                                          | cmp eax, 3                                  |                      |                             
                                           `--------------------'                                          | je 0x862;[gl]                               |                      |                             
                                                   | |                                                     `---------------------------------------------'                      |                             
                                                   | |                                                             | |                                                          |                             
                                                   | '--.                                                          | |                                                          |                             
                                .------------------'    |                                                          | |                                                          |                             
                                |                       |                                                          | '--------------.                                           |                             
                                |                       |                                                   .------'                |                                           |                             
                                |                       |                                                   |                       |                                           |                             
                                |                       |                                                   |                       |                                           |                             
                        .--------------------.    .---------------------------------------------.   .--------------------.    .---------------------------------------------.   |                             
                        |  0x838 ;[gk]       |    |  0x846 ;[gh]                                |   |  0x83f ;[gn]       |    |  0x862 ;[gl]                                |   |                             
                        | jmp 0x87e;[gj]     |    |      ; JMP XREF from 0x00000836 (sym.func2) |   | cmp eax, 4         |    |      ; JMP XREF from 0x0000083d (sym.func2) |   |                             
                        `--------------------'    |   ; 0x9a4                                   |   | je 0x870;[gm]      |    |   ; 0x9b5                                   |   |                             
                            |                     |   ; "Apple. "                               |   `--------------------'    |   ; "Banana. "                              |   |                             
                            |                     | lea rdi, qword str.Apple.                   |           | |               | lea rdi, qword str.Banana.                  |   |                             
                            |                     | call sym.imp.puts;[gb]                      |           | |               | call sym.imp.puts;[gb]                      |   |                             
                            |                     | jmp 0x88a;[gp]                              |           | |               | jmp 0x88a;[gp]                              |   |                             
                            |                     `---------------------------------------------'           | |               `---------------------------------------------'   |


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

Циклы While и For

Другой случай очень распространенной динамики, которую вы увидите почти в каждом бинарнике, - это циклы, обычно представленные операторами while и for. Код, расположенный внутри цикла while, будет продолжать циклическое выполнение до техпор, пока условие цикла истинно, код, расположенный внутри for, будет выполняться N раз.

C:
#include <stdio.h>

func2(){
int num;

printf("Enter a num, (exit with 0):");

scanf("%d", &num);

while(num != 0){

        if(num > 0) printf("Positive num\n");
        else printf("Negative num\n");

        printf("Enter another num (exit with 0):");

        scanf("%d", &num);

    }

}

main(){

    func2();
    getchar();

}

Внутри radare2 функция func2 будет выглядеть следующим образом:

Код:
[0x7fba89d68090]> s sym.func2
[0x55b75b1ba7aa]> pdf
/ (fcn) sym.func2 170
|   sym.func2 ();
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x55b75b1ba85d (sym.main)
|           0x55b75b1ba7aa      55             push rbp
|           0x55b75b1ba7ab      4889e5         mov rbp, rsp
|           0x55b75b1ba7ae      4883ec10       sub rsp, 0x10
|           0x55b75b1ba7b2      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x55b75b1ba7bb      488945f8       mov qword [local_8h], rax
|           0x55b75b1ba7bf      31c0           xor eax, eax
|           0x55b75b1ba7c1      488d3d300100.  lea rdi, qword str.Enter_a_num___exit_with_0_: ; 0x55b75b1ba8f8 ; "Enter a num, (exit with 0):"
|           0x55b75b1ba7c8      b800000000     mov eax, 0
|           0x55b75b1ba7cd      e88efeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x55b75b1ba7d2      488d45f4       lea rax, qword [local_ch]
|           0x55b75b1ba7d6      4889c6         mov rsi, rax
|           0x55b75b1ba7d9      488d3d340100.  lea rdi, qword [0x55b75b1ba914] ; "%d"
|           0x55b75b1ba7e0      b800000000     mov eax, 0
|           0x55b75b1ba7e5      e896feffff     call sym.imp.__isoc99_scanf
|       ,=< 0x55b75b1ba7ea      eb4a           jmp 0x55b75b1ba836
|      .--> 0x55b75b1ba7ec      8b45f4         mov eax, dword [local_ch]
|      :|   0x55b75b1ba7ef      85c0           test eax, eax
|     ,===< 0x55b75b1ba7f1      7e0e           jle 0x55b75b1ba801
|     |:|   0x55b75b1ba7f3      488d3d1d0100.  lea rdi, qword str.Positive_num ; 0x55b75b1ba917 ; "Positive num"
|     |:|   0x55b75b1ba7fa      e841feffff     call sym.imp.puts       ; int puts(const char *s)
|    ,====< 0x55b75b1ba7ff      eb0c           jmp 0x55b75b1ba80d
|    |`---> 0x55b75b1ba801      488d3d1c0100.  lea rdi, qword str.Negative_num ; 0x55b75b1ba924 ; "Negative num"
|    | :|   0x55b75b1ba808      e833feffff     call sym.imp.puts       ; int puts(const char *s)
|    | :|      ; JMP XREF from 0x55b75b1ba7ff (sym.func2)
|    `----> 0x55b75b1ba80d      488d3d240100.  lea rdi, qword str.Enter_another_num__exit_with_0_: ; 0x55b75b1ba938 ; "Enter another num (exit with 0):"
|      :|   0x55b75b1ba814      b800000000     mov eax, 0
|      :|   0x55b75b1ba819      e842feffff     call sym.imp.printf     ; int printf(const char *format)
|      :|   0x55b75b1ba81e      488d45f4       lea rax, qword [local_ch]
|      :|   0x55b75b1ba822      4889c6         mov rsi, rax
|      :|   0x55b75b1ba825      488d3de80000.  lea rdi, qword [0x55b75b1ba914] ; "%d"
|      :|   0x55b75b1ba82c      b800000000     mov eax, 0
|      :|   0x55b75b1ba831      e84afeffff     call sym.imp.__isoc99_scanf
|      :|      ; JMP XREF from 0x55b75b1ba7ea (sym.func2)
|      :`-> 0x55b75b1ba836      8b45f4         mov eax, dword [local_ch]
|      :    0x55b75b1ba839      85c0           test eax, eax
|      `==< 0x55b75b1ba83b      75af           jne 0x55b75b1ba7ec
|           0x55b75b1ba83d      90             nop
|           0x55b75b1ba83e      488b55f8       mov rdx, qword [local_8h]
|           0x55b75b1ba842      644833142528.  xor rdx, qword fs:[0x28]
|       ,=< 0x55b75b1ba84b      7405           je 0x55b75b1ba852
|       |   0x55b75b1ba84d      e8fefdffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x55b75b1ba852      c9             leave
\           0x55b75b1ba853      c3             ret
[0x55b75b1ba7aa]>

Как видите, логика программы здесь в общих чертах похожа на предыдущие блоки if-else. Разница лишь в том, что от инструкции, расположенной по адресу 0x55b75b1ba83b, вверх идет стрелка, которая отражает суть цикла. Давайте теперь пройдем шаг за шагом.

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

Код:
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x55b75b1ba85d (sym.main)
|           0x55b75b1ba7aa      55             push rbp
|           0x55b75b1ba7ab      4889e5         mov rbp, rsp
|           0x55b75b1ba7ae      4883ec10       sub rsp, 0x10
|           0x55b75b1ba7b2      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x55b75b1ba7bb      488945f8       mov qword [local_8h], rax
|           0x55b75b1ba7bf      31c0           xor eax, eax

Затем программа запрашивает ввод пользователя и сохраняет его в переменной local_ch.

Код:
|           0x55b75b1ba7cd      e88efeffff     call sym.imp.printf     ; int printf(const char *format)
|           0x55b75b1ba7d2      488d45f4       lea rax, qword [local_ch]
|           0x55b75b1ba7d6      4889c6         mov rsi, rax
|           0x55b75b1ba7d9      488d3d340100.  lea rdi, qword [0x55b75b1ba914] ; "%d"
|           0x55b75b1ba7e0      b800000000     mov eax, 0
|           0x55b75b1ba7e5      e896feffff     call sym.imp.__isoc99_scanf

Мы можем даже переименовать эту переменную, чтобы сделать все это более читабельным.

Код:
[0x55b75b1ba7aa]> afvn local_ch input
[0x55b75b1ba7aa]> afvn
local_8h
input
[0x55b75b1ba7aa]>

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

Код:
|       ,=< 0x55b75b1ba7ea      eb4a           jmp 0x55b75b1ba836
|      .--> 0x55b75b1ba7ec      8b45f4         mov eax, dword [input]

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

Код:
|      :`-> 0x55b75b1ba836      8b45f4         mov eax, dword [input]
|      :    0x55b75b1ba839      85c0           test eax, eax
|      `==< 0x55b75b1ba83b      75af           jne 0x55b75b1ba7ec

Следующий блок кода? Мы уже знаем его

Код:
|      .--> 0x55b75b1ba7ec      8b45f4         mov eax, dword [input]
|      :|   0x55b75b1ba7ef      85c0           test eax, eax
|     ,===< 0x55b75b1ba7f1      7e0e           jle 0x55b75b1ba801
|     |:|   0x55b75b1ba7f3      488d3d1d0100.  lea rdi, qword str.Positive_num ; 0x55b75b1ba917 ; "Positive num"
|     |:|   0x55b75b1ba7fa      e841feffff     call sym.imp.puts       ; int puts(const char *s)
|    ,====< 0x55b75b1ba7ff      eb0c           jmp 0x55b75b1ba80d
|    |`---> 0x55b75b1ba801      488d3d1c0100.  lea rdi, qword str.Negative_num ; 0x55b75b1ba924 ; "Negative num"
|    | :|   0x55b75b1ba808      e833feffff     call sym.imp.puts       ; int puts(const char *s)
|    | :|      ; JMP XREF from 0x55b75b1ba7ff (sym.func2)

Программа проверяет входные данные, положительные или отрицательные, выполняя тест и переход.

И сразу после этого она снова запрашивает входные данные и проверяет условие выхода в нижней части блока

Код:
|    `----> 0x55b75b1ba80d      488d3d240100.  lea rdi, qword str.Enter_another_num__exit_with_0_: ; 0x55b75b1ba938 ; "Enter another num (exit with 0):"
|      :|   0x55b75b1ba814      b800000000     mov eax, 0
|      :|   0x55b75b1ba819      e842feffff     call sym.imp.printf     ; int printf(const char *format)
|      :|   0x55b75b1ba81e      488d45f4       lea rax, qword [input]
|      :|   0x55b75b1ba822      4889c6         mov rsi, rax
|      :|   0x55b75b1ba825      488d3de80000.  lea rdi, qword [0x55b75b1ba914] ; "%d"
|      :|   0x55b75b1ba82c      b800000000     mov eax, 0
|      :|   0x55b75b1ba831      e84afeffff     call sym.imp.__isoc99_scanf
|      :|      ; JMP XREF from 0x55b75b1ba7ea (sym.func2)
|      :`-> 0x55b75b1ba836      8b45f4         mov eax, dword [input]
|      :    0x55b75b1ba839      85c0           test eax, eax
|      `==< 0x55b75b1ba83b      75af           jne 0x55b75b1ba7ec

И это практически все.

Другой распространенный способ создания циклов - использование for, как показано здесь:

C:
#include <stdio.h>


func2(){

int counter = 0;

for(counter=1; counter <=10; counter++){

        printf("%d ", counter);

}

}

main(){

        func2();
        getchar();

}

А дизазм radare2 будет выглядеть следующим образом:

Код:
[0x0000068a]> pdf
/ (fcn) sym.func2 59
|   sym.func2 ();
|           ; var int input @ rbp-0x4
|              ; CALL XREF from 0x000006ce (sym.main)
|           0x0000068a      55             push rbp
|           0x0000068b      4889e5         mov rbp, rsp
|           0x0000068e      4883ec10       sub rsp, 0x10
|           0x00000692      c745fc000000.  mov dword [input], 0
|           0x00000699      c745fc010000.  mov dword [input], 1
|       ,=< 0x000006a0      eb1a           jmp 0x6bc
|       |      ; JMP XREF from 0x000006c0 (sym.func2)
|      .--> 0x000006a2      8b45fc         mov eax, dword [input]
|      :|   0x000006a5      89c6           mov esi, eax
|      :|   0x000006a7      488d3db60000.  lea rdi, qword [0x00000764] ; "%d "
|      :|   0x000006ae      b800000000     mov eax, 0
|      :|   0x000006b3      e898feffff     call sym.imp.printf         ; int printf(const char *format)
|      :|   0x000006b8      8345fc01       add dword [input], 1
|      :|      ; JMP XREF from 0x000006a0 (sym.func2)
|      :`-> 0x000006bc      837dfc0a       cmp dword [input], 0xa      ; [0xa:4]=0
|      `==< 0x000006c0      7ee0           jle 0x6a2
|           0x000006c2      90             nop
|           0x000006c3      c9             leave
\           0x000006c4      c3             ret
[0x0000068a]>

Обратите внимание, что мы не видим здесь никакой защиты стека, можете сказать почему? Наверное, потому что мы не получаем здесь никаких входных данных!

Итак, программа начинается с установки значения в ноль (счетчик = 0), затем она устанавливает значение в 1, чтобы запустить процесс for

Код:
|           0x00000692      c745fc000000.  mov dword [input], 0
|           0x00000699      c745fc010000.  mov dword [input], 1
|       ,=< 0x000006a0      eb1a           jmp 0x6bc

Затем он сравнивает значение с 10 (0xA).

Код:
|      :`-> 0x000006bc      837dfc0a       cmp dword [input], 0xa      ; [0xa:4]=0
|      `==< 0x000006c0      7ee0           jle 0x6a2

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

Код в основном печатает значение переменной и прибавляет к нему 1

Код:
|      .--> 0x000006a2      8b45fc         mov eax, dword [input]
|      :|   0x000006a5      89c6           mov esi, eax
|      :|   0x000006a7      488d3db60000.  lea rdi, qword [0x00000764] ; "%d "
|      :|   0x000006ae      b800000000     mov eax, 0
|      :|   0x000006b3      e898feffff     call sym.imp.printf         ; int printf(const char *format)
|      :|   0x000006b8      8345fc01       add dword [input], 1

Легко, да?

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

Источник:
Reverse engineering 32 and 64 bits binaries with Radare2 - 3 (funcs, cases and loops)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх