CSAPP之Bomb LAB

Bomb LAB

phase_1

直接IDA或者gdb断点进去看到一个比较函数,字符串为Border relations with Canada have never been better.直接输入过第一关

phase_2

因为是学习,尽量不用IDA的F5,锻炼看汇编的能力,用gdb查看炸弹函数的汇编代码

0x0000000000400efc <+0>:     push   rbp
0x0000000000400efd <+1>: push rbx
0x0000000000400efe <+2>: sub rsp,0x28
0x0000000000400f02 <+6>: mov rsi,rsp
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers> #要传6个参数,否则bomb
0x0000000000400f0a <+14>: cmp DWORD PTR [rsp],0x1 #第一个参数一定要是1
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov eax,DWORD PTR [rbx-0x4]
0x0000000000400f1a <+30>: add eax,eax
0x0000000000400f1c <+32>: cmp DWORD PTR [rbx],eax
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add rbx,0x4
0x0000000000400f29 <+45>: cmp rbx,rbp
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea rbx,[rsp+0x4]
0x0000000000400f35 <+57>: lea rbp,[rsp+0x18]
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add rsp,0x28
0x0000000000400f40 <+68>: pop rbx
0x0000000000400f41 <+69>: pop rbp
0x0000000000400f42 <+70>: ret

传入参数通过rbx来寻址,逻辑是一个简单的首项为1,公比为2的等比数列,所以我们传入的应该为1 2 4 8 16 32

看到+57处有个lea rbp,[rsp+0x18],这里是相当于设置一个比较值,判断是否循环有6次了,因为

0x18 = 24
24 / 4 = 6

因为每次写下一题都要先过上一题,所以这里建议将答案写在一个文件里,方便下断点调试,在gdb里就可以直接

例如

b *(0x0x0000000000400f5b) 
r exp.txt

phase_3

先看汇编

0x0000000000400f43 <+0>:     sub    rsp,0x18
0x0000000000400f47 <+4>: lea rcx,[rsp+0xc]
0x0000000000400f4c <+9>: lea rdx,[rsp+0x8]
0x0000000000400f51 <+14>: mov esi,0x4025cf
/*
x/s 0x4025cf
0x4025cf: "%d %d"
读入两个参数
*/
0x0000000000400f56 <+19>: mov eax,0x0
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt> #返回值要大于1
0x0000000000400f60 <+29>: cmp eax,0x1
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmp DWORD PTR [rsp+0x8],0x7 #第一个参数值不能大于7, 否则bomb
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov eax,DWORD PTR [rsp+0x8]
0x0000000000400f75 <+50>: jmp QWORD PTR [rax*8+0x402470]
0x0000000000400f7c <+57>: mov eax,0xcf
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov eax,0x2c3
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov eax,0x100
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov eax,0x185
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov eax,0xce
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov eax,0x2aa
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov eax,0x147
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov eax,0x0
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov eax,0x137
0x0000000000400fbe <+123>: cmp eax,DWORD PTR [rsp+0xc]
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add rsp,0x18
0x0000000000400fcd <+138>: ret

熟悉的师傅可以看到0x0000000000400f75 <+50>: jmp QWORD PTR [rax*8+0x402470]这是一个典型switch语句实现,并且有一串的mov jmp,也可用IDA反汇编看看伪C代码,我们可以发现,所有分支都会跳到0x0000000000400fb9的位置,比较eax 和 我们输入的第二个参数

所以这里我输入 1 311

phase_4

这里有两个函数,首先看炸弹

0x000000000040100c <+0>:     sub    rsp,0x18
0x0000000000401010 <+4>: lea rcx,[rsp+0xc] #参数2
0x0000000000401015 <+9>: lea rdx,[rsp+0x8] #参数1
0x000000000040101a <+14>: mov esi,0x4025cf # %d %d
0x000000000040101f <+19>: mov eax,0x0 #eax = 0
0x0000000000401024 <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp eax,0x2 #两个数字,否则bomb
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmp DWORD PTR [rsp+0x8],0xe # 1参 <= 0xe
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov edx,0xe #edx = 0xe
0x000000000040103f <+51>: mov esi,0x0 #esi = 0
0x0000000000401044 <+56>: mov edi,DWORD PTR [rsp+0x8] #edi = 1参数
0x0000000000401048 <+60>: call 0x400fce <func4>
0x000000000040104d <+65>: test eax,eax #测试eax是否为空,是空则跳转,bomb
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmp DWORD PTR [rsp+0xc],0x0 #2参为0,则跳转
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>
0x000000000040105d <+81>: add rsp,0x18
0x0000000000401061 <+85>: ret

这里我们知道输入两个参数,且参数2为0参数1 <= 0xe,接着看func4

# edx = i, esi = j, edi = k, eax = result
0x0000000000400fce <+0>: sub rsp,0x8
0x0000000000400fd2 <+4>: mov eax,edx # eax = i
0x0000000000400fd4 <+6>: sub eax,esi # num = i - j
0x0000000000400fd6 <+8>: mov ecx,eax # val = num
0x0000000000400fd8 <+10>: shr ecx,0x1f # val = num >> 0x1f = 0
0x0000000000400fdb <+13>: add eax,ecx # temp = num + val 判断符号
0x0000000000400fdd <+15>: sar eax,1 # temp = temp / 2
0x0000000000400fdf <+17>: lea ecx,[rax+rsi*1] # (i/2 - j/2 + j) == (i + j) / 2;
0x0000000000400fe2 <+20>: cmp ecx,edi # temp,k
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
0x0000000000400fe6 <+24>: lea edx,[rcx-0x1] # i = temp - 1
0x0000000000400fe9 <+27>: call 0x400fce <func4>
0x0000000000400fee <+32>: add eax,eax # return result * 2
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov eax,0x0 # return result = 0
0x0000000000400ff7 <+41>: cmp ecx,edi
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
0x0000000000400ffb <+45>: lea esi,[rcx+0x1] # j = temp + 1
0x0000000000400ffe <+48>: call 0x400fce <func4>
0x0000000000401003 <+53>: lea eax,[rax+rax*1+0x1] # return result * 2 + 1
0x0000000000401007 <+57>: add rsp,0x8
0x000000000040100b <+61>: ret

可以发现是个递归函数,跟着汇编一步一步把C代码写出来,写个脚本求出值,这个还是花了不少时间,对汇编的结构还是不太熟悉,太🥬了

exp

#include "stdio.h"

int func4(int k, int j, int i)
{
// edx = i, esi = j, edi = k
int val, temp, result;
val = (i - j) >> 0x1f;
temp = (i-j) + val;
temp = temp >> 1;
temp += j;
if(temp > k)
{
return 2 * func4(k, j, temp - 1);
}
result = 0;
if(temp < k)
{
return 2 * func4(k, temp + 1, i) + 1;
}
return result;
}
int main(void)
{
for (int k = 0; k <= 14; ++k) {
if(func4(k, 0, 14) == 0)
{
printf("%d ", k);
}
}
return 0;
}

有四个答案,随便填一个就行

phase_5

0x0000000000401062 <+0>:     push   rbx
0x0000000000401063 <+1>: sub rsp,0x20
0x0000000000401067 <+5>: mov rbx,rdi
0x000000000040106a <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000401073 <+17>: mov QWORD PTR [rsp+0x18],rax #熟悉的canary,不过好像没啥用
0x0000000000401078 <+22>: xor eax,eax
0x000000000040107a <+24>: call 0x40131b <string_length>
0x000000000040107f <+29>: cmp eax,0x6 #读入长度为6的字符串
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: call 0x40143a <explode_bomba>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzx ecx,BYTE PTR [rbx+rax*1]
0x000000000040108f <+45>: mov BYTE PTR [rsp],cl
0x0000000000401092 <+48>: mov rdx,QWORD PTR [rsp]
0x0000000000401096 <+52>: and edx,0xf #获取当前字符串(rcx寄存器处)的后四位
0x0000000000401099 <+55>: movzx edx,BYTE PTR [rdx+0x4024b0] #以rdx为索引,从0x4024b0处的字符串中取出字符
0x00000000004010a0 <+62>: mov BYTE PTR [rsp+rax*1+0x10],dl #存起来
0x00000000004010a4 <+66>: add rax,0x1 #相当于循环计数器
0x00000000004010a8 <+70>: cmp rax,0x6
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: mov BYTE PTR [rsp+0x16],0x0
0x00000000004010b3 <+81>: mov esi,0x40245e # 要比较的字符串 ---> flyers
0x00000000004010b8 <+86>: lea rdi,[rsp+0x10]
0x00000000004010bd <+91>: call 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test eax,eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: call 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nop DWORD PTR [rax+rax*1+0x0]
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov eax,0x0
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov rax,QWORD PTR [rsp+0x18]
0x00000000004010de <+124>: xor rax,QWORD PTR fs:0x28
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: call 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add rsp,0x20
0x00000000004010f2 <+144>: pop rbx
0x00000000004010f3 <+145>: ret

大概意思就是对你输入的字符串每一个字符进行一顿操作,简单来说就是对每个字符进行& 0xf运算,然后将这个数当作索引,到上图第一个字符串里找到对应的那个字符存起来,把六位字符都处理成新字符在与后面那个字符串进行比较,相同就过关,可以写个脚本

exp

data = [9, 15, 14, 5, 6, 7]
payload = ''

for k in range(0, 6):
for i in range(97, 123):
t = i & 0xf
if t == data[k]:
payload += chr(i)
break

print(payload)
#ionefg
#当然答案不止这一个

这个更像逆向密码题..

phase_6

这题确实难,最好是分成一部分一部分地去分析,很多时候跳来跳去,寄存器的值根本记不清(寄,还好有强大的gdb

0x00000000004010f4 <+0>:     push   r14
0x00000000004010f6 <+2>: push r13
0x00000000004010f8 <+4>: push r12
0x00000000004010fa <+6>: push rbp
0x00000000004010fb <+7>: push rbx
0x00000000004010fc <+8>: sub rsp,0x50
0x0000000000401100 <+12>: mov r13,rsp #r13 = rsp
0x0000000000401103 <+15>: mov rsi,rsp #rsi = rsp
0x0000000000401106 <+18>: call 0x40145c <read_six_numbers> #经典六个数
0x000000000040110b <+23>: mov r14,rsp #r14 = rsp = r13 = rsi
0x000000000040110e <+26>: mov r12d,0x0 #r12d = 0
0x0000000000401114 <+32>: mov rbp,r13 #rbp = r13 = rsp
0x0000000000401117 <+35>: mov eax,DWORD PTR [r13] #rax = num[i]
0x000000000040111b <+39>: sub eax,0x1 #num[i] - 1
0x000000000040111e <+42>: cmp eax,0x5
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52> # num[i] <= 6
0x0000000000401123 <+47>: call 0x40143a <explode_bomb>
每个数要小于等于6
<---------------------------------------------------------------------------------------->
0x0000000000401128 <+52>: add r12d,0x1 #r12d += 1
0x000000000040112c <+56>: cmp r12d,0x6 #计数器
0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
0x0000000000401132 <+62>: mov ebx,r12d
0x0000000000401135 <+65>: movsxd rax,ebx
0x0000000000401138 <+68>: mov eax,DWORD PTR [rsp+rax*4] #eax = num[i+1]
0x000000000040113b <+71>: cmp DWORD PTR [rbp+0x0],eax #比较num[i]和num[i+1]
0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
0x0000000000401140 <+76>: call 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add ebx,0x1 #ebx += 1
0x0000000000401148 <+84>: cmp ebx,0x5 #计数器
0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
0x000000000040114d <+89>: add r13,0x4 #r13 += 4
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>
六个数各不相等,所以一定是 1 2 3 4 5 6 这六个数之间排序
<---------------------------------------------------------------------------------------->
0x0000000000401153 <+95>: lea rsi,[rsp+0x18] #rsi = [rsp+0x18] = 0x00
0x0000000000401158 <+100>: mov rax,r14 #rax = r14 = rsp
0x000000000040115b <+103>: mov ecx,0x7 #ecx = 0x7
0x0000000000401160 <+108>: mov edx,ecx #edx = 0x7
0x0000000000401162 <+110>: sub edx,DWORD PTR [rax] # edx = 7 - num[i]
0x0000000000401164 <+112>: mov DWORD PTR [rax],edx # 再把相减后的值放到rax里
0x0000000000401166 <+114>: add rax,0x4 #rax->num[i+1]
0x000000000040116a <+118>: cmp rax,rsi #比较num[i+1]与0
0x000000000040116d <+121>: jne 0x401160 <phase_6+108>
将所有值变为 7-num[i]
<---------------------------------------------------------------------------------------->
0x000000000040116f <+123>: mov esi,0x0 #esi = 0
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
0x0000000000401176 <+130>: mov rdx,QWORD PTR [rdx+0x8] #指向下一个值
0x000000000040117a <+134>: add eax,0x1
0x000000000040117d <+137>: cmp eax,ecx #循环累加eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
0x0000000000401183 <+143>: mov edx,0x6032d0
0x0000000000401188 <+148>: mov QWORD PTR [rsp+rsi*2+0x20],rdx #rdx->node6(0x1bb)
0x000000000040118d <+153>: add rsi,0x4
0x0000000000401191 <+157>: cmp rsi,0x18 #0x18 = 24 循环6
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
0x0000000000401197 <+163>: mov ecx,DWORD PTR [rsp+rsi*1] # ecx = 7-num[i]
0x000000000040119a <+166>: cmp ecx,0x1 ecx <= 0x1
0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
0x000000000040119f <+171>: mov eax,0x1 #eax = 1
0x00000000004011a4 <+176>: mov edx,0x6032d0 #奇怪的地址,见下面截图
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
<---------------------------------------------------------------------------------------->
node[i]->next = node[i-1];
类似遍历链表
0x00000000004011ab <+183>: mov rbx,QWORD PTR [rsp+0x20]
0x00000000004011b0 <+188>: lea rax,[rsp+0x28]
0x00000000004011b5 <+193>: lea rsi,[rsp+0x50]
0x00000000004011ba <+198>: mov rcx,rbx
0x00000000004011bd <+201>: mov rdx,QWORD PTR [rax]
0x00000000004011c0 <+204>: mov QWORD PTR [rcx+0x8],rdx
0x00000000004011c4 <+208>: add rax,0x8
0x00000000004011c8 <+212>: cmp rax,rsi
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
0x00000000004011cd <+217>: mov rcx,rdx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
<---------------------------------------------------------------------------------------->
0x00000000004011d2 <+222>: mov QWORD PTR [rdx+0x8],0x0
0x00000000004011da <+230>: mov ebp,0x5
0x00000000004011df <+235>: mov rax,QWORD PTR [rbx+0x8]
0x00000000004011e3 <+239>: mov eax,DWORD PTR [rax]
0x00000000004011e5 <+241>: cmp DWORD PTR [rbx],eax
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
0x00000000004011e9 <+245>: call 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov rbx,QWORD PTR [rbx+0x8]
0x00000000004011f2 <+254>: sub ebp,0x1
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
0x00000000004011f7 <+259>: add rsp,0x50
0x00000000004011fb <+263>: pop rbx
0x00000000004011fc <+264>: pop rbp
0x00000000004011fd <+265>: pop r12
0x00000000004011ff <+267>: pop r13
0x0000000000401201 <+269>: pop r14
0x0000000000401203 <+271>: ret

前面存着输入的值,后面存着后一个值的地址,加上node的字眼,明显就是链表的结构(虽然我数据结构没学好,我爬

稍微算算大概写出这个结构体

struct node{
int value; //4 node节点存储的数据
int index; //4 表示是在节点第几个元素
node* next; //8 指向下一个节点
};
hex->dec
0x14->1->332
0xa8->2->168
0x39c->3->924
0x2b3->4->691
0x1dd->5->477
0x1bb->6->443

7-num[i] -> value
value从大到小排列
3 4 5 6 1 2
所以输入序列应该为
4 3 2 1 6 5

这就拆完了六个关卡的炸弹

To Do…

作者

秋秋晚

发布于

2022-01-27

更新于

2023-01-10

许可协议

# 相关文章
  1.CSAPP之Attack LAB
评论

:D 一言句子获取中...