Bomb Lab环境配置及解题

2023-12-13 09:54:00

Bomb Lab环境配置及解题

前言:

自上次做Lab隔了不少时间,环境配置也有点忘了,上次用的是mac搭docker这次直接用windows虚拟机,很简单,打开虚拟机用命令安装一下gdb和wget,然后用wget把官网的实验材料下载下来,最后用tar解压就行,具体命令可以看我这篇文章《Linux基操那些事儿》

前置知识:

《程序的机器级表示》 && 下图

下图表示,调用函数时,每个参数的存储位置。

image-20231208192146211

ok,开始做题之前,打开文件都看一遍,英文全翻译一遍,下附上译文。

* 邪恶博士的阴险炸弹,版本 1.1

* 版权所有 2011,Dr. Evil Incorporated。版权所有。

*

* 执照:

*

* Dr. Evil Incorporated(犯罪者)特此授予您(

* VICTIM)明确许可使用此炸弹(BOMB)。这是一个

* 有时间限制的许可证,在受害者死亡后到期。

* 肇事者对损坏、挫败感、

* 精神错乱、眼球突出、腕管综合症、睡眠不足或其他

* 对受害者造成伤害。除非犯罪者想要获得功劳,

* 那是。受害者不得将此炸弹源代码分发给

* 犯罪者的任何敌人。没有受害者可以调试,

* 逆向工程,运行“字符串”,反编译,解密,或使用任何

* 了解炸弹并拆除炸弹的其他技术。炸弹

* 执行此程序时不得穿着防护服。这

* 犯罪者不会为犯罪者的不良意识道歉

* 幽默。在禁止使用 BOMB 的情况下,本许可证无效

/*

* 自我提醒:记得删除这个文件,这样我的受害者就不会再有

* 知道发生了什么,所以它们都会爆炸

* 壮观的恶魔爆炸。 ——邪恶博士

*/

* 根据法律。

/* 自我提醒:记得将此炸弹移植到 Windows 并放置一个

* 非常棒的图形用户界面。 */

/* 当不带参数运行时,炸弹读取其输入行

* 来自标准输入。 */

/* 当使用一个参数 <file> 运行时,炸弹会从 <file> 读取

* 直到EOF,然后切换到标准输入。因此,正如你

* 化解每个阶段,可以将其化解字符串添加到<file>中并

* 避免重新输入。 */

/* 你不能使用超过 1 个命令行参数来调用炸弹。 */

/* 做各种秘密的事情,让炸弹更难拆除。 */

/* 嗯...六相一定比一相更安全! */

/* 获取输入 */

/* 运行阶段 */

/* 糟糕!他们想通了!

* 让我知道他们是怎么做到的。 */

我这里是一股脑挪过来的,译文还是要对着相关代码来看。

大概解释一下:

  • 有六个炸弹要你拆,拆的方式是通过运行bomb文件后标准输入,如果输入符合预期那就成功拆除当前炸弹,连续六次成功即全部拆除。
  • 以防每次出错都要重新输入,可以把结果放在一个文件里,用gdb的run 附带一个文件名,它就会从文件中读取内容作为你的输入。

phase_1

image-20231208105828841

可以看到这里的逻辑,调用了phase_1,input作为参数,如果phase_1顺利结束,接下来调用的就是phase_defused,此时第一个炸弹就被拆除了。

也就是说,如果phase_1没法顺利结束,那么炸弹就会被引爆,所以我们需要研究的就是phase_1,此时我们用obdjump反汇编整个文件去找phase_1,或者直接在gdb中用disas来查看反汇编内容,这里建议用第一种方法,这样你就可以在文本编辑器中慢慢研究,我用的文本编辑器是VSCode,FinalShell可以接入外部文本编辑器。

输出objdump帮助信息:
objdump --help或者 man objdump

Usage: objdump <option(s)> <file(s)>
    Display information from object <file(s)>.
    At least one of the following switches must be given:
    -a, --archive-headers    Display archive header information
    -f, --file-headers       Display the contents of the overall file header
    -p, --private-headers    Display object format specific file header contents
    -P, --private=OPT,OPT... Display object format specific contents
    -h, --[section-]headers  Display the contents of the section headers
    -x, --all-headers        Display the contents of all headers
    -d, --disassemble        Display assembler contents of executable sections
    -D, --disassemble-all    Display assembler contents of all sections
    -S, --source             Intermix source code with disassembly
    -s, --full-contents      Display the full contents of all sections requested
    -g, --debugging          Display debug information in object file
    -e, --debugging-tags     Display debug information using ctags style
    -G, --stabs              Display (in raw form) any STABS info in the file
    -W[lLiaprmfFsoRt] or
    --dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
          =frames-interp,=str,=loc,=Ranges,=pubtypes,
          =gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
          =addr,=cu_index]
                           Display DWARF info in the file
    -t, --syms               Display the contents of the symbol table(s)
    -T, --dynamic-syms       Display the contents of the dynamic symbol table
    -r, --reloc              Display the relocation entries in the file
    -R, --dynamic-reloc      Display the dynamic relocation entries in the file @<file>                  Read options from <file>
    -v, --version            Display this program's version number
    -i, --info               List object formats and architectures supported
    -H, --help               Display this information

 The following switches are optional:
    -b, --target=BFDNAME     Specify the target object format as BFDNAME
    -m, --architecture=MACHINE     Specify the target architecture as MACHINE
    -j, --section=NAME       Only display information for section NAME
    -M, --disassembler-options=OPT Pass text OPT on to the disassembler
    -EB --endian=big         Assume big endian format when disassembling
    -EL --endian=little      Assume little endian format when disassembling
      --file-start-context   Include context from start of file (with -S)
    -I, --include=DIR        Add DIR to search list for source files
    -l, --line-numbers       Include line numbers and filenames in output
    -F, --file-offsets       Include file offsets when displaying information
    -C, --demangle[=STYLE]   Decode mangled/processed symbol names
                             The STYLE, if specified, can be `auto', `gnu',
                                  `lucid', `arm', `hp', `edg', `gnu-v3', `java'
                                  or `gnat'
    -w, --wide               Format output for more than 80 columns
    -z, --disassemble-zeroes       Do not skip blocks of zeroes when disassembling
      --start-address=ADDR   Only process data whose address is >= ADDR
      --stop-address=ADDR    Only process data whose address is <= ADDR
      --prefix-addresses     Print complete address alongside disassembly
      --[no-]show-raw-insn   Display hex alongside symbolic disassembly
      --insn-width=WIDTH     Display WIDTH bytes on a single line for -d
      --adjust-vma=OFFSET    Add OFFSET to all displayed section addresses
      --special-syms         Include special symbols in symbol dumps
      --prefix=PREFIX        Add PREFIX to absolute paths for -S
      --prefix-strip=LEVEL   Strip initial directory names for -S
      --dwarf-depth=N        Do not display DIEs at depth N or greater
      --dwarf-start=N        Display DIEs starting with N, at the same depth
                             or deeper
      --dwarf-check          Make additional dwarf internal consistency checks.      

objdump: supported targets: elf64-x86-64 elf32-i386 elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
objdump: supported architectures: i386 i386:x86-64 i386:x64-32 i8086 i386:intel i386:x86-64:intel i386:x64-32:intel l1om l1om:intel k1om k1om:intel plugin

The following i386/x86-64 specific disassembler options are supported for use
with the -M switch (multiple options should be separated by commas):
    x86-64      Disassemble in 64bit mode
    i386        Disassemble in 32bit mode
    i8086       Disassemble in 16bit mode
    att         Display instruction in AT&T syntax
    intel       Display instruction in Intel syntax
    att-mnemonic
                Display instruction in AT&T mnemonic
    intel-mnemonic
                Display instruction in Intel mnemonic
    addr64      Assume 64bit address size
    addr32      Assume 32bit address size
    addr16      Assume 16bit address size
    data32      Assume 32bit data size
    data16      Assume 16bit data size
    suffix      Always display instruction suffix in AT&T syntax
Report bugs to <http://bugzilla.redhat.com/bugzilla/>.

输出disas帮助信息(gdb中)

(gdb) help disas
Disassemble a specified section of memory.
Default is the function surrounding the pc of the selected frame.
With a /m modifier, source lines are included (if available).
With a /r modifier, raw instructions in hex are included.
With a single argument, the function surrounding that address is dumped.
Two arguments (separated by a comma) are taken as a range of memory to dump,
  in the form of "start,end", or "start,+length".

Note that the address is interpreted as an expression, not as a location
like in the "break" command.
So, for example, if you want to disassemble function bar in file foo.c
---Type <return> to continue, or q <return> to quit---

方法一:

objdump -d bomb > bomb.asm

方法二:

(gdb) disas phase_1

接下来看到的就是这一串。

image-20231208111452424

汇编看不懂去看这篇《汇编语言学习笔记》总结的很全,不过不是ATT格式的汇编。

可以看到传了一个0x402400给寄存器esi,然后就调用了strings_not_equal方法,我们再进去看看strings_not_equal方法里是啥。

image-20231208111933109

大概看了一下发现,就是把寄存器rdi、rsi的值分别赋值给rbx和rbp,后面用以rbx和rbp的值作为地址去寻址(存储器寻址),找到了之后比较一下这两个位置开始的字符串相不相等,不相等返回1,相等返回0。我这里只截了部分,需要自己去打开文件研究。

接下来回到phase_1,可以发现%eax不为0的时候就直接会进入下一条指令explode_bomb引爆炸弹,而%eax为0即成功解除炸弹。

所以我们就要看看rdi和rsi到底是什么需要相等,rsi的值我们可以直接去访问代码中给的地址0x402400,指令如下。

x/s 0x402400

结果如下:

image-20231208120701082

所以只要保证%rdi的值也是这一串就可以解除炸弹。动动聪明的小脑瓜就能猜到,当你输入这一串这样的字符串,他就会被赋值到%rdi寄存器所指向的内存地址中,当然不能用猜,我们要亲眼看到才行。

接下来让我们去找到标准输入函数。

image-20231208120847651

进汇编文件搜索这个read_line,可以看到这个函数有以下东西。

image-20231208120933277

我们需要全部看完吗?不,不需要,让我们想想返回值存在哪?%rax对吧,那就回调用它的函数中看看rax赋值给了谁,显而易见,我们去看看主函数。

image-20231208121043091

往下找找,可以看到read_line在299行。

image-20231208121123450

好好好,果然把rax赋值给rdi了。

第一个炸弹就这样轻松解除,我们只要简简单单跑一下bomb文件,输入之前查看的那一串字符串就行,结果如下。

image-20231208121358550

此时就提示你第一个炸弹解除了,然后可以发现程序没有结束,让你输入next one,也就是说我要连续解决6个炸弹才行,那比如我搞了三天才解决了第二题,我已经忘了第一题答案咋办,这时我们就想起来一开始规则说的,你可以把答案放在一个文件里,然后在gdb中run时后面加一个参数放文件名,他就会读取你的文件内容作为输入啦(gdb命令大全自己搜一下噢),操作如下。

1、答案放入res.txt

image-20231208121702429

2、run加上参数

image-20231208121751424

但是这里出现了一个很奇怪的问题,为啥还是爆炸了?经过我打断点一步一步调试去查看寄存器rdi和rsi地址的值发现,不知道为什么它竟然把我最后一个字符**“.”**去掉了!!!!!!我非常不爽的在我的res.txt文件第一行最后按了一个空格。

image-20231208122031134

结果发现,炸弹解除了。

image-20231208122048228

初步怀疑(%rsi)中最后也是有一个字符串终止符的,只是显示的时候看不到,于是我手动加上(手动狗头)。

后来解其他炸弹的时候还是会有问题,于是我不用外部编辑器,直接用vim命令去编辑,发现没问题,所以有可能是因为外部编辑器编辑出来内容编码会不同。

phase_2

复现循环

接下来让我们看phase_2,如下图。

image-20231208224530905

执行的内容相同,直接进汇编文件看看,如下图。

image-20231208224757623

在361行调用了read_six_numbers函数,大概介绍一下这个函数,它里面使用了一个sscanf的函数,函数原型如下图。

image-20231208225205410

image-20231208235551549

根据前置知识中的图可知,第一个参数存在了rdi寄存器中,第二个参数表示格式,存在了rsi寄存器中,下图是查看内存显示的内容。

image-20231208225432574

第三个参数存在rdx,第四个参数存在rcx,第五个参数存在r8,第六个参数存在r9,第七第八个参数存在栈中,第2到8的参数对应的就是格式中输入的6个整数,并且这六个数依次放在栈中,那么我们就搞清楚要输入什么东西了,那具体输入的内容让我们回到phase_2中看看。

362行rsp中的内容和1比较,如果不同就会爆炸,所以我们栈rsp的位置就要存放整数1,即我们输入的第一个数应为整数1。

接着我们跳转到地址400f30即375行,因为整数int4个字节,所以375行执行栈指针加4赋值给rbx,栈指针加18赋值给rbp作为终止位置,注意这里的18是16进制,代表的是24。

然后跳转到366行,rbx-4又回到rsp的位置,取出内容即为刚才的1赋值给eax,eax自己加自己即eax*2变为2,2在跟rbx当前位置的内容比较,所以第二位输入应为2。

跳转到371行,rbx再加4判断是否达到最终位置rbp,若没有再次回到366行执行相同的操作,此时eax*2变为4,4再跟rbx当前位置的内容比较,所以第三位输入应为4。

同理,循环6次直到rbx和rbp位置相同,终止循环,所以输入6位数分别为1 2 4 8 16 32。

跑一下验证:

image-20231208231049875

image-20231208231035717

phase_3

复现switch case

直接看phase_3函数,如下图。

image-20231209010052526

image-20231209010106480

同样,根据第二个参数rsi寄存器中存的格式来进行输入,如下图。

image-20231209010223082

可以看到要输入两个整数。

接着往下看,第389行调用了sscanf,sscanf函数会返回参数的个数,所以返回值应该是2(如果你输入格式正确的话)。

390行返回值存在rax中,rax的值为2比1大,所以跳转到393行。

从这一行可以看出rdx也就是第一个参数的值不能大于7且不能小于0(因为这里是无符号比较),否则就会跳转到爆炸函数。

接着跳转到0x402470的地方去执行,我们来打印看一下那一块有个啥。

image-20231209010811253

之所以用8是因为rdx的值不能大于7,所以rax的值也不会超过7(因为rdx赋值给rax了)那么我们就只用看后续8个命令就行。

可以看到当一个参数为0时(即rdx=0)跳转到0x400f7c,当第一个参数为1跳转到0x400fb9,依次类推。

我们就选第一个去看看,可以看到在第397行,它执行的操作是把0xcf给rax,然后跳转到415行,去和第二个参数rcx比较,如果相同就返回,炸弹解除,所以可以在0-7中任选一个数,然后找到它对应的第二个参数,比如我这里就输入

0和207

输出结果如下:

image-20231209011506182

第三题就解决啦,实验已经过半啦!接下来让我们乘胜追击!

phase_4

复现递归

image-20231209103805183

这题打印rsi可以看出输入的参数同样是两个整数,并且第一个整数不能大于14,否则触发爆炸,难点在fun4中,需要判断func4是否能返回0,如果为0才能解除炸弹,这里的func4需要全部看完推一边才能懂,这里配上我推理时的注释,最后发现当第一个输入为7、3、1、0时可以返回0否则就会执行一下441行代码。

离开func4函数后很容易看出后面的语句表示第二个参数为0

所以答案为7 0 或 3 0 或 1 0 或 0 0。

image-20231209103838911

phase_5

直接上phase_5代码。

image-20231209133031090

image-20231209133042204

在这里我们先看一下473行,这里在fs段寄存器中取段地址,偏移量为0x28,拿到这个值作为金丝雀值,作为栈保护的验证,以防栈收到攻击。475行把金丝雀值放在了0x18(rsp)的位置,从这可以推断出只使用了0x18(rsp)之前的栈位置,从而从后面的cmp 0x6能推断出来这里只能输入6个字符,再看看503行到507行,这里就是金丝雀值的验证,没被改变说明栈没有被攻击,金丝雀值概念如下图。

image-20231209133441818

继续往下看,rbx存了rdi的值,指向输入字符串的开头,在482行,rbx加上了一个rax*1的偏移值然后赋值给ecx,而rax一开始等于0,所以也就是取出输入字符串的第一个字符,后续操作就是把这个字符的低四位取出(高四位任意,因为会被and操作变为0)作为索引,将该索引在地址0x4024b0指向的字符串中取出字符,放在(rsp)+ 0x10 + rax * 1中,然后循环,rax自增,依次把字符放入栈中,最后rdi指向0x10(rsp)栈顶,和rsi指向的内存地址0x40245e中的内容比较,若相同则解除炸弹。

我们来看看0x4024b0和0x40245e中放了啥。

image-20231209140937734

也就是说,根据索引从上面长长的字符串中取出下面的“flyers”,可以发现我们需要的索引下标是9 15 14 5 6 7,那就在ASCII码表中找后四位是这些值的字符就行。

我这里以**)/>%FG**作为答案。

image-20231209141227674

记录一下,在这里发现的隐藏关卡:

image-20231209141248902

phase_6

这个题代码量比较大分成6个部分

  1. 401106 读出6个int到rsp中,要求不大于6且每个值不相同
  2. 40110e 检测六个int是否为[1,2,3,4,5,6]的任意排序
  3. 401153 将这个六个int 变为 7 - int
  4. 40116f 将 第 7 - int个链表元素 放到 rsp0x20 + int 的相对位置 * 4的位置
  5. 4011bd 链表, 被换成了 咱们数组中的顺序 , 和题目无关
  6. 4011d2 检测要求现在的数组降序

下面是六个链表的元素

(gdb) x /d 0x6032d0
0x6032d0                     <node1> :            332

(gdb)x /d 0x6032e0 
0x6032e0                     <node2>:              168

(gdb) x /d 0x6032f0
0x6032f0                   <node3> :              924

(gdb) x /d 8x603300
0x603300                     <node4>:              691

(gdb) x /d 0x603310
                            0x603310 <node5> :     477
(gdb)x /d 0x603320 
                     0x603320 <node6> :            443

所以链表降序序列是:3 4 5 6 1 2

7 减去上面的序列之后就是:答案:4 3 2 1 6 5 *(*号和上面的意思一样)

4 3 2 1 6 5

全注释的代码

00000000004010f4 <phase_6>:
  4010f4:   41 56                   push   %r14
  4010f6:   41 55                   push   %r13
  4010f8:   41 54                   push   %r12
  4010fa:   55                      push   %rbp
  4010fb:   53                      push   %rbx
  4010fc:   48 83 ec 50             sub    $0x50,%rsp !# 开了80个字节
  401100:   49 89 e5                mov    %rsp,%r13
  401103:   48 89 e6                mov    %rsp,%rsi
  401106:   e8 51 03 00 00          callq  40145c <read_six_numbers>  !# 读出六个int
  40110b:   49 89 e6                mov    %rsp,%r14                  !# rsp0

  40110e:   41 bc 00 00 00 00       mov    $0x0,%r12d            !# 初始化
                !# 检测六个int是否为[1,2,3,4,5,6]的任意顺序     r12d 是下标  
  401114:   4c 89 ed                mov    %r13,%rbp             !# rsp0
  401117:   41 8b 45 00             mov    0x0(%r13),%eax        !# 每一个一个int number 
  40111b:   83 e8 01                sub    $0x1,%eax             !# number --  下面要unsigned 比较,所以不能<= 0
  40111e:   83 f8 05                cmp    $0x5,%eax             !# number <= 5 就跳过下一行  说明原输入 <= 6  且 > 0 即[1,6]
  401121:   76 05                   jbe    401128 <phase_6+0x34>
  401123:   e8 12 03 00 00          callq  40143a <explode_bomb>
  401128:   41 83 c4 01             add    $0x1,%r12d            !# r12d ++
  40112c:   41 83 fc 06             cmp    $0x6,%r12d            !# 循环六次 
  401130:   74 21                   je     401153 <phase_6+0x5f>
  401132:   44 89 e3                mov    %r12d,%ebx            !# 下一个数
         !# 二重循环, 检测后面的数
    401135: 48 63 c3                movslq %ebx,%rax             
    401138: 8b 04 84                mov    (%rsp,%rax,4),%eax    !# rsp[4 * ebx] -> eax
    40113b: 39 45 00                cmp    %eax,0x0(%rbp)        !# 每一个数 与后面的数进行比较,相等就爆炸
    40113e: 75 05                   jne    401145 <phase_6+0x51>
    401140: e8 f5 02 00 00          callq  40143a <explode_bomb>
    401145: 83 c3 01                add    $0x1,%ebx            !# 枚举后面的数
    401148: 83 fb 05                cmp    $0x5,%ebx
    40114b: 7e e8                   jle    401135 <phase_6+0x41>
  40114d:   49 83 c5 04             add    $0x4,%r13         !# 枚举下一个数
  401151:   eb c1                   jmp    401114 <phase_6+0x20>

  401153:   48 8d 74 24 18          lea    0x18(%rsp),%rsi  !# rsp24
  401158:   4c 89 f0                mov    %r14,%rax        !# rsp0
  40115b:   b9 07 00 00 00          mov    $0x7,%ecx
            !# cur = 7 - cur, 做六次这个循环      将之前读入的int 取 7 - int 的 值
  401160:   89 ca                   mov    %ecx,%edx         
  401162:   2b 10                   sub    (%rax),%edx      !# edx -= 参数 
  401164:   89 10                   mov    %edx,(%rax)      !# 参数 = edx
  401166:   48 83 c0 04             add    $0x4,%rax        !# 上移
  40116a:   48 39 f0                cmp    %rsi,%rax        !# 直到把六个int都改变了
  40116d:   75 f1                   jne    401160 <phase_6+0x6c>

  40116f:   be 00 00 00 00          mov    $0x0,%esi
  401174:   eb 21                   jmp    401197 <phase_6+0xa3> 

    !# 这一段是 将 第 7 - int个链表元素 放到 rsp0x20 + int的相对位置 * 2 的位置
    !# 下面是这个链表中node的原型
    !#   struct node{   // 在这里需要注意的是, 虽然value是int ,但是因为64位对齐, 所以我们在访问时仍然是 +8 
    !#      int value;
    !#       node* next;
    !#  };
                    !# 寻找 第 7 - int个链表元素 
  401176:   48 8b 52 08             mov    0x8(%rdx),%rdx  !# rdx = *rdx + 8  这里是 p = p -> next 
  40117a:   83 c0 01                add    $0x1,%eax
  40117d:   39 c8                   cmp    %ecx,%eax    !# 比较这个差值,按差值的倍数 加
  40117f:   75 f5                   jne    401176 <phase_6+0x82>
  401181:   eb 05                   jmp    401188 <phase_6+0x94>
                                        !# 0x6032d0是链表头指针
  401183:   ba d0 32 60 00          mov    $0x6032d0,%edx    !# 放入八个字节的 rdx

  401188:   48 89 54 74 20          mov    %rdx,0x20(%rsp,%rsi,2) !#  rdx -> rsp[rsi *2 + 0x20 ] 
  40118d:   48 83 c6 04             add    $0x4,%rsi       !# 0x20 - 0x50 
  401191:   48 83 fe 18             cmp    $0x18,%rsi
  401195:   74 14                   je     4011ab <phase_6+0xb7>
                          !# 放入相对位置
  401197:   8b 0c 34                mov    (%rsp,%rsi,1),%ecx  !# rsp[rsi] -> ecx
  40119a:   83 f9 01                cmp    $0x1,%ecx           !# 如果这个数 <= 1 就去 401183,
  40119d:   7e e4                   jle    401183 <phase_6+0x8f>
  40119f:   b8 01 00 00 00          mov    $0x1,%eax           !# 否则回到 401176
  4011a4:   ba d0 32 60 00          mov    $0x6032d0,%edx
  4011a9:   eb cb                   jmp    401176 <phase_6+0x82>

  4011ab:   48 8b 5c 24 20          mov    0x20(%rsp),%rbx     !#rbx最初结点
  4011b0:   48 8d 44 24 28          lea    0x28(%rsp),%rax     !# rax是下一个点
  4011b5:   48 8d 74 24 50          lea    0x50(%rsp),%rsi
  4011ba:   48 89 d9                mov    %rbx,%rcx   !# rsp0x20

    !# 这里相当于 nextArrayPos = rax, cur = rcx  然后让 cur ->next = array[nextArrayPos]
    !# 也就是说这个链表呢, 被换成了 咱们数组中的顺序,这一段代码和本题无关!
  4011bd:   48 8b 10                mov    (%rax),%rdx   !#  将数组中下一个点的值 赋给 rdx
  4011c0:   48 89 51 08             mov    %rdx,0x8(%rcx)  !# rcx -> 数组中的下一个点
  4011c4:   48 83 c0 08             add    $0x8,%rax       !# 换下一个地址
  4011c8:   48 39 f0                cmp    %rsi,%rax
  4011cb:   74 05                   je     4011d2 <phase_6+0xde>
  4011cd:   48 89 d1                mov    %rdx,%rcx       !# rcx 后移
  4011d0:   eb eb                   jmp    4011bd <phase_6+0xc9>

    !# rdx 下来之后就指向最后一个结点 , 让rdx ->next = NULL
  4011d2:   48 c7 42 08 00 00 00    movq   $0x0,0x8(%rdx)
  4011d9:   00 
     !#  让每一个 都curArrayValue >= nextArrayValue  即 降序
  4011da:   bd 05 00 00 00          mov    $0x5,%ebp 
  4011df:   48 8b 43 08             mov    0x8(%rbx),%rax   !# rsp0x20 + 8 nextArrayValue = rax
  4011e3:   8b 00                   mov    (%rax),%eax 
  4011e5:   39 03                   cmp    %eax,(%rbx)      !# rbx 是 curArrayValue
  4011e7:   7d 05                   jge    4011ee <phase_6+0xfa> !# 如果 curArrayValue >= nextArrayValue 就继续,否则爆炸
  4011e9:   e8 4c 02 00 00          callq  40143a <explode_bomb>
  4011ee:   48 8b 5b 08             mov    0x8(%rbx),%rbx
  4011f2:   83 ed 01                sub    $0x1,%ebp        !# 循环5 - 1 + 1 =5 次 
  4011f5:   75 e8                   jne    4011df <phase_6+0xeb>
  4011f7:   48 83 c4 50             add    $0x50,%rsp     !# 恢复
  4011fb:   5b                      pop    %rbx
  4011fc:   5d                      pop    %rbp
  4011fd:   41 5c                   pop    %r12
  4011ff:   41 5d                   pop    %r13
  401201:   41 5e                   pop    %r14
  401203:   c3                      retq

secret_phase

c文件中有这样一句话

image-20231209153748813

我们就可以去找找是不是有东西被我们忽略了,也就是之前在phase_5中我们发现的隐藏关卡。找了一会发现有一个secret_phase的函数,我们看看它是在哪里被调用的。

image-20231209185903208

可以看到898行,测试后就会发现,当你6个phase都完成后,它才会等于6,顺序执行,接着900-904行就是参数设置。

查看903行的0x402619可看到输入参数格式为 %d %d %s,查看904行的0x603870地址的内容发现正式我们第四个phase的输入。

image-20231209191343872

所以想要触发secret_phase就要在第四个phase处更改一下输入,看到906行的sscanf返回的值要和0x3相等也能看到需要输入三个参数,从908行给定的固定地址就能发现第三个参数应该输入什么。

image-20231209191427100

这样我们就找到了secret_phase的入口。

接着进入secret_phase函数里看看。

image-20231209191705836

这里注释也做的很详细,就只用看一下strtol这个函数的原型就能理解这里,函数原型如下。

image-20231209192415820

接着我们注意到0x6030f0这个固定地址作为了参数传递给fun7,我们去看看里面有什么。

image-20231209192531313

有一个值,大小是36(十六进制24),具体什么含义还不知道,让我们去看看函数fun7。

image-20231209192651180

如果顺着看很难解释,所以在理顺之后把结论作为前提来讲会容易一点,经过分析这是一个二叉搜索树(BST),每个节点有一个值,一个节点占32个字节,存了一个值,一个左子树节点指针,一个右子树节点指针,我们可以查看一下0x6030f0后面的内容。

image-20231209193011026

可以看到n1的左子树指向地址0x00603110,右子树地址指向0x00603130,由此我们可以画出二叉树结构,如下图。

image-20231209193812864

鼠标写的有点丑/(ㄒoㄒ)/~~,不过应该能看懂吧。

接着继续说代码605行,如果输入的参数 >= 当前的节点值,跳转到611行,如果值相同,返回0。

如果 输入的参数 > 当前节点值,地址加0x10,去右子树找,递归调用fun7,返回值为fun7 * 2 + 1。

如果输入的参数 < 当前节点值,地址加0x8,去左子树找,递归调用fun7,返回值为fun7 * 2。

可以写出如下伪代码。

int fun7(node cur, int input){
	if(cur.val == input){
		return 0;
	}
	if(cur.val < input){
		return fun7(cur.right, input) * 2 + 1;
	}
	if(cur.val > input){
		return fun7(cur.left, input) * 2;
	}
}

这样应该很形象了,再根据secret_phase中要求返回值等于2,稍微推理一下就知道答案应该是22或者20,如果不是这个答案就会一直往下找一直找到rdi变成0最后返回-1。

最后所有的答案如下(不是唯一答案)。

image-20231209195001900

运行结果如下:

image-20231209195025199
参考文章:
GDB用法及命令大全

GDB基本调试命令

GDB常用命令大全

gdb查看内存 (x/100xb)

学姐文章:《CSAPP:BombLab 详细解析》

文章来源:https://blog.csdn.net/yueliangmua/article/details/134899742
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。