CSAPP: подробный анализ эксперимента лаборатории бомб

Язык программирования

Поиск в Wechat 🔍"Руководство по программированию", следуйте за этим программистом, который пишет галантерейные товары, ответьте "Ресурсы", вы можете получить обучающие маршруты и книги по внутренней разработке

предисловие

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

Экспериментальные материалы

Это один из экспериментов в классе системного программирования в этом семестре. Он был введен из CMU. Исходный код и материалы могут бытьСайт курса КМУЧтобы получить, выберите загрузку материалов для самостоятельного изучения второго эксперимента. Для проведения этого эксперимента требуется дизассемблирование и отладка. Рекомендуется использовать gdb и objdump. Если вы не знаете gdb, вы можете взглянуть на этоПростое руководство по использованию gdb, о objdumpПросто посмотри на этоВ конце концов, я использовал только одну команду objdump -d filename для этого эксперимента.

Готов к работе

В скачанном декомпрессионном пакете три файла, полезный — исполняемый файл bomb, а также есть bomb.c, позволяющий увидеть весь процесс выполнения программы.bomp.c.pngЭто основная часть основной функции.Вы можете видеть, что программа разделена на 6 фаз, каждая из которых требует от вас ввести строку строк, а затем вызвать функцию Phase_n() соответственно, чтобы определить, следует ли активировать бомбу. Сначала используйте objdump -d bomb > bomb.asm Сохраните разборку в bomb.asm, затем используйте tmux, чтобы открыть разделенный экран, слева — gdb для отладки бомбы.2.pngСначала найдите основную функцию следующим образом:

   00000000000400da0 <main>:
  400da0: 53                    push   %rbx
  400da1: 83 ff 01              cmp    $0x1,%edi
  400da4: 75 10                 jne    400db6 <main+0x16>
  400da6: 48 8b 05 9b 29 20 00  mov    0x20299b(%rip),%rax        # 603748 <stdin@@GLIBC_2.2.5>
  400dad: 48 89 05 b4 29 20 00  mov    %rax,0x2029b4(%rip)        # 603768 <infile>
  400db4: eb 63                 jmp    400e19 <main+0x79>
  400db6: 48 89 f3              mov    %rsi,%rbx
  400db9: 83 ff 02              cmp    $0x2,%edi
  400dbc: 75 3a                 jne    400df8 <main+0x58>
  400dbe: 48 8b 7e 08           mov    0x8(%rsi),%rdi
  400dc2: be b4 22 40 00        mov    $0x4022b4,%esi
  400dc7: e8 44 fe ff ff        callq  400c10 <fopen@plt>
  400dcc: 48 89 05 95 29 20 00  mov    %rax,0x202995(%rip)        # 603768 <infile>
  400dd3: 48 85 c0              test   %rax,%rax
  400dd6: 75 41                 jne    400e19 <main+0x79>
  400dd8: 48 8b 4b 08           mov    0x8(%rbx),%rcx
  400ddc: 48 8b 13              mov    (%rbx),%rdx
  400ddf: be b6 22 40 00        mov    $0x4022b6,%esi
  400de4: bf 01 00 00 00        mov    $0x1,%edi
  400de9: e8 12 fe ff ff        callq  400c00 <__printf_chk@plt>
  400dee: bf 08 00 00 00        mov    $0x8,%edi
  400df3: e8 28 fe ff ff        callq  400c20 <exit@plt>
  400df8: 48 8b 16              mov    (%rsi),%rdx
  400dfb: be d3 22 40 00        mov    $0x4022d3,%esi
  400e00: bf 01 00 00 00        mov    $0x1,%edi
  400e05: b8 00 00 00 00        mov    $0x0,%eax
  400e0a: e8 f1 fd ff ff        callq  400c00 <__printf_chk@plt>
  400e0f: bf 08 00 00 00        mov    $0x8,%edi
  400e14: e8 07 fe ff ff        callq  400c20 <exit@plt>
  400e19: e8 84 05 00 00        callq  4013a2 <initialize_bomb>
  400e1e: bf 38 23 40 00        mov    $0x402338,%edi
  400e23: e8 e8 fc ff ff        callq  400b10 <puts@plt>
  400e28: bf 78 23 40 00        mov    $0x402378,%edi
  400e2d: e8 de fc ff ff        callq  400b10 <puts@plt>
  400e32: e8 67 06 00 00        callq  40149e <read_line>
  400e37: 48 89 c7              mov    %rax,%rdi
  400e3a: e8 a1 00 00 00        callq  400ee0 <phase_1>
  400e3f: e8 80 07 00 00        callq  4015c4 <phase_defused>
  400e44: bf a8 23 40 00        mov    $0x4023a8,%edi
  400e49: e8 c2 fc ff ff        callq  400b10 <puts@plt>
  400e4e: e8 4b 06 00 00        callq  40149e <read_line>
  400e53: 48 89 c7              mov    %rax,%rdi
  400e56: e8 a1 00 00 00        callq  400efc <phase_2>
  400e5b: e8 64 07 00 00        callq  4015c4 <phase_defused>
  400e60: bf ed 22 40 00        mov    $0x4022ed,%edi
  400e65: e8 a6 fc ff ff        callq  400b10 <puts@plt>
  400e6a: e8 2f 06 00 00        callq  40149e <read_line>
  400e6f: 48 89 c7              mov    %rax,%rdi
  400e72: e8 cc 00 00 00        callq  400f43 <phase_3>      
  400e77: e8 48 07 00 00        callq  4015c4 <phase_defused>
  400e7c: bf 0b 23 40 00        mov    $0x40230b,%edi
  400e81: e8 8a fc ff ff        callq  400b10 <puts@plt>
  400e86: e8 13 06 00 00        callq  40149e <read_line>
  400e8b: 48 89 c7              mov    %rax,%rdi
  400e8e: e8 79 01 00 00        callq  40100c <phase_4>
  400e93: e8 2c 07 00 00        callq  4015c4 <phase_defused>
  400e98: bf d8 23 40 00        mov    $0x4023d8,%edi
  400e9d: e8 6e fc ff ff        callq  400b10 <puts@plt>
  400ea2: e8 f7 05 00 00        callq  40149e <read_line>
  400ea7: 48 89 c7              mov    %rax,%rdi
  400eaa: e8 b3 01 00 00        callq  401062 <phase_5>
  400eaf: e8 10 07 00 00        callq  4015c4 <phase_defused>
  400eb4: bf 1a 23 40 00        mov    $0x40231a,%edi
  400eb9: e8 52 fc ff ff        callq  400b10 <puts@plt>
  400ebe: e8 db 05 00 00        callq  40149e <read_line>
  400ec3: 48 89 c7              mov    %rax,%rdi
  400ec6: e8 29 02 00 00        callq  4010f4 <phase_6>
  400ecb: e8 f4 06 00 00        callq  4015c4 <phase_defused>
  400ed0: b8 00 00 00 00        mov    $0x0,%eax
  400ed5: 5b                    pop    %rbx

Как мы видели в bomb.c, read_line сначала вызывается в основной функции, а затем возвращаемый адрес передается в функцию Phase_n.Если ввод неверный, будет выполнена функция взрыва. Так что, конечно же, следуйте траектории выполнения основной функции, чтобы разминировать одну за другой~

Phase_1

Сначала взгляните на дизассемблированный код Phase_1:

0000000000400ee0 <phase_1>:
  400ee0: 48 83 ec 08           sub    $0x8,%rsp
  400ee4: be 00 24 40 00        mov    $0x402400,%esi
  400ee9: e8 4a 04 00 00        callq  401338 <strings_not_equal>
  400eee: 85 c0                 test   %eax,%eax
  400ef0: 74 05                 je     400ef7 <phase_1+0x17>
  400ef2: e8 43 05 00 00        callq  40143a <explode_bomb>
  400ef7: 48 83 c4 08           add    $0x8,%rsp
  400efb: c3                    retq   

Ассемблерный код фазы_1 очень лаконичен, позвольте мне сначала объяснить его.

Функция read_line будет хранить адрес прочитанной строки в rdi и rsi, а функция strings_not_equal будет использовать значения в edi и esi как двухсимвольные адреса, и судить, равны ли они, и возвращать 0 в случае равенства

Посмотрите на функцию Phase_1, сначала присвойте esi 0x402400, а затем вызовите strings_not_equal.Только что проанализировано, перед каждым вызовом Phase_n будет вызываться read_line, чтобы прочитать строку и поместить ее в edi и esi. Очевидно, здесь нужно вызвать функцию сравнения строк, чтобы сравнить, равна ли введенная нами строка строке, хранящейся по адресу 0x402400, а затем вызвать тестовую команду, если eax равен 0, то есть две строки равны, перейти к концу функции, в противном случае вызовите функциюexplore_bomb, которая является функцией, которая взрывает бомбу. Ответ здесь, все, что нам нужно ввести, это строка, хранящаяся по адресу 0x402400. Затем начните отладку с помощью gdb

(gdb) b  phase_1               ;打断点
(gdb) run                           ;执行到下一个断点
(gdb) info r                     ;查看寄存器值
(gdb) print (char*)(0x402400) ;查看内存中字符串

3.pngВ приведенном выше окне отладки вы можете видеть, что ($edi) — это именно то приветствие, которое я ввел, а ответ «Пограничные отношения с Канадой никогда не были лучше». Затем снова откройте окно отладки и введите эту строку через фазу_1.

Вы можете записать ранее решенные ответы в файл, по одной строке для каждого ответа, а затем установить параметр командной строки set args xixi (здесь имя вашего файла ответов), когда вы начнете отладку, вы можете напрямую ввести решенные ответы.

Phase_2

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

0000000000400efc <phase_2>:
  400efc: 55                    push   %rbp
  400efd: 53                    push   %rbx
  400efe: 48 83 ec 28           sub    $0x28,%rsp
  400f02: 48 89 e6              mov    %rsp,%rsi
  400f05: e8 52 05 00 00        callq  40145c <read_six_numbers>    ;读入六个数,第一个存在($rsp)处
  400f0a: 83 3c 24 01           cmpl   $0x1,(%rsp)            ;第一个数和1比较
  400f0e: 74 20                 je     400f30 <phase_2+0x34>                        ;等于1跳转
  400f10: e8 25 05 00 00        callq  40143a <explode_bomb>                      ;否则爆炸
  400f15: eb 19                 jmp    400f30 <phase_2+0x34>
  400f17: 8b 43 fc              mov    -0x4(%rbx),%eax                     ;取出rbx-4处的值赋给eax
  400f1a: 01 c0                 add    %eax,%eax                               ; eax = eax *2
  400f1c: 39 03                 cmp    %eax,(%rbx)                                        
         ;比较eax*2和rbx处的值,注意:eax是ebx-4处的值,即将rbx和前一个数的两倍比较
  400f1e: 74 05                 je     400f25 <phase_2+0x29>
                                                      ;如果相等就跳转,而跳转处的代码是将rbx+4
  400f20: e8 15 05 00 00        callq  40143a <explode_bomb>    ;否则爆炸
  400f25: 48 83 c3 04           add    $0x4,%rbx         ; 将rbx+4
  400f29: 48 39 eb              cmp    %rbp,%rbx      
                  ;将加4后的值和rbp比较,注意rbp是rsp+24,而rsp是第一个数,一个数四个字节。那么rbp就应该是
                  后那个数后面那个地址,即rbp是个循环哨兵
  400f2c: 75 e9                 jne    400f17 <phase_2+0x1b>   ;不等就继续跳转去循环
  400f2e: eb 0c                 jmp    400f3c <phase_2+0x40>  ; 相等就结束跳转到函数结尾
  400f30: 48 8d 5c 24 04        lea    0x4(%rsp),%rbx                                       ;将rsp+4存到rbx
  400f35: 48 8d 6c 24 18        lea    0x18(%rsp),%rbp                                       ;将rsp +24 存到rbp
  400f3a: eb db                 jmp    400f17 <phase_2+0x1b>                         ;跳转
  400f3c: 48 83 c4 28           add    $0x28,%rsp
  400f40: 5b                    pop    %rbx
  400f41: 5d                    pop    %rbp
  400f42: c3                    retq   

Хорошо видно, что вызывается read_six_numbers.Название этой функции нам уже сказали, но осталось только посмотреть на ее код, чтобы знать, что она будет хранить первое число в адресе ($ rsp), а затем по очереди увеличивать его. Этот комментарий к коду очень ясен, основная часть — это цикл, и каждый цикл должен определить, равны ли текущее число и удвоенное предыдущее число, и взорваться, если они не равны. Кроме того, первое число должно быть 1, затем шесть введенных чисел должны быть 1 2 4 8 16 32, что можно проверить с помощью отладки gdb.4.png

phase_3

Или сначала поставьте третью строку кода:

0000000000400f43 <phase_3>:
  400f43: 48 83 ec 18           sub    $0x18,%rsp
  400f47: 48 8d 4c 24 0c        lea    0xc(%rsp),%rcx
  400f4c: 48 8d 54 24 08        lea    0x8(%rsp),%rdx
  400f51: be cf 25 40 00        mov    $0x4025cf,%esi
  400f56: b8 00 00 00 00        mov    $0x0,%eax
  400f5b: e8 90 fc ff ff        callq  400bf0 <__isoc99_sscanf@plt>
  400f60: 83 f8 01              cmp    $0x1,%eax
  400f63: 7f 05                 jg     400f6a <phase_3+0x27>
  400f65: e8 d0 04 00 00        callq  40143a <explode_bomb>
  400f6a: 83 7c 24 08 07        cmpl   $0x7,0x8(%rsp)    
  400f6f: 77 3c                 ja     400fad <phase_3+0x6a>     #将第一个数和7比较,大于跳转到炸弹
  400f71: 8b 44 24 08           mov    0x8(%rsp),%eax
  400f75: ff 24 c5 70 24 40 00  jmpq   (,*0x402470%rax,8)
  400f7c: b8 cf 00 00 00        mov    $0xcf,%eax
  400f81: eb 3b                 jmp    400fbe <phase_3+0x7b>
  400f83: b8 c3 02 00 00        mov    $0x2c3,%eax
  400f88: eb 34                 jmp    400fbe <phase_3+0x7b>
  400f8a: b8 00 01 00 00        mov    $0x100,%eax
  400f8f: eb 2d                 jmp    400fbe <phase_3+0x7b>
  400f91: b8 85 01 00 00        mov    $0x185,%eax
  400f96: eb 26                 jmp    400fbe <phase_3+0x7b>
  400f98: b8 ce 00 00 00        mov    $0xce,%eax
  400f9d: eb 1f                 jmp    400fbe <phase_3+0x7b>
  400f9f: b8 aa 02 00 00        mov    $0x2aa,%eax
  400fa4: eb 18                 jmp    400fbe <phase_3+0x7b>
  400fa6: b8 47 01 00 00        mov    $0x147,%eax
  400fab: eb 11                 jmp    400fbe <phase_3+0x7b>
  400fad: e8 88 04 00 00        callq  40143a <explode_bomb>
  400fb2: b8 00 00 00 00        mov    $0x0,%eax
  400fb7: eb 05                 jmp    400fbe <phase_3+0x7b>
  400fb9: b8 37 01 00 00        mov    $0x137,%eax
  400fbe: 3b 44 24 0c           cmp    0xc(%rsp),%eax
  400fc2: 74 05                 je     400fc9 <phase_3+0x86>
  400fc4: e8 71 04 00 00        callq  40143a <explode_bomb>
  400fc9: 48 83 c4 18           add    $0x18,%rsp
  400fcd: c3                    retq   

Сначала я увидел sscanf, поэтому перед этой функцией должна быть строковая константа для хранения формата считываемых данных, поэтому строковая константа должна быть $0x4025cf, распечатайте ее с помощью gdb, чтобы подтвердить форматimage.pngМы видим, что формат "%d %d", поэтому нам нужно ввести два целых числа. Оглядываясь назад на сборку, мы видим, что за этим кодом стоит множество операторов jmp, и они очень регулярны. Предполагается, что это таблица переходов или оператор switch. Адрес для перехода - 0x402470+%rax+8, а eax - это то, что мы Первое число, а затем каждый jmp можно рассматривать как оператор case. Мы видим, что каждый оператор case присваивает eax параметр, такой как 0xcf, 0x2c3 и т. д., а затем все case равномерно переходят к 0x400fbe, и В этом месте мы сравниваем второе число, которое мы ввели, со значением в eax.Если оно равно, пропускаем бомбу или она взорвется.Значение eax получается путем перехода к разным случаям в соответствии с первым значением. Тогда случаев столько, сколько должно быть столько ответов на задачу.Нам нужно только определить первое число и проследить вызов к одному из случаев, а затем посмотреть, какое значение константы в этом случае, что является во-вторых, мы вводим значение. Следует отметить, что первое введенное значение должно быть меньше 7, что прокомментировано в сборке, и видно, что случаев должно быть 7. Я выбрал первое число для ввода 3, а второе число нашел как 0x100 , что является десятичным числом 256.

    所以此题的其中一个解为3 256

phase_4

Код разборки:

000000000040100c <phase_4>:
  40100c: 48 83 ec 18           sub    $0x18,%rsp
  401010: 48 8d 4c 24 0c        lea    0xc(%rsp),%rcx
  401015: 48 8d 54 24 08        lea    0x8(%rsp),%rdx
  40101a: be cf 25 40 00        mov    $0x4025cf,%esi
  40101f: b8 00 00 00 00        mov    $0x0,%eax
  401024: e8 c7 fb ff ff        callq  400bf0 <__isoc99_sscanf@plt>
  401029: 83 f8 02              cmp    $0x2,%eax
  40102c: 75 07                 jne    401035 <phase_4+0x29>
  40102e: 83 7c 24 08 0e        cmpl   $0xe,0x8(%rsp)
  401033: 76 05                 jbe    40103a <phase_4+0x2e> #第一个数小与等于0xe跳转
  401035: e8 00 04 00 00        callq  40143a <explode_bomb>
  40103a: ba 0e 00 00 00        mov    $0xe,%edx
  40103f: be 00 00 00 00        mov    $0x0,%esi
  401044: 8b 7c 24 08           mov    0x8(%rsp),%edi
  401048: e8 81 ff ff ff        callq  400fce <func4>
  40104d: 85 c0                 test   %eax,%eax      #测试返回值是否为0,否就爆炸
  40104f: 75 07                 jne    401058 <phase_4+0x4c>
  401051: 83 7c 24 0c 00        cmpl   $0x0,0xc(%rsp)
  401056: 74 05                 je     40105d <phase_4+0x51>
  401058: e8 dd 03 00 00        callq  40143a <explode_bomb>
  40105d: 48 83 c4 18           add    $0x18,%rsp
  401061: c3                    retq   

Еще есть sscan, на этот раз посмотрите прямо на формат ввода, не является ли 0x4025cf строкой формата «%d %d» в предыдущем вопросе, кажется, что в этом вопросе все еще нужно ввести два целых числа, и func4 также будет называться в функции сборки Phase_4 эта функция func4 является ключевой, разборка выглядит следующим образом:

0000000000400fce <func4>: 
  400fce: sub    $0x8,%rsp                      ;; 分配栈帧
  400fd2: mov    %edx,%eax                      ;; C                  eax
  400fd4: sub    %esi,%eax                      ;; C - B         更新 eax
  400fd6: mov    %eax,%ecx                      ;; C - B              ecx 
  400fd8: shr    $0x1f,%ecx                     ;; 右移 31 位, ecx 长为 32 位(也就是之前的最高位变为最低位,其余 31 位填充补 0),可以认为 ecx = 0
  400fdb: add    %ecx,%eax                      ;; C - B              eax
  400fdd: sar    %eax                           ;; 这里是一个缩写 sar $1,%eax (对应的机器码为 D1F8)  eax = (C-B)/2
  400fdf: lea    (%rax,%rsi,1),%ecx             ;; (C+B)/2               ecx        
  400fe2: cmp    %edi,%ecx                      ;; ecx 与 A 进行比较               (1)
  400fe4: jle    400ff2 <func4+0x24>            ;; ecx 小于等于 A 则跳转
  400fe6: lea    -0x1(%rcx),%edx                ;; C = (C+B)/2 - 1
  400fe9: callq  400fce <func4>                 ;; 递归调用
  400fee: add    %eax,%eax                      ;; 递归返回值加倍
  400ff0: jmp    401007 <func4+0x39>            ;; 跳转到 func 函数的出口处 
  400ff2: mov    $0x0,%eax                      ;; eax = 0                      (2)
  400ff7: cmp    %edi,%ecx                      ;; ecx 与 A 进行比较
  400ff9: jge    401007 <func4+0x39>            ;; eax 大于等于 A 则跳转
  400ffb: lea    0x1(%rcx),%esi                 ;; B = ecx + 1
  400ffe: callq  400fce <func4>                 ;; 递归调用
  401003: lea    0x1(%rax,%rax,1),%eax          ;; 递归返回值加倍并再加上 1
  401007: add    $0x8,%rsp                      ;; 释放栈帧
  40100b: retq                                  ;; 函数返回

В этой функции мы ясно видим, что func4 вызывает func4 внутри, что не является рекурсивным ассемблером. Попробуйте написать соответствующий код на языке c следующим образом:

int func4(int target, int step, int limit) {
  /* edi = target; esi = step; edx = limit */
  int temp = (limit - step) * 0.5;
  int mid = temp + step;
  if (mid > target) {
    limit = mid - 1;
    int ret1 = func4(target, step, limit);
    return 2 * ret1;
  } else {
    if (mid >= target) {
      return 0;
    } else {
      step = mid + 1;
      int ret2 = func4(target, step, limit);
      return (2 * ret2 + 1);
    }
  }
}

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

phase_5

0000000000401062 <phase_5>:
  401062: 53                    push   %rbx
  401063: 48 83 ec 20           sub    $0x20,%rsp
  401067: 48 89 fb              mov    %rdi,%rbx
  40106a: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax
  401071: 00 00 
  401073: 48 89 44 24 18        mov    %rax,0x18(%rsp)
  401078: 31 c0                 xor    %eax,%eax
  40107a: e8 9c 02 00 00        callq  40131b <string_length>
  40107f: 83 f8 06              cmp    $0x6,%eax   #要求输入的字符串长度为6
  401082: 74 4e                 je     4010d2 <phase_5+0x70>
  401084: e8 b1 03 00 00        callq  40143a <explode_bomb>
  401089: eb 47                 jmp    4010d2 <phase_5+0x70>
  40108b: 0f b6 0c 03           movzbl (%rbx,%rax,1),%ecx
  40108f: 88 0c 24              mov    %cl,(%rsp)
  401092: 48 8b 14 24           mov    (%rsp),%rdx
  401096: 83 e2 0f              and    $0xf,%edx     #  取edx后四位
  401099: 0f b6 92 b0 24 40 00  movzbl 0x4024b0(%rdx),%edx  #将edx后四位作为0x4024b0字符数组的索引值
  4010a0: 88 54 04 10           mov    %dl,0x10(%rsp,%rax,1)   # 依次拷贝字符数组到0x10((%rsp,%rax,1))
  4010a4: 48 83 c0 01           add    $0x1,%rax             #循环计数+1
  4010a8: 48 83 f8 06           cmp    $0x6,%rax            #循环计数和6比较,即循环6次
  4010ac: 75 dd                 jne    40108b <phase_5+0x29>
  4010ae: c6 44 24 16 00        movb   $0x0,0x16(%rsp)    #字符串末尾添加"\0"
  4010b3: be 5e 24 40 00        mov    $0x40245e,%esi  # 字符串常量
  4010b8: 48 8d 7c 24 10        lea    0x10(%rsp),%rdi
  4010bd: e8 76 02 00 00        callq  401338 <strings_not_equal> # 和字符串常量比较
  4010c2: 85 c0                 test   %eax,%eax
  4010c4: 74 13                 je     4010d9 <phase_5+0x77>
  4010c6: e8 6f 03 00 00        callq  40143a <explode_bomb>
  4010cb: 0f 1f 44 00 00        nopl   0x0(%rax,%rax,1)
  4010d0: eb 07                 jmp    4010d9 <phase_5+0x77>
  4010d2: b8 00 00 00 00        mov    $0x0,%eax
  4010d7: eb b2                 jmp    40108b <phase_5+0x29>
  4010d9: 48 8b 44 24 18        mov    0x18(%rsp),%rax
  4010de: 64 48 33 04 25 28 00  xor    %fs:0x28,%rax
  4010e5: 00 00 
  4010e7: 74 05                 je     4010ee <phase_5+0x8c>
  4010e9: e8 42 fa ff ff        callq  400b30 <__stack_chk_fail@plt>
  4010ee: 48 83 c4 20           add    $0x20,%rsp
  4010f2: 5b                    pop    %rbx
  4010f3: c3                    retq   

Ниже будет сравнение со строковой константой, давайте сначала посмотрим, что это за строковая константа:image.pngСборка "флаеров" также имеет строковую константу 0x4024b0:img "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

Поскольку ассемблерный код относительно длинный, я прямо объясню, что делает этот раздел: 1. Требовать ввода 6 символов, а затем зацикливать массив входных символов по очереди 2. Берет один символ в каждом раунде цикла, а затем принимает последние четыре цифры символа В качестве индекса возьмите символ со второй символьной константой и сохраните его по очереди по адресу 0x10 (%rsp) 3. Наконец, сравните строку по новому адресу 0x10 (%rsp) с "флаерами" , если то же самое, пройти, иначе взорваться, поэтому мы должны быть отодвинуты в соответствии с результатом.Например, символ f в флаерах получается по последним четырем цифрам первого символа, который мы вводим в качестве индекса в "maduiersnfotvbylТак что вы думаете, вы можете остановить бомбу с помощью Ctrl-C, не так ли?», но мы, зная, что четырехбитный двоичный индекс может содержать до 16 позиций, поэтому для выборки можно использовать только первые 16 символов этой длинной строки символов. нужные нам персонажи. Таким образом, индекс f равен 9, то есть двоичный 1001. Вам нужно запросить только символы, последние четыре цифры которых равны 1001 в таблице ascii. Я беру Y. И так далее, чтобы получить комбинацию из 6 символов: YONEFw

phase_6

Компиляция этого уровня действительно сложна для понимания, я только что прочитал некоторые части, но не смог связать их воедино, поэтому дизассемблирование здесь выкладывать не буду. Информация, которую я получил, вероятно, также заключается в том, что вам нужно ввести 6 чисел и меньше или равно 6. А также переворачивает каждое число (a = 7 - a) во время цикла. Проверяйте ответы других людей онлайн 4 3 2 1 6 5

Secret_phase

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

0000000000401242 <secret_phase>:
  401242: 53                    push   %rbx
  401243: e8 56 02 00 00        callq  40149e <read_line>
  401248: ba 0a 00 00 00        mov    $0xa,%edx
  40124d: be 00 00 00 00        mov    $0x0,%esi
  401252: 48 89 c7              mov    %rax,%rdi
  401255: e8 76 f9 ff ff        callq  400bd0 <strtol@plt>
  40125a: 48 89 c3              mov    %rax,%rbx
  40125d: 8d 40 ff              lea    -0x1(%rax),%eax
  401260: 3d e8 03 00 00        cmp    $0x3e8,%eax
  401265: 76 05                 jbe    40126c <secret_phase+0x2a>
  401267: e8 ce 01 00 00        callq  40143a <explode_bomb>
  40126c: 89 de                 mov    %ebx,%esi
  40126e: bf f0 30 60 00        mov    $0x6030f0,%edi
  401273: e8 8c ff ff ff        callq  401204 <fun7>
  401278: 83 f8 02              cmp    $0x2,%eax
  40127b: 74 05                 je     401282 <secret_phase+0x40>
  40127d: e8 b8 01 00 00        callq  40143a <explode_bomb>
  401282: bf 38 24 40 00        mov    $0x402438,%edi
  401287: e8 84 f8 ff ff        callq  400b10 <puts@plt>
  40128c: e8 33 03 00 00        callq  4015c4 <phase_defused>
  401291: 5b                    pop    %rbx
  401292: c3                    retq   

Но есть проблема.В основной функции мы не видим инструкции для вызова функции secret_phase,так где же она вызывается?Глобально поискав по ключевому слову, мы можем обнаружить, что она вызывается в функции Phase_defused, а фаза_defused есть будет выполняться при каждом проходе через фазу A, затем следующим шагом будет анализ того, при каких обстоятельствах будет инициирован вызов secret_phase

играть перед входом

00000000004015c4 <phase_defused>:
  4015c4: 48 83 ec 78           sub    $0x78,%rsp
  4015c8: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax
  4015cf: 00 00 
  4015d1: 48 89 44 24 68        mov    %rax,0x68(%rsp)
  4015d6: 31 c0                 xor    %eax,%eax
      比较输入的字符串数目是否等于6,不等于则跳转至程序结束
  4015d8: 83 3d 81 21 20 00 06  cmpl   $0x6,0x202181(%rip)        # 603760 <num_input_strings>
  4015df: 75 5e                 jne    40163f <phase_defused+0x7b>
  4015e1: 4c 8d 44 24 10        lea    0x10(%rsp),%r8
  4015e6: 48 8d 4c 24 0c        lea    0xc(%rsp),%rcx
  4015eb: 48 8d 54 24 08        lea    0x8(%rsp),%rdx
  4015f0: be 19 26 40 00        mov    $0x402619,%esi     
  4015f5: bf 70 38 60 00        mov    $0x603870,%edi
  4015fa: e8 f1 f5 ff ff        callq  400bf0 <__isoc99_sscanf@plt>
  4015ff: 83 f8 03              cmp    $0x3,%eax
  401602: 75 31                 jne    401635 <phase_defused+0x71>
  401604: be 22 26 40 00        mov    $0x402622,%esi
  401609: 48 8d 7c 24 10        lea    0x10(%rsp),%rdi
  40160e: e8 25 fd ff ff        callq  401338 <strings_not_equal>
  401613: 85 c0                 test   %eax,%eax
  401615: 75 1e                 jne    401635 <phase_defused+0x71>
  401617: bf f8 24 40 00        mov    $0x4024f8,%edi
  40161c: e8 ef f4 ff ff        callq  400b10 <puts@plt>
  401621: bf 20 25 40 00        mov    $0x402520,%edi
  401626: e8 e5 f4 ff ff        callq  400b10 <puts@plt>
  40162b: b8 00 00 00 00        mov    $0x0,%eax
  401630: e8 0d fc ff ff        callq  401242 <secret_phase>    ;调用secret_phase
  401635: bf 58 25 40 00        mov    $0x402558,%edi
  40163a: e8 d1 f4 ff ff        callq  400b10 <puts@plt>
  40163f: 48 8b 44 24 68        mov    0x68(%rsp),%rax
  401644: 64 48 33 04 25 28 00  xor    %fs:0x28,%rax
  40164b: 00 00 
  40164d: 74 05                 je     401654 <phase_defused+0x90>
  40164f: e8 dc f4 ff ff        callq  400b30 <__stack_chk_fail@plt>
  401654: 48 83 c4 78           add    $0x78,%rsp
  401658: c3                    retq   

Давайте проанализируем приведенный выше код по частям.

4015d6: 31 c0                 xor    %eax,%eax
      比较输入的字符串数目是否等于6,不等于则跳转至程序结束
  4015d8: 83 3d 81 21 20 00 06  cmpl   $0x6,0x202181(%rip)        # 603760 <num_input_strings>
  4015df: 75 5e                 jne    40163f <phase_defused+0x7b>

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

 4015f0: be 19 26 40 00        mov    $0x402619,%esi     
 4015f5: bf 70 38 60 00        mov    $0x603870,%edi
 4015fa: e8 f1 f5 ff ff        callq  400bf0 <__isoc99_sscanf@plt>
 4015ff: 83 f8 03              cmp    $0x3,%eax
 401602: 75 31                 jne    401635 <phase_defused+0x71>

Здесь esi и edi, очевидно, адреса двух строк, и дальше будет вызываться sscanf, поэтому одна должна быть введенной нами строкой, а другая — форматом в scanf("formate",&,&), подключаем Go вниз и используйте gdb, чтобы увидеть, что представляют собой эти две строки5.pngВидно, что в esi ставится "%d %d %s", а edi - это ответ "7 0", который мы ввели в фазе_4, но это точно не подходит, %s не может совпадать. мы продолжаем видеть

 4015fa: e8 f1 f5 ff ff        callq  400bf0 <__isoc99_sscanf@plt>
  4015ff: 83 f8 03              cmp    $0x3,%eax
  401602: 75 31                 jne    401635 <phase_defused+0x71>

После вызова sscanf определите, равно ли возвращаемое значение eax (то есть количество правильно совпадающих подстановочных знаков) 3, если нет, перейдите в конец функции, чтобы напечатать это предложение.

 401635: bf 58 25 40 00        mov    $0x402558,%edi
  40163a: e8 d1 f4 ff ff        callq  400b10 <puts@plt>

Давайте посмотрим, что здесь находится по адресу 0x402558.imgЭто первые шесть подсказок фазы прошли успешно, но мы не вошли в secret_phase, поэтому теперь будем считать, что мы ввели 3 совпадения, то есть будет выполнено добавление строки после четвертого решения задачи.

  401604: be 22 26 40 00        mov    $0x402622,%esi
  401609: 48 8d 7c 24 10        lea    0x10(%rsp),%rdi
  40160e: e8 25 fd ff ff        callq  401338 <strings_not_equal>
  401613: 85 c0                 test   %eax,%eax
  401615: 75 1e                 jne    401635 <phase_defused+0x71>
  401617: bf f8 24 40 00        mov    $0x4024f8,%edi
  40161c: e8 ef f4 ff ff        callq  400b10 <puts@plt>
  401621: bf 20 25 40 00        mov    $0x402520,%edi
  401626: e8 e5 f4 ff ff        callq  400b10 <puts@plt>
  40162b: b8 00 00 00 00        mov    $0x0,%eax
  401630: e8 0d fc ff ff        callq  401242 <secret_phase>    ;调用secret_phase

Здесь снова адреса двух строк передаются в esi и edi, а затем вызывается функция сравнения строк. Если нет, то она переходит в конец функции и печатает поздравления. Если они равны, строки по адресам 0x4024f8 и 0x402520 будет напечатан первым.Затем вызовите secret_phase.Похоже, что ключ к входу на секретный уровень состоит в том, чтобы сделать строки в edi и esi равными. Давайте сначала посмотрим, что представляют собой эти два места. Чтобы иметь возможность выполнить этот шаг, мы сначала добавляем строку после решения четвертого вопроса, то есть «7 0» на «7 0 xixi» (xixi добавлено случайно), поместите gdb ниже, чтобы просмотреть скриншот строки7.png! ! ! ! ! ! ! Именно так я и думал, строка, совпавшая с %s, помещается в rdi, а заданная заранее — в rsi. Пока эти два равны, мы можем войти в секретный уровень, ну и берем ключ "DrEvil" на замену "xixi", и начинаем официально входить в секретную_фазу (так долго входил...

главный герой после прелюдии

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

0000000000401242 <secret_phase>:
  401242: 53                    push   %rbx
  401243: e8 56 02 00 00        callq  40149e <read_line>
  401248: ba 0a 00 00 00        mov    $0xa,%edx
  40124d: be 00 00 00 00        mov    $0x0,%esi
  401252: 48 89 c7              mov    %rax,%rdi
  401255: e8 76 f9 ff ff        callq  400bd0 <strtol@plt>
  40125a: 48 89 c3              mov    %rax,%rbx
  40125d: 8d 40 ff              lea    -0x1(%rax),%eax
  401260: 3d e8 03 00 00        cmp    $0x3e8,%eax
  401265: 76 05                 jbe    40126c <secret_phase+0x2a>
  401267: e8 ce 01 00 00        callq  40143a <explode_bomb>
  40126c: 89 de                 mov    %ebx,%esi
  40126e: bf f0 30 60 00        mov    $0x6030f0,%edi
  401273: e8 8c ff ff ff        callq  401204 <fun7>
  401278: 83 f8 02              cmp    $0x2,%eax
  40127b: 74 05                 je     401282 <secret_phase+0x40>
  40127d: e8 b8 01 00 00        callq  40143a <explode_bomb>
  401282: bf 38 24 40 00        mov    $0x402438,%edi
  401287: e8 84 f8 ff ff        callq  400b10 <puts@plt>
  40128c: e8 33 03 00 00        callq  4015c4 <phase_defused>
  401291: 5b                    pop    %rbx
  401292: c3                    retq   

Еще кусок анализа

  401243: e8 56 02 00 00        callq  40149e <read_line>
  401248: ba 0a 00 00 00        mov    $0xa,%edx
  40124d: be 00 00 00 00        mov    $0x0,%esi
  401252: 48 89 c7              mov    %rax,%rdi
  401255: e8 76 f9 ff ff        callq  400bd0 <strtol@plt>
  40125a: 48 89 c3              mov    %rax,%rbx

Здесь очевидно, что сначала читается строка, а затем вызывается функция strtol, которая используется для преобразования строк в long в языке C. Прототип функции выглядит следующим образом:

Описание: библиотечная функция C *long int strtol(const char str, char endptr, int base) преобразует строку, на которую указывает параметр str, в соответствии с заданной базой Преобразуется в длинное целое (типа long int), основание должно быть от 2 до 36 (включительно) или специальное значение 0.

Объявление: long int strtol(const char *str, char **endptr, int base)

Тогда вы, вероятно, можете догадаться, что возвращаемое значение read_line rax, хранящееся в rdi, является параметром str, а 0xa в edx должно представлять десятичное число, esi должно быть специальным значением 0, а затем анализировать возвращаемое значение strtol.

  40125a: 48 89 c3              mov    %rax,%rbx             ;将rax保存到rbx中 
  40125d: 8d 40 ff              lea    -0x1(%rax),%eax               ; eax =eax -1
  401260: 3d e8 03 00 00        cmp    $0x3e8,%eax                    ;cmp 1000, eax
  401265: 76 05                 jbe    40126c <secret_phase+0x2a>     ;if  eax < = 1000 then 跳过炸弹
  401267: e8 ce 01 00 00        callq  40143a <explode_bomb>           ;炸弹
  40126c: 89 de                 mov    %ebx,%esi                    ;  传参
  40126e: bf f0 30 60 00        mov    $0x6030f0,%edi                ;      传参
  401273: e8 8c ff ff ff        callq  401204 <fun7>               ;  调用fun7
  401278: 83 f8 02              cmp    $0x2,%eax       ;比较返回值和2
  40127b: 74 05                 je     401282 <secret_phase+0x40>   ;相等就跳转输出0x402438处的字符串并返回
  40127d: e8 b8 01 00 00        callq  40143a <explode_bomb> ;不等就爆炸
  401282: bf 38 24 40 00        mov    $0x402438,%edi
  401287: e8 84 f8 ff ff        callq  400b10 <puts@plt>

Кажется, что secret_phase в целом состоит в том, чтобы ввести строку, а затем преобразовать строку в длинный тип, ошибка преобразования или преобразованное число> 1000 взорвется, а затем использовать преобразованное число для передачи в функцию fun7, если возврат значение равно 2, затем пройдите этот уровень плавно, иначе он взорвется. Итак, теперь ключ в том, что такое функция fun7, давайте зайдем и узнаем: fun7:

0000000000401204 <fun7>:
  401204: 48 83 ec 08           sub    $0x8,%rsp
  401208: 48 85 ff              test   %rdi,%rdi
  40120b: 74 2b                 je     401238 <fun7+0x34>
  40120d: 8b 17                 mov    (%rdi),%edx
  40120f: 39 f2                 cmp    %esi,%edx
  401211: 7e 0d                 jle    401220 <fun7+0x1c>
  401213: 48 8b 7f 08           mov    0x8(%rdi),%rdi  ;rdi = (rdi+8)
  401217: e8 e8 ff ff ff        callq  401204 <fun7>  ;递归1
  40121c: 01 c0                 add    %eax,%eax
  40121e: eb 1d                 jmp    40123d <fun7+0x39>
  401220: b8 00 00 00 00        mov    $0x0,%eax
  401225: 39 f2                 cmp    %esi,%edx
  401227: 74 14                 je     40123d <fun7+0x39>
  401229: 48 8b 7f 10           mov    0x10(%rdi),%rdi
  40122d: e8 d2 ff ff ff        callq  401204 <fun7>   ;递归2
  401232: 8d 44 00 01           lea    0x1(%rax,%rax,1),%eax
  401236: eb 05                 jmp    40123d <fun7+0x39>
  401238: b8 ff ff ff ff        mov    $0xffffffff,%eax
  40123d: 48 83 c4 08           add    $0x8,%rsp
  401241: c3                    retq   

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

  401208: 48 85 ff              test   %rdi,%rdi   ;edi如果为0则跳转并返回-1
  40120b: 74 2b                 je     401238 <fun7+0x34>
  

Проверьте, равен ли входящий edi 0, если да, перейдите в конец и верните 0xffffffff, который равен 0

  40120d: 8b 17                 mov    (%rdi),%edx  ;取出rdi地址的值赋给edx
  40120f: 39 f2                 cmp    %esi,%edx   ;比较edx和esi的值
  401211: 7e 0d                 jle    401220 <fun7+0x1c> ;if edx <= esi(这就是strtol转换来的数字),跳转
  401213: 48 8b 7f 08           mov    0x8(%rdi),%rdi  ;否则执行递归  rdi = (rdi+8)
  401217: e8 e8 ff ff ff        callq  401204 <fun7>  ; 递归
  40121c: 01 c0                 add    %eax,%eax         ;递归返回值*2
  40121e: eb 1d                 jmp    40123d <fun7+0x39> ;跳转至返回

этот абзац

  401220: b8 00 00 00 00        mov    $0x0,%eax ; 提前将eax置0,这其实是返回值
  401225: 39 f2                 cmp    %esi,%edx       ; 还是比较esi和edx
  401227: 74 14                 je     40123d <fun7+0x39>  ; 如果相等就跳转并返回0
  401229: 48 8b 7f 10           mov    0x10(%rdi),%rdi ;如果不相等就 edi = (edi+16)
  40122d: e8 d2 ff ff ff        callq  401204 <fun7>   ;递归2
  401232: 8d 44 00 01           lea    0x1(%rax,%rax,1),%eax  ;递归返回值 eax = 2*eax+1
  401236: eb 05                 jmp    40123d <fun7+0x39>  跳转至返回
 

Но проблема в том, что мы ранее проанализировали, что fun7 должен вернуть 2 для прохождения, так как же мы можем вернуть 2?Детали кода были четко аннотированы.Ниже приведен рекурсивный псевдо-язык C, соответствующий версии

fun7(esi, void *rdi){
  if(rdi == 0)
        return -1;
  if(*rdi <= esi ){
        if(*rdi == esi)
                return 0;                                 step1
         else 
            a = fun7(esi, *(rdi+16))
            return 2*1+1                             step2
    } else {
            return 2 * fun7(esi, *(rdi+8))       step3
    }
}

На самом деле мы видим, что два рекурсивных изменения rdi различны, поэтому для того, чтобы вернуться к 2, порядок рекурсивных вызовов должен быть step3->step2->step1, то есть значение *rdi сначала должно быть * rdi > esi, затем *rdi

  40126e: bf f0 30 60 00        mov    $0x6030f0,%edi                ;      传参
  401273: e8 8c ff ff ff        callq  401204 <fun7>               ;  调用fun7

Теперь давайте проследим за предыдущим анализом, чтобы увидеть, какое число находится по адресу 0x6030f0:8.png36! ! Итак, мы ввели число, поэтому мы можем ввести число меньше 36, чтобы увидеть второй шаг.что такое рди9.png8 ! ! Таким образом, входное число должно быть больше 8, чтобы перейти к третьему шагу, затем продолжайте делать это, пока не пройдет третий шаг.rdi == esi это уравнение, чтобы узнать esi, то есть число, которое мы должны ввести, затем gdb выполняет программу до третьего шага, чтобы распечатать значение, соответствующее rdi10.pngдвадцать два ! ! ! ! ! ! Все, что вам нужно сделать сейчас, это убедиться, что 22 правильно

Я освобождаю все вопросы в файл xixi и выполняю ./bomb xixi11.pngВау!Успешно прошел шесть уровней и один скрытый уровень, уже полвторого после разбора этого секретного уровня....

болтовня

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

Статья постоянно обновляется, и поиск WeChat «Руководство по программированию» — это первый раз, когда я ее получаю, а в ответе [Информация] есть подготовленные мной материалы интервью и шаблоны резюме ведущих производителей BAT.