Рука об руку, чтобы научить вас Лаборатория бомб
Инструкции по чтению: Есть много онлайн-руководств по версии bomb с открытым исходным кодом для учебников, поддерживающих CMU.Например, две статьи моего научного руководителя написаны довольно хорошо.
Глубокое понимание компьютерных систем (CS:APP) - подробное объяснение Bomb Lab, Глубокое понимание экспериментального отчета компьютерной системы BombLab. Я также не хочу делать повторяющуюся работу, чтобы написать то, что уже написали другие. Этот блог основан на бомбовой версии, присланной мне школой. Соответствующие исполняемые файлы будут загружены на мой github после окончания эксперимента. Выше, если вы хотите использовать другую версию бомбы для практики, вы можете использовать мою.
Всего в этом эксперименте 6 этапов и скрытый этап. В данный момент я не проходил скрытый этап. Если учитель сказал, что скрытый этап добавит очков, я могу его добавить ^_^! PS: Написание блога занимает больше времени времени, чем проводить эксперимент. QwQ
Сначала перенесите экспериментальное введение выше в инструкцию:
В этой лабораторной работе вы будете использовать свои знания курса, чтобы демонтировать «бинарные бомбы», чтобы улучшить свое понимание принципов и навыков в представлении программ на машинном уровне, языке ассемблера, отладчиках и обратном проектировании.
«Бинарные бомбы» (бинарные бомбы, далее именуемые бомбами) — это исполняемая программа на языке C для Linux, которая содержит 6 фаз (фаза 1–фаза 6). Каждый этап операции с бомбой требует от вас ввода определенной строки. Если ваш ввод соответствует вводу, ожидаемому программой, бомба на этом этапе «обезвреживается», в противном случае бомба «взрывается» и выводит слова «БУМ!! !" . Цель эксперимента - обезвредить как можно больше уровней бомб.
Каждая фаза бомбы исследует различные аспекты программы на машинном языке с возрастающей сложностью:
* Фаза 1: сравнение строк
* Фаза 2: Цикл
* Фаза 3: Условия / ветвь
* Этап 4: рекурсивный вызов и стек
* Этап 5: Указатели
* Этап 6: связанный список/указатель/структура
Также есть скрытый этап, но он появляется только в том случае, если вы добавляете определенную строку к решению на этапе 4.
Чтобы выполнить задачу по уничтожению двоичной бомбы, вам необходимо использовать отладчик gdb и objdump для дизассемблирования исполняемого файла бомбы, а также пошаговой трассировки и отладки каждого этапа машинного кода, понимания поведения или функции каждой сборки. код языка, а затем попытаться «вывести» целевую строку, необходимую для обезвреживания бомбы. Для этого может потребоваться установить точки останова перед начальным кодом каждого этапа и перед функцией, которая взрывает бомбу, для облегчения отладки.
Экспериментальный язык: язык C
Экспериментальная среда: Linux
Затем сделайте краткое описание некоторых файлов, используемых в эксперименте:
-
бомба: исполняемая программа бомбы.
-
bomb.c: основная функция программы-бомбы.
"беги ./бомба":./bomb
Это исполняемая программа, для которой требуется 0 или 1 параметр командной строки (подробности см. в функции main() в исходном файле bomb.c). Если вы не укажете параметр, то программа распечатает приветственную информацию, я жду вас, используя строку бомбы в каждой фазе, и определяет, основаны ли вы на соответствующем этапе или взрыве бомбы в соответствии с вашим миссия текущей входной строки не удалась.
Вы также можете организовать строку бомб каждой стадии в текстовый файл (например: result.txt) построчно, а затем передать ее программе в качестве единственного аргумента командной строки при запуске программы (./bomb result.txt) , программа автоматически прочитает строки в текстовом файле и по очереди проверит строки, соответствующие каждому этапу, чтобы определить успех или неудачу подрыва бомбы.
текст начинается
Сначала поместите исполняемый файлbomb
Чтобы разобрать:
objdump -d bomb > disassemble.asm
Затем откройте файл в своем любимом редакторе, я использовалVsCode
Вы можете установить этот плагин, чтобы выделить код
Основная функция относительно длинная, во всяком случае, я ее не видел, напрямуюCtrl + F
поискphase_1
войти в этап 1
первый этап
Фаза 1 относительно проста, и темы, которые мне были назначены, следующие:
08048b33 <phase_1>:
8048b33: 83 ec 14 sub $0x14,%esp
8048b36: 68 a4 9f 04 08 push $0x8049fa4
8048b3b: ff 74 24 1c pushl 0x1c(%esp)
8048b3f: e8 5e 04 00 00 call 8048fa2 <strings_not_equal>
8048b44: 83 c4 10 add $0x10,%esp
8048b47: 85 c0 test %eax,%eax
8048b49: 74 05 je 8048b50 <phase_1+0x1d>
8048b4b: e8 49 05 00 00 call 8049099 <explode_bomb>
8048b50: 83 c4 0c add $0xc,%esp
8048b53: c3 ret
Давайте проанализируем этот код, мы увидим, что есть две операции push, а затем есть операция вызова, так что предположим, что эти две операции push предназначены для этого.strings_not_equal
Параметры передачи функций, мы можем использоватьgdb
Отладьте, чтобы увидеть, так ли это:
gdb bomb
b phase_1 //打断点
b explode_bomb
run
layout asm //提供图形界面, 可以更好的观察执行到哪一条语句了
моя входная строкаhello
, а затем вы можете увидеть в процессе отладки, что строка, помещенная в стек с помощью 0x1c(%esp),hello
$1 = 0x804c3e0 <input_strings> "hello"
Таким образом, мы можем предположить, что наша входная строка должна соответствовать0x8049fa4
Сравните строки по этому адресу. Если они равны, бомба может быть успешно обезврежена. Теперь проверьте строки по этому адресу.
x /s 0x8049fa4
После выполнения получается следующий вывод:All your base are belong to us.
правильный ответ
(gdb) x /s 0x8049fa4
0x8049fa4: "All your base are belong to us."
В целом, эта задача достаточно проста.
второй этап
На этапе 2 проверяется цикл, и объем кода намного больше, чем на этапе 1. Сначала вставьте полный код этапа 2.
08048b54 <phase_2>:
8048b54: 56 push %esi
8048b55: 53 push %ebx
8048b56: 83 ec 2c sub $0x2c,%esp
8048b59: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048b5f: 89 44 24 24 mov %eax,0x24(%esp)
8048b63: 31 c0 xor %eax,%eax
8048b65: 8d 44 24 0c lea 0xc(%esp),%eax
8048b69: 50 push %eax
8048b6a: ff 74 24 3c pushl 0x3c(%esp)
8048b6e: e8 4b 05 00 00 call 80490be <read_six_numbers>
8048b73: 83 c4 10 add $0x10,%esp
8048b76: 83 7c 24 04 01 cmpl $0x1,0x4(%esp)
8048b7b: 74 05 je 8048b82 <phase_2+0x2e>
8048b7d: e8 17 05 00 00 call 8049099 <explode_bomb>
8048b82: 8d 5c 24 04 lea 0x4(%esp),%ebx
8048b86: 8d 74 24 18 lea 0x18(%esp),%esi
8048b8a: 8b 03 mov (%ebx),%eax
8048b8c: 01 c0 add %eax,%eax
8048b8e: 39 43 04 cmp %eax,0x4(%ebx)
8048b91: 74 05 je 8048b98 <phase_2+0x44>
8048b93: e8 01 05 00 00 call 8049099 <explode_bomb>
8048b98: 83 c3 04 add $0x4,%ebx
8048b9b: 39 f3 cmp %esi,%ebx
8048b9d: 75 eb jne 8048b8a <phase_2+0x36>
8048b9f: 8b 44 24 1c mov 0x1c(%esp),%eax
8048ba3: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048baa: 74 05 je 8048bb1 <phase_2+0x5d>
8048bac: e8 df fb ff ff call 8048790 <__stack_chk_fail@plt>
8048bb1: 83 c4 24 add $0x24,%esp
8048bb4: 5b pop %ebx
8048bb5: 5e pop %esi
8048bb6: c3 ret
Первые несколько строк — это какая-то подготовительная работа, вам не нужно изучать, что она делает, и я не хочу это изучать.
Начиная со строки 8 ниже, в строке 8 используетсяlea
Заявление, это получается0xc(%esp)
Адрес, а затем нажмите этот адрес на стек. Можно догадаться, что этот адрес используется для хранения 6 числа числа, и я точно знаю, для чего следующий pushl. Затем позвонитеread_six_numbers
Функция считывает 6 чисел.
строка 13,cmpl $0x1,0x4(%esp)
Этот оператор сравнивает отношение размера между первым прочитанным параметром и 1. Что касается того, почему первый параметр является первым параметром, вы можете узнать это, моделируя push-pop стека.Первый параметр здесь должен быть равен 1, иначе бомба будет взорвана Затем перейдите к строке 16, чтобы выполнить.
Строки 16 и 17 хранят указатели на первое и шестое числа в регистрах ebx и esi соответственно, откуда видно, что значение регистра esi можно рассматривать как условие завершения цикла.
Строки 18, 19 и вторая удваивают число в регистре ebx и помещают его в регистр eax, а затем сравнивают, равно ли следующее число в регистре ebx значению в регистре eax, то есть после сравнения Является ли число вдвое больше предыдущего числа, если нет, то взорвите бомбу, иначе цикл продолжается, указатель перемещается назад на одно место, пока не переместится на последнее.
Итак, окончательный ответ1 2 4 8 16 32
, эта задача относительно проста
третий этап
Этап 3 исследует условия и переходы, которые относительно просты.Давайте сначала посмотрим на исходный код этапа 3.
8048bb7: 83 ec 1c sub $0x1c,%esp
8048bba: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048bc0: 89 44 24 0c mov %eax,0xc(%esp)
8048bc4: 31 c0 xor %eax,%eax
8048bc6: 8d 44 24 08 lea 0x8(%esp),%eax
8048bca: 50 push %eax
8048bcb: 8d 44 24 08 lea 0x8(%esp),%eax
8048bcf: 50 push %eax
8048bd0: 68 6f a1 04 08 push $0x804a16f
8048bd5: ff 74 24 2c pushl 0x2c(%esp)
8048bd9: e8 32 fc ff ff call 8048810 <__isoc99_sscanf@plt>
8048bde: 83 c4 10 add $0x10,%esp
8048be1: 83 f8 01 cmp $0x1,%eax
8048be4: 7f 05 jg 8048beb <phase_3+0x34>
8048be6: e8 ae 04 00 00 call 8049099 <explode_bomb>
8048beb: 83 7c 24 04 07 cmpl $0x7,0x4(%esp)
8048bf0: 77 3c ja 8048c2e <phase_3+0x77>
8048bf2: 8b 44 24 04 mov 0x4(%esp),%eax
8048bf6: ff 24 85 00 a0 04 08 jmp *0x804a000(,%eax,4)
8048bfd: b8 f3 01 00 00 mov $0x1f3,%eax
8048c02: eb 3b jmp 8048c3f <phase_3+0x88>
8048c04: b8 c0 00 00 00 mov $0xc0,%eax
8048c09: eb 34 jmp 8048c3f <phase_3+0x88>
8048c0b: b8 57 01 00 00 mov $0x157,%eax
8048c10: eb 2d jmp 8048c3f <phase_3+0x88>
8048c12: b8 9b 01 00 00 mov $0x19b,%eax
8048c17: eb 26 jmp 8048c3f <phase_3+0x88>
8048c19: b8 5c 03 00 00 mov $0x35c,%eax
8048c1e: eb 1f jmp 8048c3f <phase_3+0x88>
8048c20: b8 38 02 00 00 mov $0x238,%eax
8048c25: eb 18 jmp 8048c3f <phase_3+0x88>
8048c27: b8 67 01 00 00 mov $0x167,%eax
8048c2c: eb 11 jmp 8048c3f <phase_3+0x88>
8048c2e: e8 66 04 00 00 call 8049099 <explode_bomb>
8048c33: b8 00 00 00 00 mov $0x0,%eax
8048c38: eb 05 jmp 8048c3f <phase_3+0x88>
8048c3a: b8 83 02 00 00 mov $0x283,%eax
8048c3f: 3b 44 24 08 cmp 0x8(%esp),%eax
8048c43: 74 05 je 8048c4a <phase_3+0x93>
8048c45: e8 4f 04 00 00 call 8049099 <explode_bomb>
8048c4a: 8b 44 24 0c mov 0xc(%esp),%eax
8048c4e: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048c55: 74 05 je 8048c5c <phase_3+0xa5>
8048c57: e8 34 fb ff ff call 8048790 <__stack_chk_fail@plt>
8048c5c: 83 c4 1c add $0x1c,%esp
8048c5f: c3 ret
Начиная со строки 5, две операции lea здесь, очевидно, являются передачей адресов, подобно &a, и затем используются функцией sacnf.
Обратите внимание на строку 9 здесьpush $0x804a16f
, здесь нажимается адресная константа.По вызываемому позже scanf догадываемся, что это должна быть строка формата.Проверяем значение этой строки через gdb
(gdb) x /s 0x804a16f
0x804a16f: "%d %d"
Как видно из вывода, scanf принимает два целых числа типа int.
Затем посмотрите на строку 13, там есть операция сравнения между регистром eax и 1. Как правило, регистр eax хранит возвращаемое значение функции, а возвращаемое значение функции sacnf — это количество успешно прочитанных элементов, поэтому она может видно, что количество элементов читается здесь. Если оно меньше или равно 1, бомба будет взорвана.
Затем посмотрите на строку 16, которая предназначена для сравнения отношения размера между первым прочитанным параметром и 7. Обратите внимание, что ja в следующей строке является беззнаковым сравнением, поэтому видно, что значение 1 первого параметра должно быть между 0 и 7, иначе будут детонировать бомбы
Следующая строка 18, 19, интересное место, здесь реализация типичной таблицы переходов, такой как таблица векторов прерываний, в этой идее используется переключатель, например, первое число, которое я ввожу, равно 1, затем адрес Я перехожу к тому, что нужно получить в gdb следующим образом:
(gdb) print /x *(int*)(0x804a000+4)
$1 = 0x8048bfd
Затем найдите соответствующий адрес в строках 19–36 и перейдите к нему, например, 0x8048bfd — это 20-я строка, а соответствующий код —
8048bfd: b8 f3 01 00 00 mov $0x1f3,%eax
8048c02: eb 3b jmp 8048c3f <phase_3+0x88>
Таким образом, значение eax равно 0x1f3, что равно 499.
Передайте код в строке 38
8048c3f: 3b 44 24 08 cmp 0x8(%esp),%eax
8048c43: 74 05 je 8048c4a <phase_3+0x93>
8048c45: e8 4f 04 00 00 call 8049099 <explode_bomb>
Можно известно, что второе число, которое нам нужно ввести, равно стоимости в регистре EAX, в противном случае бомба будет взорваться, поэтому один из возможных ответов - 1 499, конечно, есть и другие ответы.
Четвертый этап
Четвертый этап требует, чтобы вы перевернули соответствующий код C, что будет намного проще решить. Я скоро переверну код C для этой проблемы, но я не ожидал, что функция scanf будет ямкой. Ее первый параметр - ваш input Второй, второй параметр - это первый введенный вами, поэтому я всегда думал, что делаю неправильный реверс.Позже, под напоминанием Че Даниу, пошаговая отладка наконец-то нашла этот жалкий BUG
Позвольте мне рассказать вам, как я изменил его и как я его отладил.
Во-первых, давайте вставим код фазы_4 следующим образом:
08048ca3 <phase_4>:
8048ca3: 83 ec 1c sub $0x1c,%esp
8048ca6: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048cac: 89 44 24 0c mov %eax,0xc(%esp)
8048cb0: 31 c0 xor %eax,%eax
8048cb2: 8d 44 24 04 lea 0x4(%esp),%eax
8048cb6: 50 push %eax
8048cb7: 8d 44 24 0c lea 0xc(%esp),%eax
8048cbb: 50 push %eax
8048cbc: 68 6f a1 04 08 push $0x804a16f
8048cc1: ff 74 24 2c pushl 0x2c(%esp)
8048cc5: e8 46 fb ff ff call 8048810 <__isoc99_sscanf@plt>
8048cca: 83 c4 10 add $0x10,%esp
8048ccd: 83 f8 02 cmp $0x2,%eax
8048cd0: 75 0c jne 8048cde <phase_4+0x3b>
8048cd2: 8b 44 24 04 mov 0x4(%esp),%eax
8048cd6: 83 e8 02 sub $0x2,%eax
8048cd9: 83 f8 02 cmp $0x2,%eax
8048cdc: 76 05 jbe 8048ce3 <phase_4+0x40>
8048cde: e8 b6 03 00 00 call 8049099 <explode_bomb>
8048ce3: 83 ec 08 sub $0x8,%esp
8048ce6: ff 74 24 0c pushl 0xc(%esp)
8048cea: 6a 09 push $0x9
8048cec: e8 6f ff ff ff call 8048c60 <func4>
8048cf1: 83 c4 10 add $0x10,%esp
8048cf4: 3b 44 24 08 cmp 0x8(%esp),%eax
8048cf8: 74 05 je 8048cff <phase_4+0x5c>
8048cfa: e8 9a 03 00 00 call 8049099 <explode_bomb>
8048cff: 8b 44 24 0c mov 0xc(%esp),%eax
8048d03: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048d0a: 74 05 je 8048d11 <phase_4+0x6e>
8048d0c: e8 7f fa ff ff call 8048790 <__stack_chk_fail@plt>
8048d11: 83 c4 1c add $0x1c,%esp
8048d14: c3 ret
Начиная со строки 6, вот знакомый lea, знакомый push, знакомый scanf, а затем с тем же трюком вы можете узнать, является ли строка формата%d %d
Итак, мы знаем, что здесь два числа также читаются, но тот, который с низким адресом - это тот, который выталкивается сразу ...
14 строк сравнили возвращаемое значение, а не 2, взорвали бомбу и еще раз доказали два числа
Строки 16-18 сравнивают первый параметр scanf, то есть отношение размера между вторым введенным числом и 2 после вычитания 2. Здесь нам нужно обратить особое внимание на строку 19 — это jbe, которая представляет собой сравнение беззнаковых чисел, поэтому мы вводим значение второго параметра должно быть между 2 и 4.
Строки 21–23 подготавливают параметры для вызова func 4. После просмотра исходного кода func 4 мы можем знать, что параметры справа передаются первыми.
Предположим, что мы вводим два числа ba, тогда вызов функции здесь func4(9, a).Что касается того, почему вы можете рисовать стек здесь, вы знаете, почему это слишком хлопотно рисовать на компьютере, поэтому я не буду нарисуйте его (да, когда вы анализируете стек самостоятельно, вы должны обратить внимание на то, что адрес возврата будет помещен в стек при вызове функции)
Давайте сначала не будем смотреть на func4, а затем посмотрим на Phase_4.Из строк с 26 по 28 мы видим, что значение первого параметра, который нам нужно ввести, равно возвращаемому значению после вызова func4, иначе бомба взорвется, поэтому , далее, обращаем func4
Сначала перейдите к исходному коду func4:
08048c60 <func4>:
8048c60: 57 push %edi
8048c61: 56 push %esi
8048c62: 53 push %ebx
8048c63: 8b 5c 24 10 mov 0x10(%esp),%ebx
8048c67: 8b 7c 24 14 mov 0x14(%esp),%edi
8048c6b: 85 db test %ebx,%ebx
8048c6d: 7e 2b jle 8048c9a <func4+0x3a>
8048c6f: 89 f8 mov %edi,%eax
8048c71: 83 fb 01 cmp $0x1,%ebx
8048c74: 74 29 je 8048c9f <func4+0x3f>
8048c76: 83 ec 08 sub $0x8,%esp
8048c79: 57 push %edi
8048c7a: 8d 43 ff lea -0x1(%ebx),%eax
8048c7d: 50 push %eax
8048c7e: e8 dd ff ff ff call 8048c60 <func4>
8048c83: 83 c4 08 add $0x8,%esp
8048c86: 8d 34 07 lea (%edi,%eax,1),%esi
8048c89: 57 push %edi
8048c8a: 83 eb 02 sub $0x2,%ebx
8048c8d: 53 push %ebx
8048c8e: e8 cd ff ff ff call 8048c60 <func4>
8048c93: 83 c4 10 add $0x10,%esp
8048c96: 01 f0 add %esi,%eax
8048c98: eb 05 jmp 8048c9f <func4+0x3f>
8048c9a: b8 00 00 00 00 mov $0x0,%eax
8048c9f: 5b pop %ebx
8048ca0: 5e pop %esi
8048ca1: 5f pop %edi
8048ca2: c3 ret
Первые несколько строк готовятся к вызову функции, а не анализируются.
Строки 5 и 6 берут параметры из стека.Если вы сами проанализируете предыдущий стек, то тут не сложно проанализировать, что ebx ставит первый параметр x1, а edi ставит второй параметр x2.
Функция 7-й и 8-й строк состоит в том, чтобы судить, равен ли x1 ≤ 0. После анализа нетрудно найти код языка C, соответствующий этим ассемблерным кодам.Примечание: регистр eax хранит возвращаемое значение функции
if (x1 <= 0) return 0;
Затем девятая строка присваивает регистру eax значение x2, а затем выносит решение о x1.Если он равен 0, то для выполнения выполняется переход к 27-й строке, поэтому мы можем обратить следующий код языка C:
if (x1 == 1) return x2;
Следующий звонокfunc4(x1 - 1, x2)
, Из строки 18 видно, что возвращаемое значение плюс x2 присваивается esi
Линии 19-21 готовит параметры для вызова функции Func4. После небольшого анализа вы можете знать, что следующий звонокfunc4(x1 -2, x2)
Наконец, из строки 24 видно, что возвращаемое значение второго вызова func4 добавляется к значению esi для получения окончательного возвращаемого значения, поэтому общий код обратной программы выглядит следующим образом:
int func(int x1, int x2) {
if (x1 <= 0) return 0;
if (x1 == 1) return x2;
return func(x1 - 1, x2) + x1 + func(x1 - 2, x2);
}
Очень элегантный.
Конечно, если вам немного сложно понять вышеизложенное, то могу привести еще один тупой, но понятный код, мы заметили, что значение регистра eax не сохраняется во время вызова, так что можно подумать это как глобальная переменная, пусть это будет переменная eax, так что у вас есть следующая программа, которая точно соответствует уродливой программе C оператора сборки:
int eax;
void func(int x1, int x2) {
if (x1 <= 0) {
eax = 0;
return;
}
eax = x2;
if (x1 == 1) return;
eax = x1 - 1;
func(eax, x2);
int temp = x2 + eax;
x1 -= 2;
func(x1, x2);
eax += temp;
return;
}
int main() {
printf("%d", eax);
}
Если вы внимательно сравните, то увидите, что поведение каждого оператора этой программы соответствует сборке один к одному, что может быть проще для понимания.
Окончательный результат 264 3
Поговорим о том, как я отлаживал, когда меня в начале стравливал scanf.
Сначала gdb заходит в отладочный интерфейс, потом бьет брейкпойнт на expire_bomb, а брейкпойнт на Phase_4, потом пошаговая отладка, а потом наблюдайте значение регистра и где он взорвался, ну я чушь говорю, нет Как для отладки таким образом?Просто посмотрите документацию по командам, связанным с gdb, не нужно запоминать их.
Этап пятый
Вы могли заметить, что у меня не было комментариев к предыдущим программам, потому что мне действительно не нужны были комментарии, но начиная с Фазы 5, я начал писать комментарии, иначе я бы не знал, что я смотрю
Впрочем, сложность пятого этапа неплохая, я прошел его менее чем за час.
Исходный код первого пятого этапа:
08048d15 <phase_5>:
8048d15: 53 push %ebx
8048d16: 83 ec 14 sub $0x14,%esp
8048d19: 8b 5c 24 1c mov 0x1c(%esp),%ebx
8048d1d: 53 push %ebx
8048d1e: e8 60 02 00 00 call 8048f83 <string_length>
8048d23: 83 c4 10 add $0x10,%esp
8048d26: 83 f8 06 cmp $0x6,%eax
8048d29: 74 05 je 8048d30 <phase_5+0x1b>
8048d2b: e8 69 03 00 00 call 8049099 <explode_bomb>
8048d30: 89 d8 mov %ebx,%eax;eax指向了字符串的开始
8048d32: 83 c3 06 add $0x6,%ebx;ebx现在指向了字符串的结尾的后一个,象征着结束标志
8048d35: b9 00 00 00 00 mov $0x0,%ecx
8048d3a: 0f b6 10 loop: movzbl (%eax),%edx;取出了eax指向的那个字符,放到了edx中,而且是0扩展
8048d3d: 83 e2 0f and $0xf,%edx;将低四位保留,高四位清零了
8048d40: 03 0c 95 20 a0 04 08 add 0x804a020(,%edx,4),%ecx;这个有点switch的感觉
8048d47: 83 c0 01 add $0x1,%eax
8048d4a: 39 d8 cmp %ebx,%eax
8048d4c: 75 ec jne 8048d3a <phase_5+0x25>
8048d4e: 83 f9 2c cmp $0x2c,%ecx;需要ecx加6次等于0x2c 10 + 10 + 10 + 10 + 2 + 2
8048d51: 74 05 je 8048d58 <phase_5+0x43>
8048d53: e8 41 03 00 00 call 8049099 <explode_bomb>
8048d58: 83 c4 08 add $0x8,%esp
8048d5b: 5b pop %ebx
8048d5c: c3 ret
Этот вопрос исследует указатели, и я не знаю, куда я указываю, не комментируя.
Сначала взгляните на строку 6, это имя функции предназначено для быстрого вычисления длины строки, и эта строка должна быть строкой, которую мы потеряли, не спрашивайте меня, почему, человеческая интуиция, она так точна.
Строка 8, по сравнению с размером 6 eax, значение eax является возвращаемым значением функции string_length, не верьте, вы можете отладить свой собственный вид, поэтому отсюда мы можем получить важное сообщение, длина строки 6
Кстати, в строке 5 тоже есть важная информация. Этот оператор push явно подготавливает параметры для вызова функции string_length, поэтому указатель на первый символ строки хранится в регистре ebx.
Затем присваивание в строке 11 заставляет eax также указывать на начало строки.Поскольку длина строки равна 6, из строки 12 видно, что она указывает на последнюю единицу в конце строки, что, очевидно, то, что мы обычно используем.str[i] != 0
знак окончания
14 строк и 15 строк комментариев очень понятно, то есть некоторая обработка символов
16-я строка - добавление ecx к значению по соответствующему адресу. Здесь снова предыдущая таблица переходов недоступна. Мы можем посмотреть значение по этому адресу в gdb.
(gdb) print (int)*(0x804a020)
$2 = 2
(gdb) print (int)*(0x804a024)
$3 = 10
(gdb) print (int)*(0x804a028)
$4 = 6
(gdb) print (int)*(0x804a02c)
$5 = 1
(gdb) print (int)*(0x804a030)
$6 = 12
(gdb) print (int)*(0x804a034)
$7 = 16
(gdb) print (int)*(0x804a038)
$8 = 9
(gdb) print (int)*(0x804a03c)
$9 = 3
(gdb) ...
Из строк с 17 по 19 мы можем знать, что нам нужно добавить всего 6 раз.
В строке 20 мы видим, что значение после добавления 6 раз равно 0x2c
Таким образом, один из возможных исходов — 10 + 10 + 10 + 10 + 2 + 2.
10 означает, что младшие четыре бита соответствующего символа равны 1, а 2 означает, что значение младших четырех битов равно 0. Это получается из соотношения между адресом таблицы переходов и соответствующим значением, поэтому возможный ответ: 111100.
Наконец, когда дело дошло до самого сложного вопроса в этом эксперименте, я написал много заметок, чтобы разобраться в этом вопросе, на этот вопрос у меня ушло около двух часов.
этап шестой
Код для этого вопроса огромен, если бы не задание, я бы не делал это QwQ
08048d5d <phase_6>: 8048d5d: 56 push %esi 8048d5e: 53 push %ebx 8048d5f: 83 ec 4c sub $0x4c,%esp 8048d62: 65 a1 14 00 00 00 mov %gs:0x14,%eax 8048d68: 89 44 24 44 mov %eax,0x44(%esp) 8048d6c: 31 c0 xor %eax,%eax 8048d6e: 8d 44 24 14 lea 0x14(%esp),%eax 8048d72: 50 push %eax 8048d73: ff 74 24 5c pushl 0x5c(%esp) 8048d77: e8 42 03 00 00 call 80490be <read_six_numbers> 8048d7c: 83 c4 10 add $0x10,%esp 8048d7f: be 00 00 00 00 mov $0x0,%esi 8048d84: 8b 44 b4 0c mov 0xc(%esp,%esi,4),%eax 8048d88: 83 e8 01 sub $0x1,%eax 8048d8b: 83 f8 05 cmp $0x5,%eax 8048d8e: 76 05 jbe 8048d95 <phase_6+0x38> 8048d90: e8 04 03 00 00 call 8049099 <explode_bomb> 8048d95: 83 c6 01 add $0x1,%esi;这里就是限定了数的取值范围 8048d98: 83 fe 06 cmp $0x6,%esi 8048d9b: 74 1b je 8048db8 <phase_6+0x5b> 8048d9d: 89 f3 mov %esi,%ebx 8048d9f: 8b 44 9c 0c mov 0xc(%esp,%ebx,4),%eax 8048da3: 39 44 b4 08 cmp %eax,0x8(%esp,%esi,4) 8048da7: 75 05 jne 8048dae <phase_6+0x51> 8048da9: e8 eb 02 00 00 call 8049099 <explode_bomb> 8048dae: 83 c3 01 add $0x1,%ebx;这里限定了数字两两不能相等 8048db1: 83 fb 05 cmp $0x5,%ebx 8048db4: 7e e9 jle 8048d9f <phase_6+0x42> 8048db6: eb cc jmp 8048d84 <phase_6+0x27> 8048db8: 8d 44 24 0c lea 0xc(%esp),%eax;指向第1个数 8048dbc: 8d 5c 24 24 lea 0x24(%esp),%ebx;指向第6个数的后一个,也就是end标志 8048dc0: b9 07 00 00 00 mov $0x7,%ecx 8048dc5: 89 ca mov %ecx,%edx;an = 7 - an, 后面记得还要再变回来 8048dc7: 2b 10 sub (%eax),%edx 8048dc9: 89 10 mov %edx,(%eax) 8048dcb: 83 c0 04 add $0x4,%eax;指向下一个 8048dce: 39 c3 cmp %eax,%ebx 8048dd0: 75 f3 jne 8048dc5 <phase_6+0x68>;;
8048dd2: bb 00 00 00 00 mov $0x0,%ebx 8048dd7: eb 16 jmp 8048def <phase_6+0x92> 8048dd9: 8b 52 08 mov 0x8(%edx),%edx;大于1跳到这里, 这里目测是链表的 p = p -> next操作 8048ddc: 83 c0 01 add $0x1,%eax 8048ddf: 39 c8 cmp %ecx,%eax 8048de1: 75 f6 jne 8048dd9 <phase_6+0x7c>
8048de3: 89 54 b4 24 mov %edx,0x24(%esp,%esi,4);小于等于1跳到这里,这里的操作就是把链表指针依次放到这6个数的后面 8048de7: 83 c3 01 add $0x1,%ebx 8048dea: 83 fb 06 cmp $0x6,%ebx 8048ded: 74 17 je 8048e06 <phase_6+0xa9>;这里的处理完后,这6个数后面的链表指针的大小1排序就对应着这6个数的排序
8048def: 89 de mov %ebx,%esi 8048df1: 8b 4c 9c 0c mov 0xc(%esp,%ebx,4),%ecx;从输入后处理完的第一个数开始遍历 8048df5: b8 01 00 00 00 mov $0x1,%eax 8048dfa: ba 3c c1 04 08 mov $0x804c13c,%edx;这个是链表的首地址 8048dff: 83 f9 01 cmp $0x1,%ecx;这个数和1进行比较 8048e02: 7f d5 jg 8048dd9 <phase_6+0x7c>;大于跳转 8048e04: eb dd jmp 8048de3 <phase_6+0x86>;小于或者等于跳转 8048e06: 8b 5c 24 24 mov 0x24(%esp),%ebx;这个值就是这6个数后面的第一个链表指针的值 8048e0a: 8d 44 24 24 lea 0x24(%esp),%eax;指向了后面第一个链表指针 8048e0e: 8d 74 24 38 lea 0x38(%esp),%esi;指向最后一个链表指针 8048e12: 89 d9 mov %ebx,%ecx 8048e14: 8b 50 04 mov 0x4(%eax),%edx;指向栈上第二个链表指针 8048e17: 89 51 08 mov %edx,0x8(%ecx);让栈上第一个链表指针指向栈上第二个链表指针 8048e1a: 83 c0 04 add $0x4,%eax 8048e1d: 89 d1 mov %edx,%ecx 8048e1f: 39 c6 cmp %eax,%esi 8048e21: 75 f1 jne 8048e14 <phase_6+0xb7>;这个循环就是让栈上这6个链表节点按栈上面的顺序建立连接关系 8048e23: c7 42 08 00 00 00 00 movl $0x0,0x8(%edx);最后一个指向null 8048e2a: be 05 00 00 00 mov $0x5,%esi 8048e2f: 8b 43 08 mov 0x8(%ebx),%eax 8048e32: 8b 00 mov (%eax),%eax;现在eax指向了第二个节点了 8048e34: 39 03 cmp %eax,(%ebx);比较这两个节点的值? 8048e36: 7d 05 jge 8048e3d <phase_6+0xe0>;前一个需要大于或者等于后面一个 8048e38: e8 5c 02 00 00 call 8049099 <explode_bomb> 8048e3d: 8b 5b 08 mov 0x8(%ebx),%ebx 8048e40: 83 ee 01 sub $0x1,%esi 8048e43: 75 ea jne 8048e2f <phase_6+0xd2> 8048e45: 8b 44 24 3c mov 0x3c(%esp),%eax 8048e49: 65 33 05 14 00 00 00 xor %gs:0x14,%eax 8048e50: 74 05 je 8048e57 <phase_6+0xfa> 8048e52: e8 39 f9 ff ff call 8048790 <__stack_chk_fail@plt> 8048e57: 83 c4 44 add $0x44,%esp 8048e5a: 5b pop %ebx 8048e5b: 5e pop %esi 8048e5c: c3 ret
Я немного устал писать, не хочу вдаваться в подробности, и я написал много комментариев.Обратите внимание, что связанный список, упомянутый в комментариях, относится к той части стека, которая подключена к 6 числа.
Начиная со строки 8, здесь знакомая операция lea, знакомые операции push и call, эта функция считывает шесть чисел по указанному адресу в стеке.
Последний использует много кода — это две очень скучные операции.Во-первых, диапазон значений, определяющих 6 чисел, должен быть 1 ~ 6, а во-вторых, шесть чисел разные.
Затем выполните преобразование
Строка 56 отмечает, что есть операция присвоения адресной константы.Это значение адреса является адресом, по которому находится голова связанного списка.Через gdb мы можем увидеть следующую информацию
Каждый узел занимает 12 байт, первый байт — это значение, второй байт — номер идентификатора, а третий байт — адрес следующего узла.
Далее выполняется операция размещения узлов связного списка на стеке в определенном порядке после 6 номеров, а затем выполняется операция установления отношения связи связного списка на стеке, т. е. следующий узел каждого узла переопределяется.Кто (он последний в стеке), в конце находится основная часть информации, то есть порядок этих 6 узлов от большого к меньшему, поэтому мы можем узнать идентификатор узла в соответствии со значением узла с помощью приведенного выше рисунка. Порядок от большого к меньшему равен 6 2 4 3 1 5, и, наконец, выполнить операцию сокращения an = 7 - an, чтобы получить окончательный ответ 1 5 3 4 6 2!
Заканчиваем цветочную композицию!!!