csapp的第二个实验.
之前稀里糊涂的做过,但是并不是太懂.现在重做一下.
开始的准备
CS:APP3e, Bryant and O’Hallaron (cmu.edu)
实验网址,有好几个lab.选择Bomb lab的self-study就行了.最新版2016年的.
原来做的时候老师没有发文档,现在有个bomblab文档,很有用.
我还找到了这篇pdf的机翻,不过建议一起看.《深入理解计算机系统》实验二Bomb Lab下载和官方文档机翻_达的程序员日记-CSDN博客
说实话,到现在我还是有点不懂
需要一点对于二进制文件和汇编基础.
- ELF文件头:存储文件类型和大小的有关信息,以及文件加载后程序执行的入口信息
- 程序头表:可执行文件的数据在虚拟地址空间中的组织方式,以及段的数目,位置以及用途
- 节:将文件分成一个个节区,每个节区都有其对应的功能,如符号表,哈希表等
- 段:就是将文件分成一段一段映射到内存中。段中通常包括一个或多个节区
- 节头表:存储段的附加信息
大端模式与小端模式
大端:多字节值的大端存储在该值的起始位置;(老大站排头为大)
小端:多字节值的小端存储在该值的起始位置
我这里是小端
objdump与gdb常用命令
(其实我觉得命令太多了记不完的,记住重要的需要时再查就行了)
objdump
-f 文件头信息
-h 节头表信息 输出目标文件中节表(Section Table)中所包含的所有节头(Section Header)的信息
-x 所有的头信息 包括文件头,程序头和节头还包括符号表信息
-d 反汇编 可执行节(段)信息
-D 反汇编所有节(段)信息
-S 源代码与汇编混合 编译时需要-g
-s 所有节以及二进制
-t 显示符号表
-T 动态符号表
文件头
节(段)头信息
反汇编结果
符号表
Linux objdump 命令用法详解-Linux命令大全(手册) (ipcmen.com)
节(段)介绍(注意.bss与.data .rodata)
- .text:已编译程序的机器代码。
- .rodata:只读数据,比如printf语句中的格式串和开关(switch)语句的跳转表。
- .data:已初始化的全局C变量。局部C变量在运行时被保存在栈中,既不出现在.data中,也不出现在.bss节中。
- .bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。目标文件格式区分初始化和未初始化变量是为了空间效率在:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。
- .symtab:一个符号表(symbol table),它存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
- .rel.text:当链接噐把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示链接器包含这些信息。
- .rel.data:被模块定义或引用的任何全局变量的信息。一般而言,任何已初始化全局变量的初始值是全局变量或者外部定义函数的地址都需要被修改。
- .debug:一个调试符号表,其有些表目是程序中定义的局部变量和类型定义,有些表目是程序中定义和引用的全局变量,有些是原始的C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表。
- .line:原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译驱动程序时,才会得到这张表。
- .strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。
gdb
gdb
运行二进制文件 run 简介r 运行 直到执行整个程序或断点处
break 简写b 打断点
注意打断点可以在 行号 函数 内存地址处
delete 简写d 删除断点
x/nfu addr 按照指定的格式而非程序的数据类型考察内存数据(这个指令很有意思)
continue 简写c 继续执行被调试程序 直到下一个断点或结束
step 简写s 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数
next 简写n 执行一行源程序代码,此行代码中的函数调用也一并执行
注意对于汇编我们需要使用si,ni代替
print 简写p 显示变量值
display 设置程序中断后欲显示的数据及其格式 /i 表示以十六进行显示
undispaly,取消先前的display设置,编号从1开始递增
quit 简写q 退出
值得一提的是gdb tui模式,
1
2
3
4
5
6
7
8
9 gdb -tui #进入
#或者gdb进入后
focous #进入tui
ctrl+x a #退出tui
layout asm|reg|split|src #展示相关信息
ctrl+p ctrl+n #gdb上一条 下一条命令
Ctrl + L #刷新窗口
Ctrl + x #再按1:单窗口模式,显示一个窗口
Ctrl + x #再按2:双窗口模式,显示两个窗口先知道这么多吧.
注意汇编指令也有格式 可以看出这里是AT&T格式
同时注意汇编指令 call ret leave
1
2
3
4
5
6
7
8
9
10
11 call addr:
push rip
mov addr rip
#call指令 调用函数
ret :
pop rip
#ret指令 返回
leave :
mov rbp rsp
pop rbp
#leave指令 关闭栈帧汇编语言CALL和RET指令:调用一个过程 (biancheng.net)
还有vim查看16进制,因为需要修改二进制可执行文件
1
2
3
4
5
6 #打开file文件
vim file
#在命令模式下输入.. 以16进制显示
:%!xxd
#在命令模式下输入.. 切换回默认显示
:%!xxd -rreadelf
-h ELF文件开始的文件头信息
-l 程序头 节到段的映射
-S 显示节头信息
-s 符号表信息
-r 显示可重定位段的信息
-d 显示动态段的信息
正式开始
initial_bomb
首先先看源文件,不得不说这个作者真的很humorous.
注意如果运行时无参数就读取输入,否则读取文件.
然后进入了initialize_bomb函数,我们尝试看一看.
1 objdump -d bomb这样可以查看.text段里的信息.
查看initialize_bomb这个函数
1
2
3
4
5
6
7 00000000004013a2 <initialize_bomb>:
4013a2: 48 83 ec 08 sub $0x8,%rsp
4013a6: be a0 12 40 00 mov $0x4012a0,%esi
4013ab: bf 02 00 00 00 mov $0x2,%edi
4013b0: e8 db f7 ff ff callq 400b90 <signal@plt>
4013b5: 48 83 c4 08 add $0x8,%rsp
4013b9: c3首先开栈,注意这是x86_64架构的,mov指令是mov src->dst,这跟intel指令不一样.
AT&T 格式 Intel 格式 movl -4(%ebp), %eax mov eax, [ebp - 4] movl array(, %eax, 4), %eax mov eax, [eax*4 + array] movw array(%ebx, %eax, 4), %cx mov cx, [ebx + 4*eax + array] movb $4, %fs:(%eax) mov fs:eax, 4
寄存器 描述 rdi 传递第一个参数 rsi 传递第二个参数 寄存器介绍这个可以看一看
这里有一篇很好的文章x86-64 规定只有6个寄存器来存参数,那 C 函数为什么还能超过6个参数呢? (mengkang.net)
当了解了汇编基础知识(其实可以面向搜索引擎学习,遇到不懂的指令去搜索就可以了)后,我们知道esi值第二个参数,edi是第一个参数.然后callq 400b90转去执行这个函数.
注意jmpq *0x2024c2(%rip)表示跳转到 %rip+0x2024c2地址所存储的值,即0x603058地址存储的值(是一个地址)
这个函数叫signal@plt,我也不懂,我在网上搜索后.这跟链接,重定位相关重定位和链接
Linux Debugging(七): 使用反汇编理解动态库函数调用方式GOT/PLTanzhsoft的技术专栏-CSDN博客动态库反汇编
assembly - What does @plt mean here? - Stack Overflow
在实际的可执行程序或者共享目标文件中,GOT表在名称为.got.plt的section中,PLT表在名称为.plt的section中
GOT是Global Offset Table,是保存库函数地址的区域。程序运行时,库函数的地址会设置到GOT中。由于动态库的函数是在使用时才被加载,因此刚开始GOT表是空的。地址的设置就涉及到了PLT,Procedure Linkage Table,它包含了一些代码以调用库函数,它可以被理解成一系列的小函数,这些小函数的数量其实就是库函数的被使用到的函数的数量。
简单来说,PLT就是跳转到GOT中所设置的地址而已。如果这个地址是空,那么PLT的跳转会巧妙的调用_dl_runtime_resolve去获取最终地址并设置到GOT中去。由于库函数的地址在运行时不会变,因此GOT一旦设置以后PLT就可以直接跳转到库函数的真实地址了。
stack overflow中的解答,类似的
printf@plt
is actually a small stub which (eventually) calls the realprintf
function, modifying things on the way to make subsequent calls faster.The real
printf
function may be mapped into any location in a given process (virtual address space) as may the code that is trying to call it.So, in order to allow proper code sharing of calling code (left side below) and called code (right side below), you don’t want to apply any fixups to the calling code directly since that will restrict where it can be located in other processes.
让我们也试试看吧.
首先signal@plt跳转到一个地址(*0x603058),这里需要gdb看一看
所以jmp到了0x00400b96,这个这份地址又是什么呢,有点眼熟?注意signal@plt中的汇编代码,貌似我们又跳回来了.
使用gdb的x指令查看内存的数据,指定x /5i 表示往后五个单位的指令(i表示指令),可以看出先把0x0b压栈,又跳转一个地址0x400ad0
使用gdb执行到0x400ad0处
我在这卡了很久,因为我以为跳转的就是这个地址,实际上这是32位的值,是原本64位地址的低地址.jmpq跳转到64位地址.这也跟我gdb使用不熟练有关
然后执行函数
这中间又跳转执行了很多函数。
最后又跳了回来.
GOT表和PLT表知识详解_qq_18661257的专栏-CSDN博客_got表
再来看看传入的参数0x4012a0,可以看到这是函数的地址
可以知道在initial_bomb中使用了signal函数,传入信号量2,调用写的sig_handler函数.
sig_handler中进行了一些设置,主要捕获中断信号,针对ctrl+c.
C 库函数 – signal() | 菜鸟教程 (runoob.com)
好了,initial_bomb就解析到这吧.
phase_1
还是很好理解的,读取输入并传输函数.
第一行就不说明了,使用gdb调试第二行.
1
2 b phase_1 #打断点
r #运行随便进行输入,然后进入函数.接下来就是艰难的读汇编代码阶段.
1
2
3
4
5
6
7
8 0x400ee0 <phase_1> sub $0x8,%rsp
0x400ee4 <phase_1+4> mov $0x402400,%esi
0x400ee9 <phase_1+9> callq 0x401338 <strings_not_equal>
0x400eee <phase_1+14> test %eax,%eax
0x400ef0 <phase_1+16> je 0x400ef7 <phase_1+23>
0x400ef2 <phase_1+18> callq 0x40143a <explode_bomb>
0x400ef7 <phase_1+23> add $0x8,%rsp
0x400efb <phase_1+27> retq可以看到首先是开栈,栈顶移动8个字节,然后将$0x402400赋值给函数第二个调用参数,然后调用函数,通过名字就可以知道是判断字符串是否相等.然后测试结果根据结果进行跳转.je 表示相等就跳转否则调用炸弹爆炸.
我们到stings_not_equal中看看,其实不用细看,我们主要想知道进行比较的是什么字符串,它们的地址是什么.
首先需要肯定的是,调用字符串比较函数需要参数,%esi是第二个参数.我们看看这个寄存器的值
x /s显示地址上的值,以字符串形式.
使用info r查看寄存器%rdi值
看到了之前随意输入的参数
所以字符串修改为0x402400上的字符串即可.
参数可以为文件,每行一个题答案.
1 ./bomb myanswers.txtphase_defused函数,我们也可以看看.估计就是输出一些语句就行了.
phase_2
1
2
3
4
5
6 /* The second phase is harder. No one will ever figure out
* how to defuse this... */
input = read_line();
phase_2(input);
phase_defused();
printf("That's number 2. Keep going!\n");可以在gdb使用set args设置参数为答案文本.
然后继续打断点,打在phase_2即可
可以看到先读取了调用函数6个数字,在这之前将栈顶地址移到$rsi中,可以看看这个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 0x40145c <read_six_numbers> sub $0x18,%rsp │
│ 0x401460 <read_six_numbers+4> mov %rsi,%rdx │
│ 0x401463 <read_six_numbers+7> lea 0x4(%rsi),%rcx │
│ 0x401467 <read_six_numbers+11> lea 0x14(%rsi),%rax │
│ 0x40146b <read_six_numbers+15> mov %rax,0x8(%rsp) │
│ 0x401470 <read_six_numbers+20> lea 0x10(%rsi),%rax │
│ 0x401474 <read_six_numbers+24> mov %rax,(%rsp) │
│ 0x401478 <read_six_numbers+28> lea 0xc(%rsi),%r9 │
│ 0x40147c <read_six_numbers+32> lea 0x8(%rsi),%r8 │
│ 0x401480 <read_six_numbers+36> mov $0x4025c3,%esi │
│ 0x401485 <read_six_numbers+41> mov $0x0,%eax │
│ 0x40148a <read_six_numbers+46> callq 0x400bf0 <__isoc99_sscanf@plt> │
│ 0x40148f <read_six_numbers+51> cmp $0x5,%eax │
│ 0x401492 <read_six_numbers+54> jg 0x401499 <read_six_numbers+61> │
│ 0x401494 <read_six_numbers+56> callq 0x40143a <explode_bomb> │
│ 0x401499 <read_six_numbers+61> add $0x18,%rsp │
│ 0x40149d <read_six_numbers+65> retq可以看到调用了sscanf@plt也就是后面会执行sanf函数,需要两参数,一个格式字符串.看到mov $0x4025c3 %esi,看一下是什么
1
2 (gdb) x 0x4025c3
0x4025c3: "%d %d %d %d %d %d"也就是六个整数.这一有一个知识点,传函数参数时,如果数量比较多那么就会使用栈.也就是使用%rbp或%rsp指示,同时函数参数是从右往左开始的,也就是最后一个输入的参数会先被传入寄存器或栈中.
再看看%edi
1
2 (gdb) x /s $edi
0x6037d0 <input_strings+80>: "1 12 23 34 13 100"即传入了这个字符串到read_six_numbers函数.
注意到后面有jg即大于跳转,也就是输入的正确参数要大于5,即6个(跟函数名称一样就行了).然后跳出这个函数.继续执行
1
2
3 0x400f05 <phase_2+9> callq 0x40145c <read_six_numbers>
0x400f0a <phase_2+14> cmpl $0x1,(%rsp)
0x400f0e <phase_2+18> je 0x400f30 <phase_2+52>后面又继续跳转到0x400f30(当然这里有条件寄存器rsp值等于1,后面会说)
这里进行了赋值后又继续跳转到0x400f17
1
2
3
4
5 0x400f17 <phase_2+27> mov -0x4(%rbx),%eax
0x400f1a <phase_2+30> add %eax,%eax
0x400f1c <phase_2+32> cmp %eax,(%rbx)
0x400f1e <phase_2+34> je 0x400f25 <phase_2+41>
0x400f20 <phase_2+36> callq 0x40143a <explode_bomb>可以看出又会进行比较值相等才能避免爆炸,相等的话又会跳转到一个地址0x400f25.
0x400f25
1
2
3
4
5
6
7
8
9
10
11 0x400f25 <phase_2+41> add $0x4,%rbx │
│ 0x400f29 <phase_2+45> cmp %rbp,%rbx │
│ 0x400f2c <phase_2+48> jne 0x400f17 <phase_2+27> │
│ 0x400f2e <phase_2+50> jmp 0x400f3c <phase_2+64> │
│ 0x400f30 <phase_2+52> lea 0x4(%rsp),%rbx │
│ 0x400f35 <phase_2+57> lea 0x18(%rsp),%rbp │
│ 0x400f3a <phase_2+62> jmp 0x400f17 <phase_2+27> │
│ 0x400f3c <phase_2+64> add $0x28,%rsp │
│ 0x400f40 <phase_2+68> pop %rbx │
│ 0x400f41 <phase_2+69> pop %rbp │
│ 0x400f42 <phase_2+70> retq可以看到这里又会进行比较如果不等又会跳回去,相等才能脱离苦海.如果你想锻炼你的汇编代码阅读能力可以直接硬读,我这直接设置6个特别的数值然后看看寄存器存储的什么值.
跳出读取6个值的函数后,跳回0x400f0a,这里对寄存器$rsp值进行了比较(如果你设置特殊值进行多次测试就知道这个值等于什么了)
这里我们回去关注一下%rsp的值(关于寄存器符号我这里%与$混合使用)
所以我们需要好好探讨一下read_six_numbers中传给scanf的参数.
关于函数传参寄存器中存储的值.
C语言参数传递所使用的寄存器天马行空-CSDN博客参数寄存器
由于%edi是phase_2传入read_six_numbers的参数,所以read_six_numbers向scanf传参是从%esi开始,需要七个参数(6个数字与一个格式化参数).rdx,rcx,r8,r9也是传参的寄存器,算上%esi就有五个,还有两个就需要用栈了.
可以看到在read_six_numbers中将%rsi传给了%rdx,这是第一个参,然后将0x4(%rsi)存储的值赋给%rcx,就是第二个参数.(一个值4字节,但一个寄存器大小是8字节).也就是说%rcx存储的是第二个参数的地址.
还是画图好理解.开始的4个用寄存器存,后面的寄存器不够雷人用栈存.当scanf执行完后,%rsp = %rsp’.所以(%rsp)就是输入的第一个值.返回值%eax就是6(正确读取了6个值)
所以第一个值为1.至于为什么是第一个而不是最后一个,因为要存储最后两个值寄存器不够所以入栈了,它们值存储在距离phase_2栈帧的栈顶最远的地方.
1
2
3 0x400f30 <phase_2+52> lea 0x4(%rsp),%rbx
0x400f35 <phase_2+57> lea 0x18(%rsp),%rbp
0x400f3a <phase_2+62> jmp 0x400f17 <phase_2+27>然后便是将%rbx赋值为第二个值的地址,栈底赋值为%rsp+24.24个字节是6个int大小.
1
2
3 0x400f17 <phase_2+27> mov -0x4(%rbx),%eax
0x400f1a <phase_2+30> add %eax,%eax
0x400f1c <phase_2+32> cmp %eax,(%rbx)然后将上一个值赋给%eax,再加倍.判断与当前值的大小.相等才能避免爆炸
1
2 0x400f25 <phase_2+41> add $0x4,%rbx
0x400f29 <phase_2+45> cmp %rbp,%rbx再将当前值指向下一个(4字节是一个int大小),然后将地址与%rbp比较.因为%rbp = 0x18(%rsp).经历五次循环后就结束了.而每循环都需要将当前值设置为前一个值的二倍.
所以结果就是1 2 4 8 16 32
phase_3
有空了,开冲.
代码类似.在这之前
将输入值传入phase_3参数.
进入phase_3函数查看0x4025cf的值
1
2 (gdb) x 0x4025cf
0x4025cf: "%d %d"后面调用scanf函数,第一个参数就在%esi中,”%d %d”表示传入两个整数,第一个在%rdx,第二个在%rcx.
cmp $0x1,%eax表示比较,必须大于一个才能跳转.
后面比较第二个参数,ja表示无符号比较.汇编语言—-跳转指令ja、jb、jlpoptar的博客-CSDN博客汇编语言ja
0x400fad跳转到一个调用引爆炸弹的地方,所以第一个参数要小于等于7.
1 0x400fad <phase_3+106> callq 0x40143a <explode_bomb>后面jmp到一个地址,(0x402470)(,%rax,8) 8表示8个字节,即(0x402470)(,%rax,8)表示0x402470+%rax\8.
寻址方式
1 地址或偏移(%基址或偏移量寄存器, %索引寄存器, 比例因子)地址的计算公式是:
1 最终地址 = 地址或偏移 + %基址或偏移量寄存器 + %索引寄存器 * 比例因子这里第一个参数假设为6,地址是400f9f x/10gx 表示查看后面10个giant(8字节)以16进制形式的数据
0x2aa移到%eax值然后与第二个参数进行比较,需要等于才能避免爆炸.
所以这就是一个答案,一共应该有0-6七组答案,这里我就不写了.
phase_4
进入phase_4函数,查看x $edi表明是将输入的参数传入函数phase_4.
在函数中后面又继续传入参数,x 0x4025cf
1
2 (gdb) x 0x4025cf
0x4025cf: "%d %d"表明需要输入两个整数.0xc(%rsp)是第二个参数,0x8(%rsp)是第一个参数.
1
2
3
4 >0x40102c <phase_4+32> jne 0x401035 <phase_4+41>
0x40102e <phase_4+34> cmpl $0xe,0x8(%rsp)
0x401033 <phase_4+39> jbe 0x40103a <phase_4+46>
0x401035 <phase_4+41> callq 0x40143a <explode_bomb>如果不是输入了两个整数jne跳向炸弹爆炸.接着进行比较第一个整数,与0xe(14)比较.jbe表示小于等于.也就是第一个参数如果小于等于14则跳转否则不跳转爆炸.
这里我们输入14,后面跳转到0x40103a,将14赋值给%edx. %esi赋值为0.第一个参数赋值给%edi.调用func4.
也就是func4(第一个参数(这里我们是14),0,14).
查看func4函数
SHR(右移)指令使目的操作数逻辑右移若干位,最高位用 0 填充。最低位复制到进位标志位,而进位标志位中原来的数值被丢弃.因为%ecx值是0xe,最低位是0.CF=0.
sar与shr差别 两者的区别在于SAR右移时保留操作数的符号,即用符号位来补足,而SHR右移时总是用0来补足
sar %eax表示将%eax存储的值右移一位,最高位由符号位补齐.14右移一位得到7.
1
2
3
4
5
6
7 │ 0x400fdf <func4+17> lea (%rax,%rsi,1),%ecx │
│ >0x400fe2 <func4+20> cmp %edi,%ecx │
│ 0x400fe4 <func4+22> jle 0x400ff2 <func4+36> │
│ 0x400fe6 <func4+24> lea -0x1(%rcx),%edx │
│ 0x400fe9 <func4+27> callq 0x400fce <func4> │
│ 0x400fee <func4+32> add %eax,%eax │
0x400ff0 <func4+34> jmp 0x401007 <func4+57>我们可以先看看函数外的判断
1
2
3
4 40104d: 85 c0 test %eax,%eax
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+0x51test和je指令的组合用法_counsellor的专栏-CSDN博客_je指令
所以我们需要函数结果为0,同时第二个参数也为0.
比较简单的处理就是第一个参数等于%ecx,也就是等于7.
phase_5
1
2 40107a: e8 9c 02 00 00 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax输入参数,字符串长度等于6.所以需要输入含有6个字符的字符串.
进入phase_5函数.
注意后面的代码
1
2
3
4
5
6 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>可以看到将0x40245e赋值给%esi
看一下是什么
1
2 (gdb) x /s 0x40245e
0x40245e: "flyers"表明我们的%rdi的需要等于这个字符串.
1
2
3
4
5
6
7
8
9
10 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
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)
4010a4: 48 83 c0 01 add $0x1,%rax
4010a8: 48 83 f8 06 cmp $0x6,%rax
4010ac: 75 dd jne 40108b <phase_5+0x29>
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)注意到将0x4024b0后面的字节移动到%edi.而之前的%rdx是由我们输入的字符的ascii码与0xf相与得到的.
1
2 (gdb) x 0x4024b0
0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"所以需要首先得到flyers每个字母在上面字符串中的位置,分别是(从0开始)9 15 14 5 6.
我们输入的6个字符的ascii码与0xf相与后必须是以上的值.所以答案有很多.
比如0x69 0x6f 0x6e 0x65 0x66.即ionefg
phase_6
一开始进行比较
1
2
3
4
5
6 0x401145 <phase_6+81> add $0x1,%ebx │
│ >0x401148 <phase_6+84> cmp $0x5,%ebx │
│ 0x40114b <phase_6+87> jle 0x401135 <phase_6+65> │
│ 0x40114d <phase_6+89> add $0x4,%r13 │
│ 0x401151 <phase_6+93> jmp 0x401114 <phase_6+32> │
│ 0x401153 <phase_6+95> lea 0x18(%rsp),%rsi6个数均小于等于6且不能相等.
后面跳转到0x0000000000401153,用7去减去每一个数
0x40116f 0x40116f 0x401188
0x4011ab这里的代码将链表反向串联,便于比较.
1 x /24wx 0x6032d0执行完后
关键代码
1
2
3
4
5
6
7
8 0x4011d0 <phase_6+220> jmp 0x4011bd <phase_6+201> │
│ 0x4011d2 <phase_6+222> movq $0x0,0x8(%rdx) │
│ 0x4011da <phase_6+230> mov $0x5,%ebp │
│ 0x4011df <phase_6+235> mov 0x8(%rbx),%rax │
│ 0x4011e3 <phase_6+239> mov (%rax),%eax │
│ >0x4011e5 <phase_6+241> cmp %eax,(%rbx) │
│ 0x4011e7 <phase_6+243> jge 0x4011ee <phase_6+250> │
│ 0x4011e9 <phase_6+245> callq 0x40143a <explode_bomb>000032d0是node1的地址,这里需要修改二进制文件.
将节点值从头到尾依次增大(假设输入 1 2 3 4 5 6).
1
2
3
4
5
6
7
8
9
10 0x4011e5 <phase_6+241> cmp %eax,(%rbx) │
│ 0x4011e7 <phase_6+243> jge 0x4011ee <phase_6+250> │
│ 0x4011e9 <phase_6+245> callq 0x40143a <explode_bomb> │
│ 0x4011ee <phase_6+250> mov 0x8(%rbx),%rbx │
│ 0x4011f2 <phase_6+254> sub $0x1,%ebp │
│ 0x4011f5 <phase_6+257> jne 0x4011df <phase_6+235> │
│ 0x4011f7 <phase_6+259> add $0x50,%rsp │
│ >0x4011fb <phase_6+263> pop %rbx │
│ 0x4011fc <phase_6+264> pop %rbp │
│ 0x4011fd <phase_6+265> pop %r12secret_phase
strings bomb发现有一个secret_phase很可疑.
查看反汇编文件发现与phase_defused有关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 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
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>需要在第四题答案后加上这个字符串.
1
2
3
4 (gdb) x /s 0x603870
0x603870 <input_strings+240>: "7 0"
(gdb) x /s 0x402619
0x402619: "%d %d %s"比较输入过的答案数量,等于6则进行特殊的测试.
1
2
3
4
5
6
7 0x4015d8 <phase_defused+20> cmpl $0x6,0x202181(%rip) # 0x603760 <num_input_strings> │
│ >0x4015df <phase_defused+27> jne 0x40163f <phase_defused+123> │
│ 0x4015e1 <phase_defused+29> lea 0x10(%rsp),%r8 │
│ 0x4015e6 <phase_defused+34> lea 0xc(%rsp),%rcx │
│ 0x4015eb <phase_defused+39> lea 0x8(%rsp),%rdx │
│ 0x4015f0 <phase_defused+44> mov $0x402619,%esi │
│ 0x4015f5 <phase_defused+49> mov $0x603870,%edi最后结果需要等于2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 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
401217: e8 e8 ff ff ff callq 401204 <fun7>
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>
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需要等于一个类似二叉树中节点的值才能跳出来.
同时不能随便等于一个节点值,2 = 1+1,需要等与第二个节点值.如果给的参数值大于等于节点值才能进行进一步等于的比较
gdb设置汇编代码风格,Linux下是AT&T.
Linux下gdb显示intel和at&t汇编_freezing111的博客-CSDN博客
使用vim编辑可执行文件—16进制模式_astrotycoon-CSDN博客
结束语
网上搜到的资料
CSAPP: Bomb Lab 实验解析 - 简书 (jianshu.com)
IDA反汇编工具
做到后面不想写了…核心还是函数的一些知识和循环分支语句的汇编代码,传参用的寄存器.
注意第六题其实并不需要改变可执行文件,需要按顺序排列1-6,这里我直接改变的可执行文件…
-------------本文结束感谢您的阅读-------------感谢阅读.
- 本文链接: https://www.sekyoro.top/2022/02/24/Bomb-lab实验/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
欢迎关注我的其它发布渠道