🌺1. CSAPP与Bomb简介
🍀1.1 CSAPP
《CSAPP》是指计算机系统基础课程的经典教材《Computer Systems: A Programmer's Perspective》,由Randal E. Bryant和David R. O'Hallaron编写。该书的主要目标是帮助深入理解计算机系统的工作原理,包括硬件和软件的相互关系,其涵盖了计算机体系结构、汇编语言、操作系统、计算机网络等主题,旨在培养学生系统级编程和分析的能力。
🍀1.2 Bomb
"Bomb实验" 是与CSAPP教材相关的一项编程实验。它是一种反汇编和逆向工程任务,旨在教授如何分析和解决复杂的程序问题。Bomb实验的目标是解开一系列的"炸弹",每个炸弹都有不同的解锁方法,需要分析程序的汇编代码,理解其工作原理,并找到正确的输入来解除炸弹。这个实验教授了计算机系统的底层知识,包括汇编语言和程序执行的原理。
资源获取:关注文末公众号回复 CSAPP Bomb实验
🌺2. bomb
🍀2.1 实验环境
- VMware Workstation虚拟机环境下的Ubuntu 64位。
🍀2.2 实验过程
实验准备阶段:首先需要使用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如下所示。
🍀2.3 phase_6
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
🍀2.4 实验结果
以上代码均存储在bomb_idea.txt文件中,每行代表对应的关卡,各阶段密钥如下所示:
在终端输入
./bomb result.txt
显示全部通关。
🍀2.5 实验体会
- 深度解析Phase_6: 在CSAPP的BombLab实验中,深入研究了Phase_6,透过逆向分析和程序攻击手段,揭示了解密这一阶段的复杂性。通过理解程序逻辑和数据结构,成功解锁了Phase_6的奥秘。
- 实战经验分享: 通过实际操作,积累了丰富的实战经验。从调试器的使用到汇编代码的分析,逐步攻克了Phase_6中的各个难关。这一过程不仅提升了对计算机系统底层原理的理解,也增强了程序逆向工程的技能。
- 学术收获与挑战感: BombLab实验是一场学术冒险,解密Phase_6的过程既充满挑战感,又带来了深刻的学术收获。通过对程序的分析和攻击,更深刻地理解了计算机系统的运行机制,为进一步的研究和学习打下了坚实基础。
📝 总结
计算机系统的世界,如同一座未被揭示奥秘的古老迷宫,引领你勇敢踏入计算机科学的神秘领域。CSAPP的Bomblab实验便是这场独特的学习冒险,从基本概念到底层实现,逐步揭示更深层次的计算机系统内核、汇编语言和数据结构的奥秘。