CSAPP Bomb Lab
Posted joker D888
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CSAPP Bomb Lab相关的知识,希望对你有一定的参考价值。
CSAPP Bomb Lab
bomb lab给了我们一个bomb的可执行文件,以及一个bomb.c的源文件,不过这个文件只是程序的逻辑逻辑框架,无法编译。进入bomb.c可以看到程序的流程是有6个phase,先读取一行输入,再进入phase判断输入是否正确,正确就可以进入下一关,否则炸弹就会爆炸。
我们需要用反汇编,将可执行文件反汇编为汇编文件,使用objdump -d bomb > bomb.s
得到bomb.s文件。
对于我们的输入,我们将标准输入重定向到文件中去,这样我们就不用每次重复同样的工作,这里使用answer.txt作为输入文件。
我们需要分析汇编代码,可能还需要gdb的调试,打断点,查看内存地址中的值,还要能清楚一些主要寄存器的作用,还有各种常见指令的用法及作用。
本lab中调试用到的gdb指令有:
gdb bomb
使用gdb调试bombr < answer.txt
重定向标准输入到文件,或用r answer.txt
,这是由于main可以指定输入文件。b *0x4015e0
在0x4015e0设置断点x/s 0x402400
以字符串形式打印0x402400地址处的值x 0x6032d0
默认以十六进制打印0x6032d0地址处的值,更多gdb之x命令用法,点这里
phase_1
phase_1汇编代码如下
0000000000400ee0 <phase_1>:
400ee0: 48 83 ec 08 sub $0x8,%rsp ;栈指针减小,分配栈帧
400ee4: be 00 24 40 00 mov $0x402400,%esi ;将0x402400放到寄存器esi中
400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> ;调用函数strigns_not_equal,其中第一个参数为phase_1的参数input,第二个参数为0x402400(%esi)
400eee: 85 c0 test %eax,%eax ; test用来检查%eax是正数负数还是零
400ef0: 74 05 je 400ef7 <phase_1+0x17> ; 是0就跳转到0x4003f7
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> ; 否则,调用expolde_bomb,通过函数名,可以得知要使炸弹爆炸
400ef7: 48 83 c4 08 add $0x8,%rsp ; 栈指针增加,释放栈帧
400efb: c3 retq
代码中分号后面的内容即为该行注释,仅通过phase_1函数我们大概可以猜出,是要比较我们的input和地址0x402400处的字符串是否相等,所以不妨打印一下0x402400处的地址,通过gdb x/s 0x402400 来打印该字符串。
看来结果显然易见了,我们只需要输入Border relations with Canada have never been better. 这句话,第一关就可以过了。
如果仅凭调用pahse_1不能说服你的话,我们不妨进入strings_not_equal看一看是否如我们所想。
0000000000401338 <strings_not_equal>: ; string_not_equal(input,0x402400) 参一:rdi 参二:rsi
401338: 41 54 push %r12
40133a: 55 push %rbp
40133b: 53 push %rbx
40133c: 48 89 fb mov %rdi,%rbx ; rdi -> rbx
40133f: 48 89 f5 mov %rsi,%rbp ; rsi -> rbp
401342: e8 d4 ff ff ff callq 40131b <string_length> ; 计算input字符串的长度
401347: 41 89 c4 mov %eax,%r12d
40134a: 48 89 ef mov %rbp,%rdi ; rbp -> rdi
40134d: e8 c9 ff ff ff callq 40131b <string_length> ; 计算M[0x402400]字符串的长度
401352: ba 01 00 00 00 mov $0x1,%edx
401357: 41 39 c4 cmp %eax,%r12d
40135a: 75 3f jne 40139b <strings_not_equal+0x63>
40135c: 0f b6 03 movzbl (%rbx),%eax ; M[rbx] -> eax 参一
40135f: 84 c0 test %al,%al
401361: 74 25 je 401388 <strings_not_equal+0x50>
401363: 3a 45 00 cmp 0x0(%rbp),%al ; 比较 M[rbp] 和 al 的值,也就是比较0x402400处的值和input的值
401366: 74 0a je 401372 <strings_not_equal+0x3a>
401368: eb 25 jmp 40138f <strings_not_equal+0x57>
40136a: 3a 45 00 cmp 0x0(%rbp),%al ; 比较 M[rbp] 和 al 的值,也就是比较0x402400处的值和input的值
40136d: 0f 1f 00 nopl (%rax)
401370: 75 24 jne 401396 <strings_not_equal+0x5e>
401372: 48 83 c3 01 add $0x1,%rbx ; 准备比较下一个
401376: 48 83 c5 01 add $0x1,%rbp ; 准备比较下一个
40137a: 0f b6 03 movzbl (%rbx),%eax
40137d: 84 c0 test %al,%al ; al所表示字符不为’\\0'
40137f: 75 e9 jne 40136a <strings_not_equal+0x32> ; 跳上去,再比较一番
401381: ba 00 00 00 00 mov $0x0,%edx
401386: eb 13 jmp 40139b <strings_not_equal+0x63>
401388: ba 00 00 00 00 mov $0x0,%edx
40138d: eb 0c jmp 40139b <strings_not_equal+0x63>
40138f: ba 01 00 00 00 mov $0x1,%edx
401394: eb 05 jmp 40139b <strings_not_equal+0x63>
401396: ba 01 00 00 00 mov $0x1,%edx
40139b: 89 d0 mov %edx,%eax
40139d: 5b pop %rbx
40139e: 5d pop %rbp
40139f: 41 5c pop %r12
4013a1: c3 retq
对于上strings_not_equal的汇编代码我们关系参一参二,即寄存器rdi,rsi这两种寄存器的相关操作,可以发现正如我们所想,观察各种跳转指令的的目的地址,可以发现strings_not_equal此函数的大致流程是逐字符比较,若相同就再比较下一个字符。也有不少代码是比较长度的。
answer
Border relations with Canada have never been better.
phase_2
phase_2汇编代码如下
0000000000400efc <phase_2>:
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp ; rsp-40 栈中开辟了40字节内存,可以认为开了个数组,设此时栈指针的值为a,通过后续指令cmpl,eax等可以发现,数组的类型是int
400f02: 48 89 e6 mov %rsp,%rsi ; rsp-> rsi 第二参数寄存器指向栈指针
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers> ; 调用函数read_six_numbers(input,rsi),此函数作用为读取6个数字到数组中
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) ; a[0]是否为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 即eax=a[0]
400f1a: 01 c0 add %eax,%eax ; eax+=eax
400f1c: 39 03 cmp %eax,(%rbx) ; 比较eax和[rbx] 即 2*a[0]==a[1]
400f1e: 74 05 je 400f25 <phase_2+0x29> ; 相等
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 ; 比较rbp和rbx 即比较当前位置是否到了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
我们发现,刚进入函数开了40字节内存,且第二参数指向栈指针后,就调用了read_six_numbers,通过函数名字,我们大概可以猜到是读6个数字到数组中。这个read_six_numbers函数放到后面将。然后一步一步从上往下模拟过程可以发现,最开始要求a[0]=1,随后的过程中要求每个元素是前一个的2倍,直到“指针”指向a[6]。也就是说我们只要按次要求输入:1 2 4 8 16 32 64即可。ok,运行程序,输入答案,Keep going!值得一提的是,最少需要6个数字且这前6个数字是固定的,之后你想输入几个输入几个,想输入几输入几,因为它只读前6个。
汇编代码流程C语言形式大致如下:
void phase_2(char* input) // 不关心它返回的啥,认为void就行
int a[8]; // 40字节
read_six_numbers(input,a); // 读6个数字
if(a[0]!=1) explode_bomb(); // a[0]!=1就爆炸
int* l=a+1,*r=a+6; // 循环判断,每个元素是不是前一个的2倍,不是就爆炸
while(l!=r)
int num=l-1;
num+=num;
if(num!=a[l]) explode_bomb();
++l;
现在来看read_six_numbers汇编代码的具体内容:
000000000040145c <read_six_numbers>: ; 调用函数read_six_numbers(char* input,rsi)
40145c: 48 83 ec 18 sub $0x18,%rsp ; rep-=24 开24字节空间,大小形如long long[3];
401460: 48 89 f2 mov %rsi,%rdx ; rsi -> rdx a[0] rdx=&a[0];
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx ; rsi+4 -> rcx a[1] rcx=&a[1];
401467: 48 8d 46 14 lea 0x14(%rsi),%rax ; rsi+20 -> rax a[5] rax=&a[5];
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) ; rax -> rsp+8 b[2] b[1]=rax;
401470: 48 8d 46 10 lea 0x10(%rsi),%rax ; rsi+16 -> rax a[4] rax=&a[4];
401474: 48 89 04 24 mov %rax,(%rsp) ; rax -> rsp b[0] b[0]=rax;
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 ; rsi+12 -> r9 a[3] r9=&a[3];
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8 ; rsi+8 -> r8 a[2] r8=&a[2];
401480: be c3 25 40 00 mov $0x4025c3,%esi ; 0x4025c3 ->esi
401485: b8 00 00 00 00 mov $0x0,%eax ; 0 -> eax
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax ; 比较sscanf返回值和5,sscanf的返回值是读取到的元素个数
401492: 7f 05 jg 401499 <read_six_numbers+0x3d> ; 若读到的个数>5
401494: e8 a1 ff ff ff callq 40143a <explode_bomb> ; 否则爆炸
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq
我们发现,a数组前6个地址,每个4字节,通过寄存器和栈内容传到了sscanf中去,我们知道sscanf的作用是读取格式化的字符串中的数据,且返回值是读取元素的个数。通过cmp $0x5,%eax可以发现,若返回个数<=5就会引爆炸弹。所以我们至少要输入6个元素。
answer
1 2 4 8 16 32 64
phase_3
phase_3汇编代码如下
0000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp ; rsp-=24 ,
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx ; rcx = rsp+12
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx ; rdx = rsp+8
400f51: be cf 25 40 00 mov $0x4025cf,%esi ; esi = 0x4025cf
400f56: b8 00 00 00 00 mov $0x0,%eax ; eax = 0
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt> ; 读第一个数到rep+8,读第二个数到rsp+12
400f60: 83 f8 01 cmp $0x1,%eax ; comp(1,eax)
400f63: 7f 05 jg 400f6a <phase_3+0x27> ; 若eax>1 即输入个数需要大于1
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb> ; 否则爆炸
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) ; comp(7,rsp+8)
400f6f: 77 3c ja 400fad <phase_3+0x6a> ; 若大于(无符号),跳过去爆炸, 说明第一个数要小于等于7
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax ; eax = [rsp+8]
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) ; 跳到[0x402470+[rax]*8],这里应该是个地址,不妨打印出来看看,用rax等于1带入,结果为400fb9,果然是下面的某个指令的地址
400f7c: b8 cf 00 00 00 mov $0xcf,%eax ; eax=0
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax ; eax=2
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax ; eax=3
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax ; eax=4
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax ; eax=5
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax ; eax=6
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax ; eax=7
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 ; eax = 1
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax ; cmp([rsp+12],eax)
400fc2: 74 05 je 400fc9 <phase_3+0x86> ; 相等的话,跳过去结束。说明第二个数要等于eax
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
老样子从上往下一步一步模拟,我们可以发现sscanf读入了两个数,第一个数存到了[rsp+8],第而个数存到了[rsp+12],而后我们发现第一个数不能大于7,也就是说第一个输入的数需要在[0,7]之间。又通过jmpq *0x402470(,%rax,8)我们可以发现,是要根据第一个数的值跳到某处去,我们发现0x402470这个值汇编代码中根本没有,所以我们大概可以猜测需要用gdb将其打印出来,再做进一步打算。在gdb中使用x/8xb 0x402470+8(我们这里设rax=1),结果为400fb9,恰好对应mov $0x137,%eax 这行代码,所以我们可以认为通过jmpq *0x402470(,%rax,8) 跳过去后会根据rax的值再跳回来到某个位置。接着在看中间有很多步相同的操作,都是对eax赋了某些值,然后跳过cmp 0xc(%rsp),%eax进行比较,之后就一目了然了,通过对比判断炸弹爆炸与否。
rax取值0到7打印结果如下:
综上,总流程为,读取两个数,然后根据第一个数,跳到不同的位置比较第二个数,若相同,Halfway there!,否则爆炸。
answer
答案的话,共有8组,对应的组合已经标到代码注释中了,这里就以第一个数为1为例。
1 311
phase_4
phase_4汇编代码如下
000000000040100c <phase_4>:
40100c: 48 83 ec 18 sub $0x18,%rsp ; rsp-=24
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx ; rcx = rsp+12
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx ; rdx = rsp+8
40101a: be cf 25 40 00 mov $0x4025cf,%esi ; esi = 0x4025cf
40101f: b8 00 00 00 00 mov $0x0,%eax ; eax = 0
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt> ; 第一个数据读到rdx=rsp+8,第二个参数读到rcx =rsp+12
401029: 83 f8 02 cmp $0x2,%eax ; comp(2,eax)
40102c: 75 07 jne 401035 <phase_4+0x29> ; 若不等,跳过去爆炸,说明只能输入两个数据
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) ; comp(14,[rsp+8])
401033: 76 05 jbe 40103a <phase_4+0x2e> ; 若[rsp+8]<=14,跳过去 ,第一个输入数据需要<=14
401035: e8 00 04 00 00 callq 40143a <explode_bomb> ; 否则爆炸
40103a: ba 0e 00 00 00 mov $0xe,%edx ; edx = 14 参三
40103f: be 00 00 00 00 mov $0x0,%esi ; esi = 0 参二
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi ; edi = [rsp+8] 参一
401048: e8 81 ff ff ff callq 400fce <func4> ; 调用func4,参数如上所示
40104d: 85 c0 test %eax,%eax ; eax==0
40104f: 75 07 jne 401058 <phase_4+0x4c> ; 不等,跳过去爆炸,说明返回值需要是0
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) ; comp(0,[rsp+12])
401056: 74 05 je 40105d <phase_4+0x51> ; 相等,跳过去,结束,否则爆炸,第二个输入数据经过func4为需要0
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
看上方代码,前面依然是从输入读取数据,通过cmp $0x2,%eax 可以发现,我们只能输入两个整数,然后cmpl $0xe,0x8(%rsp)则要求第一个数要<=14,然后是三个参数的设置,随后是函数func4的调用,我们先不进去看,看紧接着的返回值判断test %eax,%eax,发现fun4的返回值必须要等于0才可以通过,否则爆炸。所以此时我们得出的信息有:输入的一个数要<=14,第二个数要为0,且调用的fun4d的返回值要为0。ok,那么接下来让我们进入fun4来一探究竟。
0000000000400fce <func4>: ; func4(a=[rsp+8],b=0,c=014) 设输入的第一个数据为14,即a=14
400fce: 48 83 ec 08 sub $0x8,%rsp ; rsp-=8 右边的等式是假设第一个数为14后的结果
400fd2: 89 d0 mov %edx,%eax ; eax = edx(14) eax=14
400fd4: 29 f0 sub %esi,%eax ; eax -= esi(0) eax=14
400fd6: 89 c1 mov %eax,%ecx ; ecx = eax ecx=14
400fd8: c1 e9 1f shr $0x1f,%ecx ; ecx >>= 31 (逻辑右移) ecx=0
400fdb: 01 c8 add %ecx,%eax ; eax += ecx eax=14
400fdd: d1 f8 sar %eax ; 只有一个操作数是省略了1, 即eax>>=1 eax=7
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx ; ecx = [rax]+[rsi] ecx=7
400fe2: 39 f9 cmp %edi,%ecx ; comp(edi,ecx) comp(14,7)
400fe4: 7e 0c jle 400ff2 <func4+0x24> ; 若 ecx <= edi,即 ecx<=a 成立。跳过去
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce <func4> ; edi esi edx
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007 <func4+0x39>
400ff2: b8 00 00 00 00 mov $0x0,%eax ; eax = 0
400ff7: 39 f9 cmp %edi,%ecx ; cmp(edi,ecx) cmp(14,7)
400ff9: 7d 0c jge 401007 <func4+0x39> ; 若ecx >= edi ,跳过去结束。但我们假设的14不成立,应该假设为7才能成立跳过去,结束,且返回0,说明第一个输入数据需要为7
400ffb: 8d 71 01 lea 0x1(%rcx),%esi ; esi = rcx+1
400ffe: e8 cb ff ff ff callq 400fce <func4> ; fun4(a=[rsp+8],8,14)
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
经过一行一行注释,不难将其“翻译”为C语言代码,翻译大概的形式在下方。func4函数主要return 0的流程要经过如下4条汇编代码,我们发现edi既要<=ecx又要>=ecx,所以只能是edi=ecx,而ecx经过计算为7,也就是edi我们的第一个参数,即我们要输入的第一个数要为7,至此答案浮出水面:7 0 ,7和0就是我们要输入的两个数。但是别急,总感觉这样做不能说服自己,虽然也对,那么通过进一步研究,发现还有其他的答案。先看下方“翻译”简化后的C语言代码,可以发现果然还有其他答案,详细分析见注释。
...
cmp %edi,%ecx
jle 400ff2 <func4+0x24>
...
cmp %edi,%ecx
jge 401007 <func4+0x39>
...
“翻译”如下:
int func4(int a, int esi = 0, int edx = 14)
int eax = edx;
eax -= esi;
int ecx = eax;
ecx >>= 31;
eax += ecx;
eax >>= 1;
ecx = eax + esi;
if (ecx <= a)
eax = 0;
if (ecx >= a)
return eax; // 很明显,这里会返回0
else
esi = ecx + 1;
eax = func4(a, esi, edx);
eax = 1 + eax + eax;
return eax;
else
edx = ecx - 1;
eax = func4(a, esi, edx);
eax += eax;
return eax;
// 进一步重命名和简化后
int func4(int a, int first = 0, int second = 14)
int b = second - first;
int c = b >> 31;
b += c;
b >>= 1;
c = b + first;
if (c <= a)
b = 0;
if (c >= a)
return b; // 进入这里,明显的返回0
else
first = c + 1;
b = func4(a, first, second);
b = 1 + b + b; // 可以看出若函数进入这里绝对返回的不是0
return b;
else
second = c - 1;
b = func4(a, first, second); // fun4(a,0,6)
// 那么进入这里呢,只要上一步的函数调用返回0,这里也会返回0。若要进入此步,要求c>a,即答案第一个数要小于7,此时上方函数调用为func4(a,0,6),模拟进入函数我们发现,若通过if (c >= a) return b;进行返回0,那么a就要为3。所以又一个答案为(3,0)。若还通过此位置返回0,那么还需要上方再调用一个fun4,那么又一个答案为(1,0),以及更深的(0,0),共计4个答案,(7,0),(3,0),(1,0),(0,0)
b += b;
return b;
综上,代码流程为:读取两个数字,若读到的数字个数不为2(但实际输入个数大于2也正确),explode_bomb。进入函数func4,func4主要逻辑为取first和second的中间值c,判断c和a的大小,进入不同分支,但只要求最终结果返回0即可。
answer
共四组答案(7,0),(3,0),(1,0),(0,0),我们任选一组即可
7 0
phase_5
还有两关,离胜利不远了。phase_5汇编代码如下:
0000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp ; rsp-=32
401067: 48 89 fb mov %rdi,%rbx ; rbx = rdi rdi为input的字符串的首地址
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax ; 存储金丝雀值(fs:40是用段寻址机制从内存读入)
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp) ; [rsp+24]=rax 从内存中读到的金丝雀值存到了rsp+24处
401078: 31 c0 xor %eax,%eax ; eax置为0
40107a: e8 9c 02 00 00 callq 40131b <string_length> ; 调用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> ; 否则,爆炸,说明我们要输入的字符长度为6
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx ; ecx = [rbx + rax]
40108f: 88 0c 24 mov %cl,(%rsp) ; [rsp]=cl
401092: 48 8b 14 24 mov (%rsp),%rdx ; rdx = [rsp]
401096: 83 e2 0f and $0xf,%edx ; rdx &= 15
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx ; rdx = 0x4024b0+rdx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) ; [16+rsp+rax]=dl
4010a4: 48 83 c0 01 add $0x1,%rax ; rax += 1
4010a8: 48 83 f8 06 cmp $0x6,%rax ; comp(6,rax);
4010ac: 75 dd jne 40108b <phase_5+0x29> ; 不等,跳过去
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) ; [rsp+22]=0
4010b3: be 5e 24 40 00 mov $0x40245e,%esi ; esi = 0x40245e
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi ; rdi = [rsp+16]
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal> ; 调strings_not_equal
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9 <phase_5+0x77> ; 若返回0,跳过去
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 ; eax = 0
4010d7: eb b2 jmp 40108b <phase_5+0x29>
4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax ; rax = [rsp+24]
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
先从上往下看,在调用string_length前,有两步是做金丝雀值的存储,不过无关紧要。string_length调用后cmp $0x6,%eax以及其后两行,我们发现,输入的字符串需要长度为6,好,这是获取的第一个重要信息。再看movzbl (%rbx,%rax,1),%ecx 这句是把input[rax]的值赋给ecx,第一次执行这句的时候rax=0,随后的mov %cl,(%rsp)以及mov (%rsp),%rdx则是吧ecx的值赋给了rdx,然后是and $0xf,%edx,即 edx &= 15(这句刚开始将and看成了add,迷了好久😑),这说明与15与后,eax的值在0~15。随后movzbl 0x4024b0(%rdx),%edx ,即edx = [rdx + 0x4024b0]。然后mov %dl,0x10(%rsp,%rax,1) 则是将dl即刚才的edx赋给了rsp[16+rax]。所以以上几步主要是从input[rax]的值与上15后,再加上0x4024b0得到新地址,随后取出地址中的值放到rsp[16+rax]中去。接下来的add $0x1,%rax,即rax++,然后cmp $0x6,%rax,判断rax是否等于6,不然再跳上去,否则,movb $0x0,0x16(%rsp),即rsp[22]=0,然后进行strings_not_equal(rsp+16,(char*)0x40245e) 进行字符串比较,若相等,返回,退出phase_5,否则爆炸。
好,既让要rdx+0x4024b0地址处的字符串与0x40245e地址处的字符串进行比较,那么打印出来看看呗
仔细观察So前面的无序字符串,唉,恰好有16个字符,而刚好eax的值在0~15。所以我们看看flyers这6个字符在无序字符串中的位置,分别是9,15,14,5,6,7。所以我们只要输入的前6个字符的二进制低4位构成的值分别是这6个数字即可。所以答案不唯一,我们直接简单将这6个数字加上64,方便构成字母进行输入。即答案可以为IONEFG,或加上96,得ionefg。
总体流程:首先判断字符串input长度是否为6,若不是explode_bomb,再逐个遍历字符串input即为input[i],然后将input[i]与上15再加上0x4024b0得到地址处的值放到rsp[16+i]中,遍历完6个字母后,rsp[22]=0即将字符串封尾,最后将rsp+16处的字符串和0x40245e地址处的字符串进行比较,若相同,Good work! On to the next,否则,爆炸。
最后然我们来看下大概相对应的C语言形式代码
void phase_5(char* input)
char* rbx = input;
char rsp[32];
int ret = string_lenhth(input);
if (ret == 6)
ret = 0;
L:
char ecx = input[ret];
rsp[0] = ecx;
char rdx = rsp[0];
rdx &= 15;
rdx = *(char*)(0x4024b0 + rdx);
rsp[16 + ret] = rdx;
ret += 1;
if (ret == 6)
*(rsp + 22) = 0;
char* esi = (char*)0x40245e;
input=rsp[16];
ret=strings_not_equal(input,esi);
if(ret==0)
ret=rsp[24];
return;
else
explode_bomb();
else
goto L;
else
explode_bomb();
answer
IONEFG
p_hase_6
最后一关了,但你想就这,看看关卡前的注释,没错,这关确实有些难,主要是繁琐,需要仔细耐心。
This phase will never be used, since no one will get past the earlier ones. But just in case, make this one extra hard.
上代码
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 ; rsp -= 80
401100: 49 89 e5 mov %rsp,%r13 ; r13 = rsp
401103: 48 89 e6 mov %rsp,%rsi ; rsi = rsp
401106: e8 51 03 00 00 callq 40145c <read_six_numbers> ; read_six_numbers,这个函数我们之前已经讨论过了,6个数分别读到数组中
40110b: 49 89 e6 mov %rsp,%r14 ; r14 = rsp
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d ; r12d = 0
401114: 4c 89 ed mov %r13,%rbp ; rbp = r13
401117: 41 8b 45 00 mov 0x0(%r13),%eax ; rax = [r13]
40111b: 83 e8 01 sub $0x1,%eax ; rax -= 1
40111e: 83 f8 05 cmp $0x5,%eax ; comp(5,rax)
401121: 76 05 jbe 401128 <phase_6+0x34> ; 若小于等于5,跳过去,无符号比较,即说明我们输入的6个数的范围为1~6
401123: e8 12 03 00 00 callq 40143a <explode_bomb> ; 否则爆炸
401128: 41 83 c4 01 add $0x1,%r12d ; r12d+=1
40112c: 41 83 fc 06 cmp $0x6,%r12d ; comp(6,r12d)
401130: 74 21 je 401153 <phase_6+0x5f> ; 相等跳过去
401132: 44 89 e3 mov %r12d,%ebx ; ebx = r12d
401135: 48 63 c3 movslq %ebx,%rax ; rax = ebx
401138: 8b 04 84 mov (%rsp,%rax,4),%eax ; rax =[rsp+rax*4]
40113b: 39 45 00 cmp %eax,0x0(%rbp) ; comp(rax,[0+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 ; ebx += 1
401148: 83 fb 05 cmp $0x5,%ebx ; comp(5,ebx)
40114b: 7e e8 jle 401135 <phase_6+0x41> ; <= ,跳
40114d: 49 83 c5 04 add $0x4,%r13 ; r13 += 4
401151: eb c1 jmp 401114 <phase_6+0x20> ; 跳
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi ; rsi = rsp + 24
401158: 4c 89 f0 mov %r14,%rax ; rax = r14
40115b: b9 07 00 00 00 mov $0x7,%ecx ; ecx = 7
401160: 89 ca mov %ecx,%edx ; edx = ecx
401162: 2b 10 sub (%rax),%edx ; edx -= [rax]
401164: 89 10 mov %edx,(%rax) ; [rax] = edx;
401166: 48 83 c0 04 add $0x4,%rax ; rax += 4;
40116a: 48 39 f0 cmp %rsi,%rax ; comp(rsi,rax)
40116d: 75 f1 jne 401160 <phase_6+0x6c> ; 不等,跳过去
40116f: be 00 00 00 00 mov $0x0,%esi ; esi = 0
401174: eb 21 jmp 401197 <phase_6+0xa3>
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx ; rdx = [8+rdx]
40117a: 83 c0 01 add $0x1,%eax ; rax += 1
40117d: 39 c8 cmp %ecx,%eax ; comp(ecx,rax)
40117f: 75 f5 jne 401176 <phase_6+0x82> ; 不等,跳
401181: eb 05 jmp 401188 <phase_6+0x94> ; 跳
401183: ba d0 32 60 00 mov $0x6032d0,%edx ; edx = 0x6032d0
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) ; [32+rsp+rsi*2] = rdx
40118d: 48 83 c6 04 add $0x4,%rsi ; rsi += 4
401191: 48 83 fe 18 cmp $0x18,%rsi ; comp(24,rsi)
401195: 74 14 je 4011ab <phase_6+0xb7> ; 相等,跳过去
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx ; ecx =[rsp+rsi]
40119a: 83 f9 01 cmp $0x1,%ecx ; comp(1,ecx)
40119d: 7e e4 jle 401183 <phase_6+0x8f> ; <= ,跳
40119f: b8 01 00 00 00 mov $0x1,%eax ; rax = 1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx ; edx = 0x6032d0
4011a9: eb cb jmp 401176 <phase_6+0x82> ; 跳
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx ; rbx = [32+rsp]
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax ; rax = 40+rsp
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi ; rsi = rsp + 80
4011ba: 48 89 d9 mov %rbx,%rcx ; rcx = rbx
4011bd: 48 8b 10 mov (%rax),%rdx ; rdx = [rax]
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) ; [8+rcx] = rdx
4011c4: 48 83 c0 08 add $0x8,%rax ; rax += 8
4011c8: 48 39 f0 cmp %rsi,%rax ; comp(rsi,rax)
4011cb: 74 05 je 4011d2 <phase_6+0xde> ; 相等,跳过去
4011cd: 48 89 d1 mov %rdx,%rcx ; rcx = rdx
4011d0: eb eb jmp 4011bd <phase_6+0xc9> ; 跳
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) ; [rdx+8] = 0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp ; rbp = 5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax ; rax = [8+rbx]
4011e3: 8b 00 mov (%rax),%eax ; eax = [rax]
4011e5: 39 03 cmp %eax,(%rbx) ; comp(eax,[rbx])
4011e7: 7d 05 jge 4011ee <phase_6+0xfa> ; 大于等于,跳
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> ; 否则爆炸
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx ; rbx = [8+rbx]
4011f2: 83 ed 01 sub $0x1,%ebp ; rbp -= 1
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
这次光看汇编代码就想要清楚逻辑流程,可能就比较困难了,主要是各种跳转指定跳来跳去,不能清楚知道跳转流程。但我们还是可以从前面一些部分得知我们需要输入6个数字,且6个数字范围在[1,6]。但后续流程就比较混乱了。 所以这次我们先大概“翻译”为C形式代码,便于梳理逻辑,“翻译”如下:
void phase_6(char* input)
int rsp[20];
int* r13 = rsp;
read_six_numbers(input, rsp);
int* r14 = rsp;
int r12 = 0;
L4:
int* rbp = r13;
int rax = *r13; // rsp[0]~rsp[5],读入的第一个数字
rax -= 1; // 此行及下行,说明输入的6个数字需要在[1,6]范围上
if (rax <= 5) // 无符号比较
// r12从0开始递增1,即我们输入的前5个数全会进入else,而else语句中则进行判重,判断当前的数和后面的数是否相同,若相同,爆炸
r12 += 1;
if (r12 == 6)
int* rsi = rsp + 24; // rsi=rsp[6]
rax = (int)r14; // rax=rsp[0]
int ecx = 7;
L:
int edx = ecx; // 以下4行主要是将我们输入的6个数全部被7减掉,然后再存在原位置
edx -= *(int*)rax;
*(int*)rax = edx;
rax += 4;
if (rsi == (int*)rax) // 6个数被7减完后
rsi = 0;
L7:
ecx = *(rsp + (int)rsi); // ecx =*(rsp+rsi)
if (ecx <= 1) // 当原始输入的数字为6时,才可以直接进去,但6只能有一个,其余要做else处理
edx = 0x6032d0;
L6:
*(32 + rsp + (int)rsi * 2CSAPP实验二:二进制炸弹(Bomb Lab)