CSAPP bomb lab
Bomb
GDB命令 常用
打断点
b phase_1 为方法断点
b explode_bomb 为爆炸方法断点
b *0x123456 为该地址指令断点
执行
layout asm 动态显示当前执行的汇编代码 (ctrl + X 再 A 退出layout)
disas pahse_1 静态显示指定方法的汇编代码
r anx.txt 指定有答案的文件执行
r 开始执行
c 继续
ni 一条一条指令执行运行
si 一条一条指令执行运行,走到方法会进去
打印
p $rax 打印rax内容
p/x 打印rax内容为十六进制
p/d 打印rax内容十进制
x/s $rax 打印rax地址的值
x/<n/f/u> [<addr>|$<reg>]
n往后几个
f 多少字节一个单位:b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes
u什么格式输出:x=十六进制,o =八进制,d=十进制,s=按字符串,
已知在0x7fffffffe2c8和 0x7fffffffe2cc保存了两个int(这两个地址相邻),分别为7 和9,
当我想要查看这两可以使用x/2wd命令,打印内存的值
2:输出2个,w:4字节一个单位,d:十进制格式输出

想要查看字符串可以x/s

phase_1

callq string_not_equla后,
test %eax %eax 就可以跳转到 400ef7 成功返回 ,
否则如果eax不是0触发 callq explpde_bomb直接炸
所以在 string_not_equal 寻找如何让eax变成0

40113c 40113f 接收两个参数 分别放到rbx和rbp,
401342 call string_length算长度 结果放在rax,
401347 然后通过第一个参数长度 从rax放到r12d
40134a 把rbp的参数放到rdi
40134d 调用call string_length算长度 第二个参数长度结果放在rax
401352 edx默认1
401357 比较rax和r12d,如果不相等,直接跳到40139b,把edx的1赋值给rax,返回phase_1,直接炸
如果长度相等
根据401372 401376每次rbx和rpx加一
可以判断字符串一个一个循环比较,相等比较下一个位置,直到全部相等。
所以,这个方法是判断两个字符串是否完全相等,关键是查看参数具体值,需要gdb来打印内存的值

可以看到rdi是我自己输入的,rsi就是密码,因此复制粘贴 输入
Border relations with Canada have never been better.
通过第一个炸弹
phase_2

根据sub 0x28 %rsp,rsp=rsp-40 可知即将有局部变量存入栈中(设当前rsp地址=r)
400f05读取6个数字,
400f0a 判断rsp指针所指的值是否等于1 jump,否则explpde_bomb(第一个数(rsp)=1)
400f30 rbx=rsp+4(r+4) 栈指针向后 (第二个数)
400f35 rbp =rsp+24 (r+24)栈指针向后 (第六个数)
jump
400f17 rax=(rbx-4)=(r)=1
400f1a rax = rax+rax=2
400f1c 比较rax和(rbx)的的值
可以知道 (rbx)=2(第二个数(rsp+4)=2)
400f25 rbx=rbx+4=(r+8) 继续向后(第三个数)
400f29 rbx和rbp不是一个地址,即是所有数字判断完了, jump
400f17 rax=(rbx-4)=(r+4)=2
400f1a rax = rax+rax=4
400f1c 比较rax和(rbx)的的值
可以知道 (rbx)=4(第三个数(rsp+8)=4)
….
可以得出规律,后一个数就是前一个数的2倍
所以6个数字为以下,解除第二个炸弹
1 2 4 8 16 32

phase_3

首先学习一下sscanf用法,
int a;
int r =sscanf("10","%d",&a)
//r=1 a=10
int a1;
int a2;
int r =sscanf("10 20","%d %d",&a1,&a2)
//r=2 a1=10 a2=20
根据csapp寄存器参数使用顺序可以分析出,sscanf接收参数(rdi,rsi,&rcx,&rdx),
因此输入的两个参数分别在rsp+12和rsp+8里面,并且返回rax=2

实际上可以看到执行完 sscanf返回rax=2

400f60 cmp $0x1,%eax 因为rax=2>1 所以jump 400f6a
400f6a cmpl $0x7,0x8(%rsp),
400f6f 如果(rsp+8)>7那么 jump 400fad,explode_bomb,所以(rsp+8)必须<=7,
400f71 rax=(rsp+8)
400f75 jump *(8 * rax+0x402470)地址所指向的地址 (一定要在400f7c到400fb2之间,才能往下继续)然后给rax赋值
指针地址有 0x402470 0x402478 0x402480 0x402488 0x402490 0x402498 0x4024A0 0x4024A8
所以依次查看这些地址的值
x打印指针的值 8gx(后显示8个地址,g表示八字节,按十六进制格式显示变量)

根据switch(rax) case一下8种

最终要去
400fbe cmp 0xc(%rsp),%eax,如果(rsp+12)!= rax 那么 explode_bomb
所以(rsp+8)和(rsp+12)之前存在着联动
具体步骤
当rax=(rsp+8)=0 jump 0x0000000000400f7c, rax=0xcf = 207 =>(rsp+8)=0 (rsp+12)=207
当rax=(rsp+8)=1 jump 0x0000000000400fb9, rax=0x137=311=>(rsp+8)=1 (rsp+12)=311
当rax=(rsp+8)=2 jump 0x0000000000400f83, rax=0x2c3=707 =>(rsp+8)=2 (rsp+12)=707
当rax=(rsp+8)=3 jump 0x0000000000400f8a, rax=0x100=256 =>(rsp+8)=3 (rsp+12)=256
当rax=(rsp+8)=4 jump 0x0000000000400f91, rax=0x185=389 =>(rsp+8)=4 (rsp+12)=389
当rax=(rsp+8)=5 jump 0x0000000000400f98, rax=0xce=206 =>(rsp+8)=5 (rsp+12)=206
当rax=(rsp+8)=6 jump 0x0000000000400f9f, rax=0x2aa=682 =>(rsp+8)=6 (rsp+12)=682
当rax=(rsp+8)=7 jump 0x0000000000400fa6, rax=0x147=327 =>(rsp+8)=7 (rsp+12)=327
又因为参数越靠后栈的位置越大(越靠近栈底部) 所以 (rsp+8)为第1个 ,(rsp+12)为第二个
输入以上组合都可以,解除第三个炸弹


phase_4

40100c-401029:和phase_3一模一样,sscanf接收参数(rdi,rsi,&rcx,&rdx),返回2成功,否则bomb!
401029: 比较M[rsp+8](即第一个参数)和0xe(十进制的14),如果M[rsp+8]大于等于14,那么bomb!
40103a-401048: 如果M[rsp+8]小于14,那么令rdx=14,rsi=0,rdi=M[rsp+8],根据寄存器作为参数的顺序可以知道,调用rax=func(rdi,rsi,rdx),暂时不管,我们看后续
40104d-40104f:比较rax是否等于0,如果不等于0,直接跳转到bomb
401051:比较M[rsp+12]是否等于0,如果等于跳到return,成功
经过上面分析我们知道了,rax=func4(M[rsp+8],0,14) 必须等于0,arg1=M[rsp+8]必须<14,arg2=M[rsp+12]必须=0
所以核心点在func4,当M[rsp+8]等于多少的时候rax才能为0,读汇编代码,可以很容易把func4写出来,下面是java代码的实现,我们挨个尝试
public static int func4(int rdi, int rsi, int rdx) {
int rax = rdx;
rax = rdx - rsi;
//逻辑
int rcx = rax >>> 31;
rax = rcx + rax;
//算数
rax = rax >> 1;
rcx = rsi + rax;
if (rcx <= rdi) {
rax = 0;
if (rcx >= rdi) {
return rax;
} else {
rsi = rcx + 1;
int r = func4(rdi, rsi, rdx);
rax = r + r + 1;
return rax;
}
}else {
rdx = rcx - 1;
int r = func4(rdi, rsi, rdx);
rax = r + r;
return rax;
}
}
public static void main(String[] args) {
for (int i = 0; i < 14; i++) {
int result = func4(i, 0, 14);
System.out.println("args1: " + i+",result: " + result);
}
}

看看结果,选 result 为 0 的args1作为参数1,依次带入实验都可以成功0,1,3,7
所以可有输入0 0或1 0或3 0或 7 0
因此输入以上组合都可以,解除第四个炸弹


phase_5
查看关键地址的值
其中答案在array =”maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?”

401062 - 401089 :输入一个长度为6的字符串,指针地址保存到rbx,否则call explode_bomb
401089: jump 4010d2
4010d2:rax从0开始
40108b-4010a8 开始进入循环处理我们输入的字符串
40108b: rcx = rax+rbx (从rbx头开始往后取) 分别是rbx+0, rbx+1…… rbx+5(循环取字符串的每一位)
40196: rdx= rdx & 1111 (字符&111)
40199 : rdx = 0x4024b0+rdx (取数组的第(rdx)个字符
4010a0: 把字符存在rsp+16+rax的地址,循环六次,依次存 arry[rbx[i]&111]构成一个rdi的指针
最终rdi( array [rbx[0]&1111], array [字符[1]&1111], array [rbx[2]&1111]… array [rbx[5]&1111])和rsi(flyers)比较
所以
rbx[0]&1111=9
rbx[1]&1111=15
rbx[2]&1111=14
rbx[3]&1111=5
rbx[4]&1111=6
rbx[5]&1111=7

可以得到
man ascii


看图可以得到满足条件的有
0x29=>) 因为0x29&1111=9 array [9] = f (或者0x39=>9 0x39&1111=9 )
0x2f=>/ 0x2f&1111=15 array [15] = e 后面以次类推 (或者0x3f=>? 0x39&1111=9 )
0x2e=>. (或者0x3e=» 0x39&1111=15 )
0x25=>% (或者0x35=>5 0x35&1111=5 )
0x26=>& (或者0x36=>6 0x36&1111=6 )
0x27=>’ (或者0x37=>7 0x37&1111=7 )
答案: )/.%&’ 或9?>567(顺序一样的字符可以替换 例如 9/.567也可以)
成功

phase_6
主要分为三个阶段
阶段1
判断数据格式 数组必须 <=6 ,并且不能有重复,类似于以下代码
//汇编行数 40110b-401151
for (int i = 0; i < 6; i++) {
for (int j = i + 1; j < 6; j++) {
if (array[i] > 6) {
System.out.println("bomb");
return;
}
if (array[i] == array[j]) {
System.out.println("bomb");
return;
}
}
}
阶段2
接下来的代码,很容易的看出来就是循环把数组每个值,改成7-array[i]

类似于以下代码
for (int i = 0; i < 6; i++) {
array[i] = 7 - array[i];
}
阶段3
调整node顺序,根据地址0x6032d0,我们慢慢分析这个数据结构

这个数据机构的第以为是int(4字节)类型或者long(8字节)
那么我们往后4字节(0x6032d4),或者8字节(0x6032d8)看看里面装的是什么,(下面直接说结论了,自己试试看)

上图所示后面8字节也是一个地址,那我我继续看看0x6032d8的内容
x/12gx 0x6032d0

上图所示,是一个数字也是int或者long,按照上面的流程,继续探索可以得到结论
所以这个是一个链表,并且有6个node,并且地址是连续的ndoe
struct{
long val;
node* next;
}node
//括号里面是值,每个node占8+8=16字节
//同时可以推算出他们的每个node的地址
//node1(332)->node2(168)->node3(924)->node4(691)->node5(477)->node6(443)
去看阶段4的结论,才知道我们下面的操作要干什么(即重新编排node的顺序,让他依次递减)
下面的汇编重点,给node编排位置
401188:mov %rdx,0x20(%rsp,%rsi,2)
40118d:add $0x4,%rsi
401191:cmp $0x18,%rsi
401195:je 4011ab <phase_6+0xb7>
401197:mov (%rsp,%rsi,1),%ecx
翻译成java语言,rcx为数组的值,循环次数,直到我们得到想要的rdx地址,rsi为地址的顺序.
rsp:数组地址 rsp 到 rsp+32,所以node要排在rsp+32后面
do {//401176
rdx = (rdx + 8);
rax = rax + 1;
} while (rax != rcx);
//rsi=0
rsp + 2 * rsi + 32 = rdx;
rsi = rsi + 4;
举个实例,红色框是我们6个node的位置,需要我们去填 前面的就是rsp到rsp+32,是数组的值这里是 4 3 2 1 6 5

我们想要的是这样的编排,第一个是node3,所以我们要循环2次,所以arr[0]=3=>rsi=0,rdx=rsp+32
//rsp+0+32=0x6032d0+16*2 node3
//rsp+8+32=0x6032d0+16*3 node4
//rsp+16+32=0x6032d0+16*4 node5
//rsp+24+32=0x6032d0+16*5 node6
//rsp+32+32=0x6032d0+16*0 node1
//rsp+40+32=0x6032d0+16*1 node2
所以结合上面的结论
要先让node3在第一个,循环两次rsp+rsi(0)+32=rdx=0x6032d0+8+8
所以arry[0]应该是3,由于之前arry[i]=7-arry[i],所指真正的arry[0]= 4。
需要循环array={3,4,5,6,1,2},
最终编排完毕的结果如下,六个node指针分别
0x6032f0->0x603300->..->0x6032e0
一句话就是:输入六个数子,然后7-六个数子,最后结果是不是从大到小的节点序号

综上;array={4,3,2,1,6,5}
阶段四
把node串起来,判断node链表是否依次递减的
总结
以上全是自己做的,没用看网上的思路,自己只查询了汇编用法,gdb用法,感觉最重要的是学会gdb调试命令 b,ni, p, x,有编程基础的话,一步一步调试,看看汇编代码,自己试着写出高级语言代码,就能感觉出他的想法
