Функция создает модифицированную копию строки из хранилища 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 обычно не так просты для отладки с помощью отладчика и требуют хотя бы некоторой математики, но фазы связанного списка, требующие наличия последовательности чисел в правильном порядке для обхода списка, также становятся тривиальными, если вы знать, как использовать отладчик для установки регистров для успешного сравнения при их получении.