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:十进制格式输出

image-20231122094357700

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

image-20231122094838736

phase_1

image-20231115195822964

callq string_not_equla后,

test %eax %eax 就可以跳转到 400ef7 成功返回 ,

否则如果eax不是0触发 callq explpde_bomb直接炸

所以在 string_not_equal 寻找如何让eax变成0

image-20231115201645067

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来打印内存的值

image-20231116101958081

可以看到rdi是我自己输入的,rsi就是密码,因此复制粘贴 输入

Border relations with Canada have never been better.

通过第一个炸弹

phase_2

image-20231116102210507

根据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

image-20231116172028405

phase_3

image-20231116174332076

首先学习一下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

image-20231117094150799

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

image-20231117100507588

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表示八字节,按十六进制格式显示变量

image-20231117102856482

根据switch(rax) case一下8种

image-20231117103528287

最终要去

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)为第二个

输入以上组合都可以,解除第三个炸弹

image-20231117104608081

image-20231117104642978

phase_4

image-20231122091040389

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);
        }
    }


image-20231122093056241

看看结果,选 result 为 0 的args1作为参数1,依次带入实验都可以成功0,1,3,7

所以可有输入0 0或1 0或3 0或 7 0

因此输入以上组合都可以,解除第四个炸弹

image-20231122091320575

image-20231122091239359

phase_5

image-20231205173020691 查看关键地址的值

其中答案在array =”maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?”

image-20231206000326268

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

image-20231206001501142

image-20231206001519432

看图可以得到满足条件的有

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也可以)

成功

image-20231206002407449

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]

image-20231212093638034

类似于以下代码


for (int i = 0; i < 6; i++) {
	array[i] = 7 - array[i];
}

阶段3

调整node顺序,根据地址0x6032d0,我们慢慢分析这个数据结构

image-20231214143309986

这个数据机构的第以为是int(4字节)类型或者long(8字节)

那么我们往后4字节(0x6032d4),或者8字节(0x6032d8)看看里面装的是什么,(下面直接说结论了,自己试试看)

image-20231214143810283

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

x/12gx 0x6032d0

image-20231214143853964

上图所示,是一个数字也是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

image-20231214145354952

我们想要的是这样的编排,第一个是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-六个数子,最后结果是不是从大到小的节点序号

image-20231214150215971

综上;array={4,3,2,1,6,5}

阶段四

把node串起来,判断node链表是否依次递减的

总结

以上全是自己做的,没用看网上的思路,自己只查询了汇编用法,gdb用法,感觉最重要的是学会gdb调试命令 b,ni, p, x,有编程基础的话,一步一步调试,看看汇编代码,自己试着写出高级语言代码,就能感觉出他的想法

image-20231214150625038