《CSAPP》是指计算机系统基础课程的经典教材《Computer Systems: A Programmer's Perspective》,由Randal E. Bryant和David R. O'Hallaron编写。该书的主要目标是帮助深入理解计算机系统的工作原理,包括硬件和软件的相互关系,其涵盖了计算机体系结构、汇编语言、操作系统、计算机网络等主题,旨在培养学生系统级编程和分析的能力。
"Bomb实验" 是与CSAPP教材相关的一项编程实验。它是一种反汇编和逆向工程任务,旨在教授如何分析和解决复杂的程序问题。Bomb实验的目标是解开一系列的"炸弹",每个炸弹都有不同的解锁方法,需要分析程序的汇编代码,理解其工作原理,并找到正确的输入来解除炸弹。这个实验教授了计算机系统的底层知识,包括汇编语言和程序执行的原理。
资源获取:关注文末公众号回复 CSAPP Bomb实验
实验准备阶段:首先需要使用ubuntu联网环境跳转到链接下载实验所需的bomblab:Bomblab源文件
下载bomblab压缩包并输入
tar –xvf bomb.tar
进行解压缩,进入该目录所有文件如下所示:
在终端输入
sudo apt-get install gdb
安装调试器。基本用法参考下图:
实验过程阶段:
“Binary bombs”是一个可在Linux系统上运行的C程序,它由6个不同的阶段(phase1~phase6)组成。在每个阶段,程序会要求输入一个特定的字符串。如果输入的字符串符合程序的预期输入,那么这个阶段的炸弹就会被“解除”,否则炸弹就会“爆炸”,并输出“BOOM!!!”的提示信息。实验的目的是尽可能多地解除这些炸弹的阶段。
每个炸弹阶段考察了机器级语言程序的一个不同方面,难度逐级递增:
* 阶段1:字符串比较 * 阶段2:循环 * 阶段3:条件/分支 * 阶段4:递归调用和栈 * 阶段5:指针 * 阶段6:链表/指针/结构
在炸弹拆除任务中,还存在一个隐藏阶段。然而,只有在第四个阶段解决后添加特定的字符串后,该隐藏阶段才会出现。为了完成任务,需要使用gdb调试器和objdump反汇编炸弹的可执行文件,然后单步跟踪每个阶段的机器代码,理解每个汇编语言的行为或作用。这将帮助“推断”出拆除炸弹所需的目标字符串。为了调试,可以在每个阶段的开始代码前和引爆炸弹的函数前设置断点。
在终端输入
objdump -d bomb > bomb.asm
得到bomb的反汇编文件bomb.asm如下所示。
phase_6是一道比较难的反汇编题目,需要使用逆向工程的技巧来解决。
为了便于表示,将phase_6拆分成四部分,分别使用对应的C代码进行描述。
该部分具有两层循环,说明输入的每个数字要求不大于6,且互不相同。
以上部分对应C代码:
r14 = 0;
r13 = 0;
r12d = 0;
while(1){
rbp = r13;
if(num[r13] - 1 > 5)
goto bomb;
r12d++;
if(r12d == 6)
break;
for(ebx = r12d; ebx <= 5; ebx++){
if(num[ebx] == num[rbp])
goto bomb;
}
r13++;
}
该部分的作用为使用立即数7减去每个输入数据,覆盖原来的数据。
图2-38
以上部分对应C代码:
rsi=7;
for(rax = 0; rax != rsi; rax++)
{
num[rax] = 7 - num[rax];
}
在gdb中输入
x/28 0x6032d0
得到:
发现最后8字节数字每次都加了16字节,类似通过指针访问下一结点,并且可以通过前面的node1、node2、node3知道这是一个链表的结点,然后访问6304480,即node1的指针,发现这个指针指向的是下一个结点 node2,类似地如果访问6304496 得到的会是node3和后续结点,由此可以推断出: 前面的 332、168、924是结点数据, 1 2 3是结点编号,最后8字节是next指针。
该链表每个结点的结构为:
struct node{
int value;
int number;
node* next;
}
得到第三部分为:
该循环根据输入数将链表中对应的第输入数个结点的地址复制到 0x20(%rsp) 开始的栈中
因为第四部分要求:链表第一项数据 > 第二项数据 >…,根据gdb调试输入
x /28 0x6032d0
查看地址0x6032d0为:
图2-42
得node[i].value排序为:
node[3]>node[4]>node[5]>node[6]>node[1]>node[2]
因为这个顺序,是经过了numx = 0x7 - numx 则原输入数据应该是:
4 3 2 1 6 5
终端验证:
系统显示通关成功。
系统的注释详细如下:
00000000004010f4 <phase_6>:
//第一部分:
4010f4: 41 56 push %r14
4010f6: 41 55 push %r13
4010f8: 41 54 push %r12
4010fa: 55 push %rbp
4010fb: 53 push %rbx
4010fc: 48 83 ec 50 sub $0x50,%rsp //申请空间
401100: 49 89 e5 mov %rsp,%r13 //%r13=%rsp
401103: 48 89 e6 mov %rsp,%rsi //%rsi=%rsp
//4010f4~401103为保存参数,分配栈帧
401106: e8 51 03 00 00 callq <read_six_numbers> //输入6个数,调用的结果是调用者的栈上按顺序存储输入的6个数
40110b: 49 89 e6 mov %rsp,%r14 //%r14=%rsp
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d //%r12d=0 %r12d当做数组索引,类似i=0
401114: 4c 89 ed mov %r13,%rbp //初始 %rbp=%r13=%rsp
401117: 41 8b 45 00 mov 0x0(%r13),%eax //%eax=num[i]
40111b: 83 e8 01 sub $0x1,%eax
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> //无符号数比较,说明num为无符号数,即大于等于0,40111b~401121 num[i]-1<=5,所以num[i]<=6
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f>
401132: 44 89 e3 mov %r12d,%ebx //401128~401132 退出大循环的条件:6个数字全部遍历到
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp)
40113e: 75 05 jne 401145 <phase_6+0x51>
401140: e8 f5 02 00 00 callq 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41> //401145~40114b 小循环,判断数组元素是否相等
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>// 40114d~401151 大循环,每次将%r13加4,之后回到401114,%r13赋给了%eax
//第二部分
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi //0x18=24,刚好为6个int型数据所占字节,将 %rsi 指向栈中跳过读入数据位置作为结束标记
401158: 4c 89 f0 mov %r14,%rax //%rax=%r14=%rsp (%rax)存放输入数
40115b: b9 07 00 00 00 mov $0x7,%ecx //%ecx=7
401160: 89 ca mov %ecx,%edx //%edx=%ecx=7
401162: 2b 10 sub (%rax),%edx //7-(%rax)=7-(%r14) 立即数7减去 %r14 指向的数据
401164: 89 10 mov %edx,(%rax) //将7减的结果存回 %r14 执行的内存单元
401166: 48 83 c0 04 add $0x4,%rax // %rax 指向下一个输入数
40116a: 48 39 f0 cmp %rsi,%rax // 比较是否达到输入数组的末尾
40116d: 75 f1 jne 401160 <phase_6+0x6c>
//第三部分
40116f: be 00 00 00 00 mov $0x0,%esi //将 %rsi 置0
401174: eb 21 jmp 401197 <phase_6+0xa3> //跳转至401197
401176: 48 8b 52 08 mov 0x8(%rdx), %rdx //将0x8(%rdx)指向内存单元的内容(即下一结点的指针值)复制到%rdx,指向链表下一个元素
40117a: 83 c0 01 add $0x1, %eax //将%eax加1
40117d: 39 c8 cmp %ecx, %eax //比较%ecx和%eax是否相等
40117f: 75 f5 jne 401176 <phase_6+0x82> //不相等,继续遍历链表 【【最终%rdx指向链表的第%ecx个节点】】
401181: eb 05 jmp 401188 <phase_6+0x94>
401183: ba d0 32 60 00 mov $0x6032d0, %edx //重置链表首地址,%edx存放链表首结点地址
401188: 48 89 54 74 20 mov %rdx, 0x20(%rsp,%rsi,2) //(%rsp+32+%rsi*2)=%rdx
40118d: 48 83 c6 04 add $0x4, %rsi //%rsi=%rsi+4
401191: 48 83 fe 18 cmp $0x18, %rsi
401195: 74 14 je 4011ab <phase_6+0xb7> //当%rsi=24时,跳转至4011ab
401197: 8b 0c 34 mov (%rsp,%rsi,1), %ecx //将(%rsp+%rsi)指向的数据复制到%ecx,%ecx存放输入数据
40119a: 83 f9 01 cmp $0x1, %ecx //比较%ecx是否小于等于1
40119d: 7e e4 jle 401183 <phase_6+0x8f> //若%ecx小于等于1,跳转(因为%ecx代表结点,结点标号从1开始,所以输入数据的范围为[1,6])
//即%ecx=1时,%edx存放链表首结点地址
40119f: b8 01 00 00 00 mov $0x1, %eax //若%ecx>1,则%eax=1
4011a4: ba d0 32 60 00 mov $0x6032d0, %edx //%edx存放链表首结点地址
4011a9: eb cb jmp 401176 <phase_6+0x82>
//第四部分
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp), %rbx //将(%rsp+32)的链表节点地址复制到%rbx
4011b0: 48 8d 44 24 28 lea 0x28(%rsp), %rax //将%rax指向栈中下一个链表结点的地址(%rsp+40)
4011b5: 48 8d 74 24 50 lea 0x50(%rsp), %rsi //将%rsi指向保存的链表节点地址的末尾(%rsp+80)
4011ba: 48 89 d9 mov %rbx, %rcx
4011bd: 48 8b 10 mov (%rax), %rdx
4011c0: 48 89 51 08 mov %rdx, 0x8(%rcx) //将栈中指向的后一个节点的地址复制到前一个节点的next指针位置
4011c4: 48 83 c0 08 add $0x8, %rax //移动到下一个节点
4011c8: 48 39 f0 cmp %rsi, %rax //判断6个节点是否遍历完毕
4011cb: 74 05 je 4011d2 <phase_6+0xde>
4011cd: 48 89 d1 mov %rdx, %rcx //继续遍历
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
4011d2: 48 c7 42 08 00 00 00 movq $0x0, 0x8(%rdx) //末尾链表next为NULL则设置为0x0
//该循环按照7减去输入数据的索引重新调整链表
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5, %ebp
4011df: 48 8b 43 08 mov 0x8(%rbx), %rax //将%rax指向%rbx下一个链表结点
4011e3: 8b 00 mov (%rax), %eax
4011e5: 39 03 cmp %eax, (%rbx) //比较链表结点中第一个字段值的大小,如果前一个节点值大于后一个节点值,跳转
4011e7: 7d 05 jge 4011ee <phase_6+0xfa>
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb>
4011ee: 48 8b 5b 08 mov 0x8(%rbx), %rbx //将%rbx向后移动,指向栈中下一个链表节点的地址
4011f2: 83 ed 01 sub $0x1, %ebp
4011f5: 75 e8 jne 4011df <phase_6+0xeb> //判断循环是否结束
//该循环判断栈中重新调整后的链表结点是否按照降序排列
4011f7: 48 83 c4 50 add $0x50, %rsp
4011fb: 5b pop %rbx
4011fc: 5d pop %rbp
4011fd: 41 5c pop %r12
4011ff: 41 5d pop %r13
401201: 41 5e pop %r13 //释放空间
401203: c3 retq
以上代码均存储在bomb_idea.txt文件中,每行代表对应的关卡,各阶段密钥如下所示:
在终端输入
./bomb result.txt
显示全部通关。
计算机系统的世界,如同一座未被揭示奥秘的古老迷宫,引领你勇敢踏入计算机科学的神秘领域。CSAPP的Bomblab实验便是这场独特的学习冒险,从基本概念到底层实现,逐步揭示更深层次的计算机系统内核、汇编语言和数据结构的奥秘。
渴望挑战计算机系统中的安全学习路径和掌握底层系统编程的技术?不妨点击下方链接,一同探讨更多计算机科学的奇迹吧。