实验介绍
本实验是对CSAPP配套实验的简化改编版本,主要考察阅读X86汇编的能力。总共分为四小关。
-
phase_1: 输入字符串密码
-
phase_2:输入六个数的密码
-
phase_3: 输入两个数,满足switch-case的关系
-
phase_4:输入一个数,满足斐波那契数列的规律。
实验环境
- wsl2.0:Windows的Linux子系统
- gdb:调试工具
- objdump:反汇编工具
phase_1
0000000000000c92 <phase_1>:
c92: 48 83 ec 08 sub $0x8,%rsp
c96: 48 8d 35 03 14 20 00 lea 0x201403(%rip),%rsi # 2020a0 <unique_str>
c9d: b8 00 00 00 00 mov $0x0,%eax
ca2: e8 8f ff ff ff callq c36 <strings_not_equal>
ca7: 85 c0 test %eax,%eax
ca9: 75 05 jne cb0 <phase_1+0x1e>
cab: 48 83 c4 08 add $0x8,%rsp
caf: c3 retq
cb0: b8 00 00 00 00 mov $0x0,%eax
cb5: e8 d2 fe ff ff callq b8c <explode_bomb>
cba: eb ef jmp cab <phase_1+0x19>
- c92: 为当前函数分配栈空间
- c96:把0x201403(%rip)这个固定地址传给%rsi
- c9d:eax清零,之后要放返回值
- ca2:执行strings_not_equal函数,此函数的返回值放在eax中
- ca7:检查eax是否为0(实际做的是逻辑与操作,若eax为0,则将ZF位置为1)
- ca9:eax若不为0则跳转到cb0,可以看到跳转后炸弹爆炸
- cab: 释放为此函数分配的栈空间
- caf:返回main函数
- cb0:eax清零
- cb5:炸弹爆炸
- cba:炸弹爆炸后也要把为此函数分配的栈空间释放
0000000000000c36 <strings_not_equal>:
c36: 48 83 ec 08 sub $0x8,%rsp
c3a: e8 c1 fb ff ff callq 800 <strcmp@plt>
c3f: 85 c0 test %eax,%eax
c41: 0f 95 c0 setne %al
c44: 0f b6 c0 movzbl %al,%eax
c47: 48 83 c4 08 add $0x8,%rsp
c4b: c3 retq
- 查看strings_not_equal的汇编,发现若两个字符串相等,eax中返回的是0.
分析以上两段汇编代码知,
我们要输入一段字符串,和0x201403(%rip)这个地址中的字符串进行比较,所以现在只需要打印出0x201403(%rip)这个地址中的内容就可以找到答案了。
phase_2
0000000000000cbc <phase_2>:
cbc: 53 push %rbx
cbd: 48 83 ec 20 sub $0x20,%rsp
cc1: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
cc8: 00 00
cca: 48 89 44 24 18 mov %rax,0x18(%rsp)
ccf: 31 c0 xor %eax,%eax
cd1: 48 89 e6 mov %rsp,%rsi
cd4: e8 73 ff ff ff callq c4c <read_six_numbers>
cd9: bb 00 00 00 00 mov $0x0,%ebx
cde: eb 03 jmp ce3 <phase_2+0x27>
ce0: 83 c3 01 add $0x1,%ebx
ce3: 83 fb 05 cmp $0x5,%ebx
ce6: 7f 1e jg d06 <phase_2+0x4a>
ce8: 48 63 c3 movslq %ebx,%rax
ceb: 48 8d 15 8e 13 20 00 lea 0x20138e(%rip),%rdx # 202080 <magic_num>
cf2: 8b 0c 82 mov (%rdx,%rax,4),%ecx
cf5: 39 0c 84 cmp %ecx,(%rsp,%rax,4)
cf8: 74 e6 je ce0 <phase_2+0x24>
cfa: b8 00 00 00 00 mov $0x0,%eax
cff: e8 88 fe ff ff callq b8c <explode_bomb>
d04: eb da jmp ce0 <phase_2+0x24>
d06: 48 8b 44 24 18 mov 0x18(%rsp),%rax
d0b: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
d12: 00 00
d14: 75 06 jne d1c <phase_2+0x60>
d16: 48 83 c4 20 add $0x20,%rsp
d1a: 5b pop %rbx
d1b: c3 retq
d1c: e8 af fa ff ff callq 7d0 <__stack_chk_fail@plt>
分析代码:
- cbc到cca是在分配栈空间和进行堆栈溢出检查 把当前栈的rsp存到rsi中作为read_six_numbers的参数 。
- 执行完read_six_numbers后,开始循环,ebx从0到5,每次循环对比以0x20138e(%rip)为起始地址的数组中的数和我们输入的放在rsp到rsp+18中的数是否相等,若不相等则炸弹爆炸。所以我们只需要输入以0x20138e(%rip)为起始地址的数组中的数即可。
查看此时rdx中的内容,这个内容就是标准答案的起始地址。
此时我们就得到了要输的6个数。
read_six_numbers
0000000000000c4c <read_six_numbers>:
c4c: 48 83 ec 08 sub $0x8,%rsp
c50: 48 89 f2 mov %rsi,%rdx
c53: 48 8d 76 14 lea 0x14(%rsi),%rsi
c57: 48 8d 42 10 lea 0x10(%rdx),%rax
c5b: 48 8d 4a 04 lea 0x4(%rdx),%rcx
c5f: 56 push %rsi
c60: 50 push %rax
c61: 4c 8d 4a 0c lea 0xc(%rdx),%r9
c65: 4c 8d 42 08 lea 0x8(%rdx),%r8
c69: 48 8d 35 a0 13 20 00 lea 0x2013a0(%rip),%rsi # 202010 <fmt_six_num>
c70: b8 00 00 00 00 mov $0x0,%eax
c75: e8 a6 fb ff ff callq 820 <__isoc99_sscanf@plt>
c7a: 48 83 c4 10 add $0x10,%rsp
c7e: 83 f8 05 cmp $0x5,%eax
c81: 7e 05 jle c88 <read_six_numbers+0x3c>
c83: 48 83 c4 08 add $0x8,%rsp
c87: c3 retq
c88: b8 00 00 00 00 mov $0x0,%eax
c8d: e8 fa fe ff ff callq b8c <explode_bomb>
分析代码:
read_numbers中主要是调用了__isoc99_sscanf@plt函数,这个函数接受了8个参数。
eax放返回值,rsi放字符串格式,rdx中放了phase_2栈帧的rsp的地址也就是第一个数要放的地址,rcx放第二个数的地址,r8放第三个数的地址,r9放第四个数的地址,超过六个参数要通过栈来传递参数,所以可以看到c5f和c60这两句是把第五个数的地址和第六个数的地址压到栈中了。所以我们输入的六个数存放在rsp到rsp+0x18这个空间内。这也是后面返回phase_2函数的时候为什么可以用rsp+4*rax来遍历我们输入的数组的原因。
phase_3
0000000000000d21 <phase_3>:
d21: 48 83 ec 18 sub $0x18,%rsp
d25: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
d2c: 00 00
d2e: 48 89 44 24 08 mov %rax,0x8(%rsp)
d33: 31 c0 xor %eax,%eax
d35: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx
d3a: 48 89 e2 mov %rsp,%rdx
d3d: 48 8d 35 ff 12 20 00 lea 0x2012ff(%rip),%rsi # 202043 <fmt_phase_3>
d44: e8 d7 fa ff ff callq 820 <__isoc99_sscanf@plt>
d49: 83 f8 01 cmp $0x1,%eax
d4c: 7e 29 jle d77 <phase_3+0x56>
d4e: 83 3c 24 00 cmpl $0x0,(%rsp)
d52: 78 2f js d83 <phase_3+0x62>
d54: 83 3c 24 07 cmpl $0x7,(%rsp)
d58: 7f 35 jg d8f <phase_3+0x6e>
d5a: 83 3c 24 07 cmpl $0x7,(%rsp)
d5e: 0f 87 0c 01 00 00 ja e70 <phase_3+0x14f>
d64: 8b 04 24 mov (%rsp),%eax
d67: 48 8d 15 22 04 00 00 lea 0x422(%rip),%rdx # 1190 <_IO_stdin_used+0x1e0>
d6e: 48 63 04 82 movslq (%rdx,%rax,4),%rax
d72: 48 01 d0 add %rdx,%rax
d75: ff e0 jmpq *%rax
d77: b8 00 00 00 00 mov $0x0,%eax
d7c: e8 0b fe ff ff callq b8c <explode_bomb>
d81: eb cb jmp d4e <phase_3+0x2d>
d83: b8 00 00 00 00 mov $0x0,%eax
d88: e8 ff fd ff ff callq b8c <explode_bomb>
d8d: eb c5 jmp d54 <phase_3+0x33>
d8f: b8 00 00 00 00 mov $0x0,%eax
d94: e8 f3 fd ff ff callq b8c <explode_bomb>
d99: eb bf jmp d5a <phase_3+0x39>
d9b: 8b 44 24 04 mov 0x4(%rsp),%eax
d9f: 39 05 bb 12 20 00 cmp %eax,0x2012bb(%rip) # 202060 <magic_num_p3>
da5: 0f 84 cf 00 00 00 je e7a <phase_3+0x159>
dab: b8 00 00 00 00 mov $0x0,%eax
db0: e8 d7 fd ff ff callq b8c <explode_bomb>
db5: e9 c0 00 00 00 jmpq e7a <phase_3+0x159>
dba: 8b 44 24 04 mov 0x4(%rsp),%eax
dbe: 39 05 a0 12 20 00 cmp %eax,0x2012a0(%rip) # 202064 <magic_num_p3+0x4>
dc4: 0f 84 b0 00 00 00 je e7a <phase_3+0x159>
dca: b8 00 00 00 00 mov $0x0,%eax
dcf: e8 b8 fd ff ff callq b8c <explode_bomb>
dd4: e9 a1 00 00 00 jmpq e7a <phase_3+0x159>
dd9: 8b 44 24 04 mov 0x4(%rsp),%eax
ddd: 39 05 85 12 20 00 cmp %eax,0x201285(%rip) # 202068 <magic_num_p3+0x8>
de3: 0f 84 91 00 00 00 je e7a <phase_3+0x159>
de9: b8 00 00 00 00 mov $0x0,%eax
dee: e8 99 fd ff ff callq b8c <explode_bomb>
df3: e9 82 00 00 00 jmpq e7a <phase_3+0x159>
df8: 8b 44 24 04 mov 0x4(%rsp),%eax
dfc: 39 05 6a 12 20 00 cmp %eax,0x20126a(%rip) # 20206c <magic_num_p3+0xc>
e02: 74 76 je e7a <phase_3+0x159>
e04: b8 00 00 00 00 mov $0x0,%eax
e09: e8 7e fd ff ff callq b8c <explode_bomb>
e0e: eb 6a jmp e7a <phase_3+0x159>
e10: 8b 44 24 04 mov 0x4(%rsp),%eax
e14: 39 05 56 12 20 00 cmp %eax,0x201256(%rip) # 202070 <magic_num_p3+0x10>
e1a: 74 5e je e7a <phase_3+0x159>
e1c: b8 00 00 00 00 mov $0x0,%eax
e21: e8 66 fd ff ff callq b8c <explode_bomb>
e26: eb 52 jmp e7a <phase_3+0x159>
e28: 8b 44 24 04 mov 0x4(%rsp),%eax
e2c: 39 05 42 12 20 00 cmp %eax,0x201242(%rip) # 202074 <magic_num_p3+0x14>
e32: 74 46 je e7a <phase_3+0x159>
e34: b8 00 00 00 00 mov $0x0,%eax
e39: e8 4e fd ff ff callq b8c <explode_bomb>
e3e: eb 3a jmp e7a <phase_3+0x159>
e40: 8b 44 24 04 mov 0x4(%rsp),%eax
e44: 39 05 2e 12 20 00 cmp %eax,0x20122e(%rip) # 202078 <magic_num_p3+0x18>
e4a: 74 2e je e7a <phase_3+0x159>
e4c: b8 00 00 00 00 mov $0x0,%eax
e51: e8 36 fd ff ff callq b8c <explode_bomb>
e56: eb 22 jmp e7a <phase_3+0x159>
e58: 8b 44 24 04 mov 0x4(%rsp),%eax
e5c: 39 05 1a 12 20 00 cmp %eax,0x20121a(%rip) # 20207c <magic_num_p3+0x1c>
e62: 74 16 je e7a <phase_3+0x159>
e64: b8 00 00 00 00 mov $0x0,%eax
e69: e8 1e fd ff ff callq b8c <explode_bomb>
e6e: eb 0a jmp e7a <phase_3+0x159>
e70: b8 00 00 00 00 mov $0x0,%eax
e75: e8 12 fd ff ff callq b8c <explode_bomb>
e7a: 48 8b 44 24 08 mov 0x8(%rsp),%rax
e7f: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
e86: 00 00
e88: 75 05 jne e8f <phase_3+0x16e>
e8a: 48 83 c4 18 add $0x18,%rsp
e8e: c3 retq
e8f: e8 3c f9 ff ff callq 7d0 <__stack_chk_fail@plt>
- d44调用了__isoc99_sscanf@plt函数,参数存在rsi,rdx,rcx,rax中,rsi放的是输入的两个数的格式,rdx放输入的第一个数要存的地址,rcx放输入的第二个数存的地址。rax放返回值,也就是正确匹配的个数。
- d49: 检查你输入的数的个数是否大于1,不大于1的话就爆炸。
- d4e-d5e: 检查第一个数的大小是否在0到7这个范围内。不在这个范围内就爆炸。
- d64-d75: 根据生成的第一个数生成一个地址并跳转。
- d9b:输入的第一个数为0的话就跳转到这个地址,此时将输入的第二个数放到eax中,然后和0x2012bb(%rip)中的数作比较,若不相等则爆炸,所以我们只需要打印出此时这个地址中的数即可知道正确答案。
这是当第一个数为0的情况,其他7个数找答案的方法同理。
phase_4
这一关并不复杂,主要是理解递归算法的思想。
0000000000000ec2 <phase_4>:
ec2: 48 83 ec 18 sub $0x18,%rsp
ec6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
ecd: 00 00
ecf: 48 89 44 24 08 mov %rax,0x8(%rsp)
ed4: 31 c0 xor %eax,%eax
ed6: 48 8d 54 24 04 lea 0x4(%rsp),%rdx
edb: 48 8d 35 5e 11 20 00 lea 0x20115e(%rip),%rsi # 202040 <fmt_phase_4>
ee2: e8 39 f9 ff ff callq 820 <__isoc99_sscanf@plt>
ee7: 83 f8 01 cmp $0x1,%eax
eea: 75 07 jne ef3 <phase_4+0x31>
eec: 83 7c 24 04 00 cmpl $0x0,0x4(%rsp)
ef1: 7f 0a jg efd <phase_4+0x3b>
ef3: b8 00 00 00 00 mov $0x0,%eax
ef8: e8 8f fc ff ff callq b8c <explode_bomb>
efd: 8b 7c 24 04 mov 0x4(%rsp),%edi
f01: e8 8e ff ff ff callq e94 <func4>
f06: 83 f8 37 cmp $0x37,%eax
f09: 74 0a je f15 <phase_4+0x53>
f0b: b8 00 00 00 00 mov $0x0,%eax
f10: e8 77 fc ff ff callq b8c <explode_bomb>
f15: 48 8b 44 24 08 mov 0x8(%rsp),%rax
f1a: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
f21: 00 00
f23: 75 05 jne f2a <phase_4+0x68>
f25: 48 83 c4 18 add $0x18,%rsp
f29: c3 retq
f2a: e8 a1 f8 ff ff callq 7d0 <__stack_chk_fail@plt>
f2f: 90 nop
可以看到ee2行调用了__isoc99_sscanf@plt函数,传了eax,rsp+4和rsi三个参数,此时打印rsi存放的地址中的内容就可以知道我们要输入的内容的格式。
可以看到我们只需输入一个十进制整数。
- efd:把输入的数放入edi,作为func4的参数
- f06:比较func4的返回值和0x37的关系,如果相等的话函数正常结束,否则就爆炸。所以只需使fun4的返回值为十进制的55即可。
进入func4函数中:
0000000000000e94 <func4>:
e94: 83 ff 01 cmp $0x1,%edi
e97: 7f 06 jg e9f <func4+0xb>
e99: b8 01 00 00 00 mov $0x1,%eax
e9e: c3 retq
e9f: 55 push %rbp
ea0: 53 push %rbx
ea1: 48 83 ec 08 sub $0x8,%rsp
ea5: 89 fb mov %edi,%ebx
ea7: 8d 7f ff lea -0x1(%rdi),%edi
eaa: e8 e5 ff ff ff callq e94 <func4>
eaf: 89 c5 mov %eax,%ebp
eb1: 8d 7b fe lea -0x2(%rbx),%edi
eb4: e8 db ff ff ff callq e94 <func4>
eb9: 01 e8 add %ebp,%eax
ebb: 48 83 c4 08 add $0x8,%rsp
ebf: 5b pop %rbx
ec0: 5d pop %rbp
ec1: c3 retq
edi<=1时,返回1,否则返回执行func(n-1)+fun(n-2),所以这是一个满足斐波那契数列的关系式(1,1,2,3,5,8,13,21,34,55)。即:
def func:
if n <= 1:
return 1
else:
return func(n-1)+func(n-2)
当n==9时,恰好返回55.故答案为9
总结与反思
其实完成这个实验,找到正确答案并不难,更重要的是实验过程中对代码的理解,以及对函数调用中栈帧变化的理解,能够更深入地认识代码执行的过程,理解背后的原理,对于自己能力还是有一定锻炼的。
此外,还通过本次实验掌握了wsl的使用及gdb的调试,收获很大。