目录
编译系统
对于源代码hello.c
。
gcc -Og -S hello.c
,则生成hello.s汇编程序。gcc -Og -c hello.c
,则生成生成hello.o可重定位文件。gcc -Og hello.c
,则生成可执行文件a.out.- 可以使用
objdump -d a.out
对.o文件或.out文件进行反汇编。
常用寄存器
操作数格式
数据传送指令
gcc 和 gdb
gdb常用指令
layout 指令非常好用 。
lab1
之前做了该实验的前三部分博客。方法有些问题,过于纠结一些库函数的汇编代码。本次重新写一遍,并且做完该实验。
phase_1
$gdb bomb
调试bomb程序
(gdb) b phase_1
在函数phase_1处打上断点
(gdb) r
运行
(gdb) layout regs
查看汇编代码及寄存器
如下,可以发现phase_1偏移9的位置的strings_not_equl
方法是关键l,再使用si
进入strings_not_equal
方法。
如上图,在strings_not_equal
运行到第4行以及第18行的时候,通过查看内存位置是$rdi
(调用string_length的参数)的内容,即可找到我们要比较的字符串。如下图
因此,第一个密码是Border relations with Canada have never been better.
phase_2
编辑input
文件内容如下,方便每次调试不用重新输入之前的密码。
Border relations with Canada have never been better.
$gdb bomb
(gdb) b phase_2
断点打在phase_2
函数上
(gdb) r input
在此步骤以后随便输入6个数字> 1 2 3 4 5 6
(gdb) layout regs
在此不对read_six_numbers
进行详细分析可以尝试不同的输入跟踪该方法(该方法如果长度不满足要求也会引爆炸弹),从结果看,可以发现read_six_numbers
方法读入6个数字到栈顶,紧接着判断第一个元素是否是1。
通过上述分析,需要判断下一个数字是否是上一个数字的两倍,即最终正确的输入需要是>1 2 4 8 16 32
phase_3
分析
本次lab3比上两个要难,在此之前,让我们搞清楚一些问题。
- read_line读入的数据在哪?(以输入
>1 3 4
为例子)
在main 函数中可以发现char *input;
,并没有分配数组,同时,通过后面的DEBUG,可以发现这些数据实际上是通过read_line
函数分配在了堆区(根据地址位置)。 - 在
phase_x
中调用函数时候传入的参数为char*指针input
,即寄存器edi。
solution
对phase_3
的反汇编代码进行分析。
$gdb bomb
$(gdb) disas phase_3
结合下面三个图,非常容易分析得到结果。
那么,可以获得最终结果是上述任意一个分支都可以。
即
0 207
1 311
2 707
3 256
4 389
5 206
6 682
7 327
phase_4
首先进行反汇编phase_4
函数,如果phase_3理解了,可以轻松明白phase_4的汇编代码。
可以看到,该函数的关键在于func4函数,可以接着对func4进行反汇编。
很容易发现,func4是个递归函数,果然递归的反汇编难读。我在参考了别的BLOG之后得到思路是先写成C函数,再求解,因此,我得到了如下代码。
//第一次调用时,y = 0, z = 14
int func4(int x, int y, int z) {
//x = edi; y = esi; z = edx;
int a = z - y;
a = (a + (a >> 31)) >> 1;
int c = a + y; //第一次调用时 c = 7
if (x <= c) {
a = 0;
if (x >= c) {
return a;
}
else {
a = func4(x, c + 1, z);
a = a + a + 1;
return a;
}
}
else {
a = func4(x, y , c - 1);
a = a + a;
}
return a;
}
如果想让func4返回0,则必须x = 7。因此最后拆炸弹的密码为7 0
phase_5
该步难度还行,直接反汇编看看代码,纠结了一会,再DEBUG就可以搞定。代码关键部分要搞清楚即可(用--------分割)
0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx //bx = rdi
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: callq 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax //输入的长度必须是6
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
---------------------------------------------------------------------------------------------
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx //cx为输入的字符ASCII用char表示
0x000000000040108f <+45>: mov %cl,(%rsp) //栈顶 = char
0x0000000000401092 <+48>: mov (%rsp),%rdx //rdx = char
0x0000000000401096 <+52>: and $0xf,%edx //edx = char保留低4位
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx //(rdx这里用作偏移地址)取到的这个字符是什么????不知道就运行时候DEBUG看看
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1) //6个不知名字符放在栈偏移0x10~0x15上
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax //循环6次
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
---------------------------------------------------------------------------------------------
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi //把上面核心段中的0x10~0x15字符串和$0x40245e位置上的字符串进行比较
0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax //ax = 0;
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt> //该段用于栈保护,执行前看看值,函数返回之前看看值,估计是怕防止覆盖了返回地址。
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: retq
通过上述反汇编代码,可以发现我们需要关注两个东西,如下。(红色标注的是需要构造出来的各个字符的偏移地址)
实际上这题的意思就是让我们用输入的6个字符的低4位作为偏移地址去取6个字符,构成flyers。那查查ASCII表挑几个字母即可解决。
需要注意的是,因为取低4位,因此我们只能在字符串前16位中找我们需要的字符串。
flyers
对应的位置分别是9 F E 5 6 7
我取了ionuvw
字符串作为结果。
phase_6
该方法的汇编代码可以说是很长了,分段分析
0x00000000004010f4 <+0>: push %r14
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx
0x00000000004010fc <+8>: sub $0x50,%rsp
0x0000000000401100 <+12>: mov %rsp,%r13 //r13栈顶
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers>
0x000000000040110b <+23>: mov %rsp,%r14 //r14存储6个数字的位置
0x000000000040110e <+26>: mov $0x0,%r12d
0x0000000000401114 <+32>: mov %r13,%rbp //rbp栈顶
0x0000000000401117 <+35>: mov 0x0(%r13),%eax //ax为第一个数字
0x000000000040111b <+39>: sub $0x1,%eax
0x000000000040111e <+42>: cmp $0x5,%eax
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52> //需要ax <=5
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d
0x000000000040112c <+56>: cmp $0x6,%r12d
0x0000000000401130 <+60>: je 0x401153 <phase_6+95> //刚开始不会跳转
0x0000000000401132 <+62>: mov %r12d,%ebx
0x0000000000401135 <+65>: movslq %ebx,%rax //ebx和abx是偏移地址
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax //取下一个数字
0x000000000040113b <+71>: cmp %eax,0x0(%rbp)
0x000000000040113e <+74>: jne 0x401145 <phase_6+81> //数字不能和第一个相等
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx
0x0000000000401148 <+84>: cmp $0x5,%ebx
0x000000000040114b <+87>: jle 0x401135 <phase_6+65> //偏移地址<=5
0x000000000040114d <+89>: add $0x4,%r13
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32> //大循环,依次判断每一个数字
该段代码的作用是读入6个数字到栈顶,并且每一个数必须小于等于6代码地址<+45>
,并且这6个数字互不相同代码地址<+65>~<+87>
。这一段就像是一个冒泡比较(第一个数字比后5个,第二个数比后4个。。。最后一个数字不需要再比了),因此需要读入的1 2 3 4 5 6
,后面考虑考虑排列顺序。
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi rsi = end(input)(4 * 6 = 24)
0x0000000000401158 <+100>: mov %r14,%rax ax = input;
0x000000000040115b <+103>: mov $0x7,%ecx cx = 7;
0x0000000000401160 <+108>: mov %ecx,%edx dx = 7;
0x0000000000401162 <+110>: sub (%rax),%edx dx -= input[0]
0x0000000000401164 <+112>: mov %edx,(%rax) input[0] = dx
0x0000000000401166 <+114>: add $0x4,%rax ax = input++
0x000000000040116a <+118>: cmp %rsi,%rax rsi != input
0x000000000040116d <+121>: jne 0x401160 <phase_6+108> //input[i] = 7-input[i]
这一段是将input[i]替换成7-input[i]
0x000000000040116f <+123>: mov $0x0,%esi esi = 0
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx 即dx = dx.next
0x000000000040117a <+134>: add $0x1,%eax ax++;
0x000000000040117d <+137>: cmp %ecx,%eax input[esi] == ax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
0x0000000000401183 <+143>: mov $0x6032d0,%edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) //node中的第input[esi]个节点放在第rsi个
0x000000000040118d <+153>: add $0x4,%rsi
0x0000000000401191 <+157>: cmp $0x18,%rsi
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183> //这一段循环6次
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx cx = input[esi]
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> input[esi] <= 1
0x000000000040119f <+171>: mov $0x1,%eax ax = 1
0x00000000004011a4 <+176>: mov $0x6032d0,%edx dx = 0x6032d0
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
实际效果啊参考这位老哥的图。注意下图他的输入数据应该是翻转以后的即真实输入时4,5,3,1,2,6
(7 - input[i]),这位老哥最后一步分析出了错误
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx //rbx为栈上存node的地址
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax //rax = rbx + 0x20 + 0x8
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi //rax = rbx + 0x20 + 0x30
0x00000000004011ba <+198>: mov %rbx,%rcx //cx = rbx
0x00000000004011bd <+201>: mov (%rax),%rdx //dx = *ax
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) //重新修改node节点的next指针
0x00000000004011c4 <+208>: add $0x8,%rax
0x00000000004011c8 <+212>: cmp %rsi,%rax //继续再栈上遍历下一个node
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov %rdx,%rcx //cx指向下一个
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx) //修改最后一个节点的尾指针
0x00000000004011da <+230>: mov $0x5,%ebp //ebp = 5
0x00000000004011df <+235>: mov 0x8(%rbx),%rax //ax = rbx.next
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx) //比较node的第一个域
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250> //(rbx) > rbx.next 降序!
0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: retq
最后一段较为简单,就是判断按照重新排列node,第一个域为降序即可。
渴望顺序3 4 5 6 1 2
,计算7-input[i],真实输入为4 3 2 1 6 5
至此,拆除炸弹成功!
结果
input
Border relations with Canada have never been better.
1 2 4 8 16 32
3 256
7 0
ionuvw
4 3 2 1 6 5
secret_phase(隐藏关?)
根据源代码提示有隐藏关,并且在函数中不停的调用一个phase_defused
没什么影响的方法,那说明这个和隐藏关有关。
/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
进入隐藏关
找到和隐藏关对应的函数phase_defused
,反汇编它。先进行注释,下面接着分析。
0x00000000004015c4 <+0>: sub $0x78,%rsp
0x00000000004015c8 <+4>: mov %fs:0x28,%rax
0x00000000004015d1 <+13>: mov %rax,0x68(%rsp)
0x00000000004015d6 <+18>: xor %eax,%eax
0x00000000004015d8 <+20>: cmpl $0x6,0x202181(%rip) # 0x603760 <num_input_strings>
0x00000000004015df <+27>: jne 0x40163f <phase_defused+123> //断点调试发现通过第六关才能进入下段代码
0x00000000004015e1 <+29>: lea 0x10(%rsp),%r8 //参数5
0x00000000004015e6 <+34>: lea 0xc(%rsp),%rcx //参数4
0x00000000004015eb <+39>: lea 0x8(%rsp),%rdx //参数3
0x00000000004015f0 <+44>: mov $0x402619,%esi //参数2"%d %d %s"
0x00000000004015f5 <+49>: mov $0x603870,%edi //参数1"7 0"
0x00000000004015fa <+54>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x00000000004015ff <+59>: cmp $0x3,%eax
0x0000000000401602 <+62>: jne 0x401635 <phase_defused+113> //这里必须读了3个数
0x0000000000401604 <+64>: mov $0x402622,%esi //"DrEvil"
0x0000000000401609 <+69>: lea 0x10(%rsp),%rdi
0x000000000040160e <+74>: callq 0x401338 <strings_not_equal> //必须读一个 DrEvil
0x0000000000401613 <+79>: test %eax,%eax
0x0000000000401615 <+81>: jne 0x401635 <phase_defused+113>
0x0000000000401617 <+83>: mov $0x4024f8,%edi
0x000000000040161c <+88>: callq 0x400b10 <puts@plt>
0x0000000000401621 <+93>: mov $0x402520,%edi
0x0000000000401626 <+98>: callq 0x400b10 <puts@plt>
0x000000000040162b <+103>: mov $0x0,%eax
0x0000000000401630 <+108>: callq 0x401242 <secret_phase>
0x0000000000401635 <+113>: mov $0x402558,%edi
0x000000000040163a <+118>: callq 0x400b10 <puts@plt>
0x000000000040163f <+123>: mov 0x68(%rsp),%rax
0x0000000000401644 <+128>: xor %fs:0x28,%rax
0x000000000040164d <+137>: je 0x401654 <phase_defused+144>
0x000000000040164f <+139>: callq 0x400b30 <__stack_chk_fail@plt>
0x0000000000401654 <+144>: add $0x78,%rsp
0x0000000000401658 <+148>: retq
<+27>行代码
发现只有第六个关卡才可以进入(应该还有其他途径分析得出)<+27>~<+49>行代码
发现调用了sscanf("7 0", "%d %d %s", 0x8(%rsp), 0x10(%rsp),0x10(%rsp))
<+64>~<+74>行代码
发现0x10(%rsp)
中存储的字符串需要是DrEvil
到此,如果敏感可以看出来7 0 这两个数是我们为过第四关的输入。那么我们可以尝试在这之后在输入一个字符出DrEvil
。即7 0 DrEvil
如果说猜的不靠谱,那我们可以打给phase_4
打断点,如下图,很显然,readline动态分配的地址就是6305904。
secret_phase(解决隐藏关)
0x0000000000401242 <+0>: push %rbx
0x0000000000401243 <+1>: callq 0x40149e <read_line>
0x0000000000401248 <+6>: mov $0xa,%edx
0x000000000040124d <+11>: mov $0x0,%esi
0x0000000000401252 <+16>: mov %rax,%rdi
0x0000000000401255 <+19>: callq 0x400bd0 <strtol@plt> //将输入rdi转成长整型
0x000000000040125a <+24>: mov %rax,%rbx
0x000000000040125d <+27>: lea -0x1(%rax),%eax
0x0000000000401260 <+30>: cmp $0x3e8,%eax //0x3e8 = 1000
0x0000000000401265 <+35>: jbe 0x40126c <secret_phase+42> //该整数需要小于等于1001
0x0000000000401267 <+37>: callq 0x40143a <explode_bomb>
0x000000000040126c <+42>: mov %ebx,%esi //参数2 输入的数字
0x000000000040126e <+44>: mov $0x6030f0,%edi //参数1 存的值是24
0x0000000000401273 <+49>: callq 0x401204 <fun7>
0x0000000000401278 <+54>: cmp $0x2,%eax //fun7函数需要返回2
0x000000000040127b <+57>: je 0x401282 <secret_phase+64>
0x000000000040127d <+59>: callq 0x40143a <explode_bomb>
0x0000000000401282 <+64>: mov $0x402438,%edi
0x0000000000401287 <+69>: callq 0x400b10 <puts@plt>
0x000000000040128c <+74>: callq 0x4015c4 <phase_defused>
0x0000000000401291 <+79>: pop %rbx
0x0000000000401292 <+80>: retq
上述代码较容易理解,下面看看fun7的反汇编代码。(显然是个递归函数,那不多说,直接翻译成c语言),需要fun7函数需要返回2。
0x0000000000401204 <+0>: sub $0x8,%rsp
0x0000000000401208 <+4>: test %rdi,%rdi
0x000000000040120b <+7>: je 0x401238 <fun7+52>
0x000000000040120d <+9>: mov (%rdi),%edx
0x000000000040120f <+11>: cmp %esi,%edx
0x0000000000401211 <+13>: jle 0x401220 <fun7+28>
0x0000000000401213 <+15>: mov 0x8(%rdi),%rdi
0x0000000000401217 <+19>: callq 0x401204 <fun7>
0x000000000040121c <+24>: add %eax,%eax
0x000000000040121e <+26>: jmp 0x40123d <fun7+57>
0x0000000000401220 <+28>: mov $0x0,%eax
0x0000000000401225 <+33>: cmp %esi,%edx
0x0000000000401227 <+35>: je 0x40123d <fun7+57>
0x0000000000401229 <+37>: mov 0x10(%rdi),%rdi
0x000000000040122d <+41>: callq 0x401204 <fun7>
0x0000000000401232 <+46>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401236 <+50>: jmp 0x40123d <fun7+57>
0x0000000000401238 <+52>: mov $0xffffffff,%eax
0x000000000040123d <+57>: add $0x8,%rsp
0x0000000000401241 <+61>: retq
对应的C代码如下:
//x = rdi, y = esi
//初始 x = 0x6030f0 *x = 24, esi = input
int func(* x, int y) {
dx = *x;
if (dx <= y) {
ax = 0;
dx = y;
if (dx == y) {
return ax;
} else {
x = *(x + 0x10);
ax = func(x, y)
ax = 2*ax + 1;
}
} else {
x = *(x + 0x8);
ax = func(x, y);
ax = ax + ax;
return ax;
}
}
继续往下分析很困惑,我只看出来x指针含有3个域,而不同的分支分别进入了x = *(x + 0x8);
和*(x + 0x10)
。其实这个数据结构是有val,left和right
的树。那就可以稍作修改(没什么别要)。如下:
int fun7(node *rt, int y) {
int ans;
int val = rt->val;
if (val <= y) {
if (val == y)
return 0;
else {
ans = fun7(x.right, y);
return 2 * ans + 1;
}
} else {
ans = fun7(rt->left, y);
return 2 * ans;
}
}
需要初始调用fun7函数返回2。找到一个调用栈为(val > y)
-> (val < y)
-> (val == y)
根节点(val > y)
-> 左孩子(val < y)
-> 左孩子的右边孩子(val == y)
综上,最后的结果为0x16,即22。
最终答案:
Border relations with Canada have never been better.
1 2 4 8 16 32
3 256
7 0 DrEvil
ionuvw
4 3 2 1 6 5
22
通关截图