Трудность понимания логики c в разобранной бинарной бомбе, фаза 3 - PullRequest
8 голосов
/ 07 апреля 2020

У меня есть следующая программа сборки из лаборатории бинарных бомб. Цель состоит в том, чтобы определить ключевое слово, необходимое для запуска двоичного файла без запуска функции explode_bomb. Я прокомментировал свой анализ сборки для этой программы, но у меня возникли проблемы с объединением всех элементов в единое целое.

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

Ниже приведена сама разобранная программа:

0x08048c3c <+0>:     push   %edi
   0x08048c3d <+1>:     push   %esi
   0x08048c3e <+2>:     sub    $0x14,%esp
   0x08048c41 <+5>:     movl   $0x804a388,(%esp)
   0x08048c48 <+12>:    call   0x80490ab <string_length>
   0x08048c4d <+17>:    add    $0x1,%eax
   0x08048c50 <+20>:    mov    %eax,(%esp)
   0x08048c53 <+23>:    call   0x8048800 <malloc@plt>
   0x08048c58 <+28>:    mov    $0x804a388,%esi
   0x08048c5d <+33>:    mov    $0x13,%ecx
   0x08048c62 <+38>:    mov    %eax,%edi
   0x08048c64 <+40>:    rep movsl %ds:(%esi),%es:(%edi)
   0x08048c66 <+42>:    movzwl (%esi),%edx
   0x08048c69 <+45>:    mov    %dx,(%edi)
   0x08048c6c <+48>:    movzbl 0x11(%eax),%edx
   0x08048c70 <+52>:    mov    %dl,0x10(%eax)
   0x08048c73 <+55>:    mov    %eax,0x4(%esp)
   0x08048c77 <+59>:    mov    0x20(%esp),%eax
   0x08048c7b <+63>:    mov    %eax,(%esp)
   0x08048c7e <+66>:    call   0x80490ca <strings_not_equal>
   0x08048c83 <+71>:    test   %eax,%eax
   0x08048c85 <+73>:    je     0x8048c8c <phase_3+80>
   0x08048c87 <+75>:    call   0x8049363 <explode_bomb>
   0x08048c8c <+80>:    add    $0x14,%esp
   0x08048c8f <+83>:    pop    %esi
   0x08048c90 <+84>:    pop    %edi
   0x08048c91 <+85>:    ret  

Следующий блок содержит мой анализ

  5 <phase_3>
  6 0x08048c3c <+0>:     push   %edi // push value in edi to stack
  7 0x08048c3d <+1>:     push   %esi // push value of esi to stack
  8 0x08048c3e <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
  9 
 10 0x08048c41 <+5>:     movl   $0x804a388,(%esp) // put 0x804a388 into loc esp points to
 11 
 12 0x08048c48 <+12>:    call   0x80490ab <string_length> // check string length, store in eax
 13 0x08048c4d <+17>:    add    $0x1,%eax // increment val in eax by 0x1 (str len + 1) 
 14 // at this point, eax = str_len + 1  = 77 + 1 = 78
 15 
 16 0x08048c50 <+20>:    mov    %eax,(%esp) // get val in eax and put in loc on stack
 17 //**** at this point, 0x804a388 should have a value of 78? ****
 18 
 19 0x08048c53 <+23>:    call   0x8048800 <malloc@plt> // malloc --> base ptr in eax
 20 
 21 0x08048c58 <+28>:    mov    $0x804a388,%esi // 0x804a388 in esi 
 22 0x08048c5d <+33>:    mov    $0x13,%ecx // put 0x13 in ecx (counter register)
 23 0x08048c62 <+38>:    mov    %eax,%edi // put val in eax into edi
 24 0x08048c64 <+40>:    rep movsl %ds:(%esi),%es:(%edi) // repeat 0x13 (19) times
 25 // **** populate malloced memory with first 19 (edit: 76) chars of string at 0x804a388 (this string is 77 characters long)? ****
 26 
 27 0x08048c66 <+42>:    movzwl (%esi),%edx // put val in loc esi points to into edx
***** // at this point, edx should contain the string at 0x804a388?
 28 
 29 0x08048c69 <+45>:    mov    %dx,(%edi) // put val in dx to loc edi points to
***** // not sure what effect this has or what is in edi at this point
 30 0x08048c6c <+48>:    movzbl 0x11(%eax),%edx // edx = [eax + 0x11]
 31 0x08048c70 <+52>:    mov    %dl,0x10(%eax) // [eax + 0x10] = dl
 32 0x08048c73 <+55>:    mov    %eax,0x4(%esp) // [esp + 0x4] = eax
 33 0x08048c77 <+59>:    mov    0x20(%esp),%eax // eax = [esp + 0x20]
 34 0x08048c7b <+63>:    mov    %eax,(%esp) // put val in eax into loc esp points to
***** // not sure what effect these movs have
 35 
 36 // edi --> first arg
 37 // esi --> second arg
 38 // compare value in esi to edi
 39 0x08048c7e <+66>:    call   0x80490ca <strings_not_equal> // store result in eax
 40 0x08048c83 <+71>:    test   %eax,%eax 
 41 0x08048c85 <+73>:    je     0x8048c8c <phase_3+80>
 42 0x08048c87 <+75>:    call   0x8049363 <explode_bomb>
 43 0x08048c8c <+80>:    add    $0x14,%esp
 44 0x08048c8f <+83>:    pop    %esi
 45 0x08048c90 <+84>:    pop    %edi
 46 0x08048c91 <+85>:    ret 

Обновление:

После проверки регистров перед вызовом strings_not_equal я получаю следующее:

eax            0x804d8aa        134535338
ecx            0x0      0
edx            0x76     118
ebx            0xffffd354       -11436
esp            0xffffd280       0xffffd280
ebp            0xffffd2b8       0xffffd2b8
esi            0x804a3d4        134521812
edi            0x804f744        134543172
eip            0x8048c7b        0x8048c7b <phase_3+63>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99

и получаю следующий разобранный псевдокод с использованием Hopper:

enter image description here

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

Ответы [ 2 ]

3 голосов
/ 07 апреля 2020

Функция создает модифицированную копию строки из хранилища stati c в буфере с ошибками.


Это выглядит странно. Размер malloc зависит от strlen + 1, но размер memcpy является константой времени компиляции? Ваша декомпиляция, очевидно, показывает, что адрес был строковым литералом, поэтому кажется, что все в порядке.

Возможно, пропущенная оптимизация произошла из-за пользовательской функции string_length(), которая, возможно, была определена только в другом .c (а бомба была скомпилировано без оптимизации времени компоновки для встраивания между файлами). Так что size_t len = string_length("some string literal"); не является константой времени компиляции, и компилятор отправил ей вызов вместо возможности использовать известную постоянную длину строки.

Но, вероятно, они использовали strcpy в источнике и компилятор сделал это как rep movs. Поскольку он, очевидно, копируется из строкового литерала, длина является константой времени компиляции, и она может оптимизировать ту часть работы, которую обычно выполняет strcpy. Обычно, если вы уже вычислили длину, лучше использовать memcpy вместо того, чтобы strcpy вычислять ее снова на лету, но в этом случае это фактически помогло компилятору создать лучший код для этой части, чем если бы они прошли возвращаемое значение от string_length до memcpy, опять же, потому что string_length не может быть встроен и оптимизирован.


   <+0>:     push   %edi // push value in edi to stack
   <+1>:     push   %esi // push value of esi to stack
   <+2>:     sub    $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)

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

Ваш комментарий к sub лучше; да, увеличение стека - это более высокий уровень semanti c, означающий здесь. Эта функция резервирует некоторое место для локальных пользователей (и для сохранения аргументов функции с mov вместо push ed).


rep movsd копий 0x13 * 4 байты, увеличивая ESI и EDI, чтобы указывать после конца скопированной области. Таким образом, другая инструкция movsd будет копировать еще 4 байта, смежные с предыдущей копией.

Код фактически копирует еще 2, но вместо использования movsw он использует загрузку слов movzw и mov хранить. Это позволяет скопировать в общей сложности 78 байтов.

  ...
      # at this point EAX = malloc return value which I'll call buf
<+28>:    mov    $0x804a388,%esi            # copy src = a string literal in .rodata?
<+33>:    mov    $0x13,%ecx
<+38>:    mov    %eax,%edi                  # copy dst = buf
<+40>:    rep movsl %ds:(%esi),%es:(%edi)   # memcpy 76 bytes and advance ESI, EDI

<+42>:    movzwl (%esi),%edx
<+45>:    mov    %dx,(%edi)        # copy another 2 bytes (not moving ESI or EDI)
 # final effect: 78-byte memcpy

На некоторых (но не на всех) процессорах было бы эффективно использовать rep movsb или rep movsw с соответствующим количеством , но это не то, что компилятор выбрал в этом случае. movzx aka AT & T movz - это хороший способ выполнять узкие нагрузки без частичной регистрации штрафов. Вот почему это делают компиляторы, поэтому они могут записать полный регистр, даже если они будут считывать только младшие 8 или 16 бит этого регистра с помощью инструкции сохранения.

После этой копии строковый литерал в buf, у нас есть байт загрузки / хранения, который копирует символ с buf. Помните, что в этот момент EAX все еще указывает на buf, возвращаемое значение malloc. Таким образом, он создает модифицированную копию строкового литерала.

<+48>:    movzbl 0x11(%eax),%edx
<+52>:    mov    %dl,0x10(%eax)             # buf[16] = buf[17]

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

Затем он сохраняет указатели в качестве аргументов стека для сравнения строк.

<+55>:    mov    %eax,0x4(%esp)               # 2nd arg slot = EAX = buf
<+59>:    mov    0x20(%esp),%eax              #  function arg = user input?
<+63>:    mov    %eax,(%esp)                  # first arg slot = our incoming stack arg
<+66>:    call   0x80490ca <strings_not_equal>

Как "обмануть": просмотр результата выполнения с помощью GDB

Некоторые бомбовые лаборатории позволяют запускать бомбу только онлайн, на тестовом сервере, который регистрирует взрывы. Вы не можете запустить его под GDB, используйте только разборку stati c (например, objdump -drwC -Mintel). Таким образом, тестовый сервер может записывать, сколько неудачных попыток у вас было. например, как CS 3330 на cs.virginia.edu , который я обнаружил в Google, где для полного кредита требуется менее 20 взрывов.

Использование GDB для проверки памяти / регистров частично проходит через функцию, делает это гораздо проще, чем работать только из анализа stati c, фактически тривиализируя эту функцию, когда единственный вход проверяется только в самом конце. например, просто посмотрите, какой другой аргумент передается strings_not_equal. (Особенно если вы используете команды GDB jump или set $pc = ... для пропуска проверок взрыва бомбы.)

Установите точку останова или пошаговую установку непосредственно перед вызовом strings_not_equal. Используйте p (char*)$eax, чтобы трактовать EAX как char* и показывать строку (11-кратную) C, начинающуюся с этого адреса. На этом этапе EAX хранит адрес буфера, как вы можете видеть из хранилища в стек.

Скопируйте / вставьте этот результат строки, и все готово.

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

2 голосов
/ 07 апреля 2020

rep movsl копирует 32-битные длинные слова из адреса %esi в адрес %edi, увеличивая оба на 4 каждый раз, количество раз равное %ecx. Думайте об этом как memcpy(edi, esi, ecx*4).

См. https://felixcloutier.com/x86/movs: movsb: movsw: movsd: movsq (это movsd в нотации Intel).

Так что это копирование 19*4=76 байт.

...