CSAPP实验:拆解二进制炸弹

序言

关于开放知识共享的声明:
  开源和分享的目的是为了相互学习和借鉴,而不是为了抄袭。我们鼓励使用开源项目来了解优秀的编程实践、算法和解决方案。然而,我们严格禁止抄袭他人的代码或将其用于商业目的。请尊重他人的劳动成果,展示你自己的创造力和独立思考能力
  学校作业的提交通常会受到查重要求。学校的数据库中往往包含了往届学生提交的内容。因此,严禁随意使用开源项目来完成作业。这种行为将被视为抄袭,可能导致严重的学术后果。我们鼓励学生们通过独立思考和努力来完成作业,从而加深对所学知识的理解和应用能力

本文在完成提交所有作业并结课得到评分后发出,参考本人实验报告原文并进行适当修改

请注意:CSAPP实验存在多种相似问题的不同组合。由此,每个人所得到的具体题目都将会不同。本人仅在此代表其中一种题目组合的解法及其他题目的相关思路。

X86-64架构

一.准备阶段

1.使用tar -xvf ./bomb163.tar解压缩文件,得到READMEbomb可执行文件与包含部分C语言源代码的文件
2.使用objdump -S -d bomb > bomb.c.txt反汇编bomb可执行文件,得到bomb.c.txt汇编代码,使用vim工具查看
3.使用gdb ./bomb在GDB调试工具下运行炸弹程序
4.通过break explode_bomb在函数<explode_bomb>位置处设下断点,阻止错误时发生爆炸
5.在GDB初始模式下,run可以运行程序
6.在GDB调试下,使用disassemble可以反汇编当前函数并查看执行到了汇编的第几步骤

炸弹1

查看<phase_1>对应汇编代码,发现将0x402690放入%esi寄存器中,进入<string_not_equal>,若返回值为0(false)则不执行<explode_bomb>发生爆炸。即应当满足两字符串相等
IMAGE-BOMB-1-01
字符串记录方式为数组地址。通过gdb下在<explode_bomb>设置断点的方式,避免发生爆炸。在GDB模式下,通过x指令,查看对应内存地址0x402690所存的数据为字符串“Only you can give me that feeling.”。因此答案即为该输入
IMAGE-BOMB-1-02

炸弹2

%rsp寄存器对应内存地址在<read_six_numbers>中读入,且单个读入数据为4大小。
%rsp寄存器对应地址所存数据不为0,则跳转到<phase_2+0x2b>行执行,即callq <explode_bomb>发生爆炸
%rsp寄存器对应地址+4 所存数据为1,则跳转到<phase_2+0x30>行执行,否则发生爆炸
%rbp寄存器存入%rsp寄存器+0x10作为进行循环的判断条件,即循环4次
每次循环中,%eax寄存器累加%rbx寄存器对应地址与%rbx寄存器对应地址+4处数据,并与%rbx寄存器对应地址+8处数据进行比较。若不相等则发生爆炸。即对于该数组,从第三位开始,每位数据应等于前两位相加。
IMAGE-BOMB-2-01
因此,输入为0、1开头的共6个累加得到的数,即0 1 1 2 3 5

炸弹3

在调用scanf之前,查看0x4029ad地址所存内容为%d %d,因此为读入两个整型
IMAGE-BOMB-3-01
%eax寄存器的值大于1时会调用<explode_bomb>函数,否则会继续执行后续指令< phase_3+0x30> = <400fe5>:cmpl $0x7, (%rsp)
%rsp寄存器对应地址数据与7相比较,若大于7则跳转到<phase_3+0x71> = <401026>发生爆炸。
mov (%rsp),%eax; jmpq *0x4026e0(,%rax,8);因此根据输入的第一个数值,将%rsp寄存器对应地址数据移植%rax,并跳转到(8 * rax + 0x4026e0)处所存的地址
若输入1,则跳转到0x4026e8地址所储存的地址
IMAGE-BOMB-3-02
通过x/x 0x4026e8查看,该地址为0x400ff5
该行执行:400ff5: mov $0x2d8, %eax,随后jmp 401037 <phase_3+0x82>开始比较
因此输入为0x2d8 = 728
IMAGE-BOMB-3-03
因此,最终输入可以为1 728.
该答案并不唯一。因为输入的首个数可以是0到7的任意整数,对应跳转到(8 * rax + 0x4026e0)处所存的不同的地址

炸弹4

同理在调用scanf函数之前,查看0x4029ad地址所存内容为%d %d,因此为读入两个整型
%eax0x2相比较,若%eax等于数值2则跳转至4010cd调用爆炸函数
mov (%rsp), %eax,将读入第一个数据放入%eax中。并执行%eax-2,之后再次进行比较。若%eax等于数值2,则跳转至4010de,否则发生爆炸。之后,将输入值移入%esi$0x8移入%edi,调用fun4函数。由此可以看出,第二个输入应当为4
IMAGE-BOMB-4-01
phase_4当中,执行完func4后,比较func4函数返回值%eax(%rsp+0x4)所存输入的第二个数进行比较,若相等跳转4010ea避免发生爆炸
在运行完后,通过查看%eax数值,确定为216
IMAGE-BOMB-4-02
故输入答案为216 4

炸弹5

在执行输入前,可以注意到,在地址$0x4029ad中所存字符串为“%d %d”,推测为输入两个整型
IMAGE-BOMB-5-01
调用scanf函数后,通过比较指令检查,如果%eax的值大于1,那么程序将跳转到phase_5中的地址0x401134,否则继续执行下一条指令(发生爆炸)
将栈顶的值(%rsp指向的地址)(读入的第一个整型)加载到%eax寄存器中,将%eax寄存器中的值与15(二进制四位为1)进行按位与操作,只保留最低的4位,将结果存储回栈顶。将%eax与 15 进行比较,如果相等,跳转到401171(发生爆炸)
IMAGE-BOMB-5-02
将0存储到%ecx寄存器,将0存储到%edx寄存器,<40114c>%edx加1
从一个数组中读取一个值,数组的起始地址为0x402720,索引是%rax的4倍。结果存储在%eax%ecx = %eax+%ecx.将%eax与15进行比较。如果不相等,跳转到40114c
将 15 存储到栈顶,将%edx与15进行比较,如果不相等,跳转到401171(发生爆炸)
将栈上偏移4位置的值(读入的第二个整型)与%ecx比较,如果相等,跳转到401176,否则继续执行(发生爆炸)。
由上分析,最终%edx应该为15。故循环应该执行15次%edx加1

通过对于0x402720内存地址的分析,可以得知,以该内存为基址,存放了0-15共16个整型。因此,%eax分别通过索引访问,并将指向的整型作为下一轮的索引。
IMAGE-BOMB-5-03
%eax最终为15,索引为24(= 4 * 6)
%ecx为逐次累加的%eax数值,等于第二个输入的整型115
%eax最初的数值为第一个整型与15进行按位与操作,且操作完不为15 (%eax != 15)
%eax按位与后为5,故输入为5

通过如上问题分析可得,正确答案应为5 115

炸弹6

%eax寄存器的值与立即数0x5进行比较,如果比较结果满足小于等于条件,则跳转到地址 4011d4 处(避免爆炸)。%eax - 1 <= 5,故输入的数据应当小于等于6
已知 %r14d寄存器为0x0 + 0x1 != 0x6,因此不执行跳转4011ff
%eax 寄存器为%rsp + 4 * %rax地址处的数据,且 %rax数值为1。%eax, 0x0(%rbp)应当不相等,跳转到4011f1处(避免爆炸)。(此时%rbp = %rsp
此时 %ebx寄存器数值为0x1 + 0x1 <= 0x5。故跳转回4011e1地址。不执行跳转需要执行共计6次使不小于等于5。此时在跳转中仍有比较 %eax, 0x0(%rbp)应当不相等。
在跳出循环之后,寄存器%r13加上4,无条件跳转4011c0地址。
IMAGE-BOMB-6-01
初次执行时,寄存器 %r13为寄存器 %rsp内地址
在外部循环,需要比较$0x6, %r14d相等时4011ff地址,其在各次执行中数值+1,故需要执行共计6次
由此看出,通过双循环,外部保证输入的六个数要大于等于1,且小于等于6,内部保证互异。因此通过<read_six_numbers>读入的六个数字为特定顺序的1 2 3 4 5 6
接下来通过循环,将读入的6个数,分别用0x7立即数减去这六个数,并放回原地址当中
通过进行两层循环,构造结构体,并在其中将其存放位置按照输入的数的大小计算得出
IMAGE-BOMB-6-02
IMAGE-BOMB-6-03
扫描内存地址0x6042f0所得到的node结构体内容
比较%eax,(%rbx),大于或等于跳转到401293避免爆炸。%ebp初始为5,减至0时不再跳转401284即结束循环。
故可得,通关条件是要求前面node数据大于后面的节点。即989 > 970 > … > 289
根据node结构体内的数据,通过7减去输入数据处理后结果为5 3 1 4 2 6
IMAGE-BOMB-6-04
因此原来的数据为输入2 4 6 3 5 1

隐藏炸弹

完成“所有”炸弹后,我们注意到了还有一个<secret_phase>函数没有使用。查找定位只有在<phase_defused>函数中有对<secret_phase>函数的调用

在该函数中,对于<strings_not_equal>函数的返回值 %eax,若不相等则会跳过调用secret_phase。因此查看输入了什么字符串。
对于scanf函数,返回值应为3。因此需要读入三个元素。已知调用该函数时读入了两整型。
0x402a00地址处,打印得知:该需要读入的字符串为DrEvil
IMAGE-BOMB-7-01
炸弹4中调用了scanf函数。读入方式为“%d %d %s”。因此在其输入后加上字符串“DrEvil”。由此,在炸弹6之后进入了隐藏关卡
进入后,查看源代码为首先使用了<read_line><strtol@plt>函数,将返回值%rax–0x1与立即数$0x3e8比较,小于等于可以跳转避免爆炸。因此输入的数应为小于等于1001
%edi置为$0x604110,调用函数<fun7>。调用该函数之后,要使返回值 %eax$0x7比较,相等可以跳转避免爆炸。因此探究如何使得函数<fun7>返回值等于7。
查看函数<fun7>,可以看出是一个递归调用。其中初始(%rdi = 0x604110)= 36%esi= 3
开头,若%rdi等于0,则直接跳转返回0xffffffff
函数递归出口为地址 4012f7,返回 %eax
存在三条路径,分别为:
1)add %eax, %eax 2)mov $0x0,%eax 3)lea 0x1(%rax,%rax,1), %eax
其分别为:%eax *= 2; %eax = 0; %eax = 2*%rax + 1;
要使%eax返回值为7,因此做法为:(2).0->(3).1->(3).3->(3).7
其中%rdi有2种操作,分别为mov 0x8(%rdi),%rdi; mov 0x10(%rdi),%rdi
其分别为:%rdi += 8; %rdi += 16;
对于rdi附近内存地址的考察有:
IMAGE-BOMB-7-02
IMAGE-BOMB-7-03
因此考察需要输入的数据:1001

ARM架构

查看<phase_1>源代码,可以看出传入了x1寄存器数据为基地址0x402000以及加值0x608。故使用x查看当前内存数据,将该字符串传入后与输入数据进行比较。若<strings_not_equal>返回为0则字符串匹配,不爆炸结束阶段一
ARM01

查看<phase_2>源代码,可以看出其通过<read_six_numbers>读入了6个数。
对于入栈区的元素,首个元素应为0,次位元素应为1,之后执行循环共计6次。
其中下一个元素等于前两个元素之和,因此输入应为0 1 1 2 3 5可以满足要求。
ARM02

对于<phase_3>源代码,首先赋值x1寄存器基地址0x402000,然后增加数值0x368
scanf返回值为读入数据个数,应不小于等于1.
此处读入两个数。将首个数[x29, #28]加载入x0寄存器,可以看出,若读入首个数为1时,首先比较1与3,跳转到0x401134,然后等于1,跳转到0x401150。此处加载的第二个数据为0x4a = 74。因此输入1 74可以满足要求。
ARM03

在函数<phase_4>当中,首先读入2个数据,且读入首个数据应当小于0xe = 14。之后调用<func4>函数进行处理
ARM0401
首先查看其返回值。返回值应当与读入的第二个数值相等
ARM0402
通过输入不同的数据后,使用gdb查看寄存器w0数据,可以得到8 35满足要求
ARM0403

可以看出,读入了一串字符串,且字符串长度为6。查看最后进行比较的部分,其中x1寄存器为基地址0x402000 + 0x640
ARM0501
0x402640所存字符串为“sabres”。可以看出,其中对读入的字符串进行了处理,使用x2寄存器0x4025f8为首地址的字符数组进行构建。其中数组偏移量为读入字符串ASCII的二进制末4位,即十六进制末位0 - F.
偏移量分别为7 1 13 6 5 7。故ASCII十六进制末位为7 1 D 6 5 7
ARM0502
可以存在的一种情况为GAMFEG

相似的思路,首先查看最后进行比较的地址数据
得到寄存器w4基地址为0x420000,加上了0x110的偏移量。故查看0x420110处的地址信息
ARM0601
成功读取到了结构体node1的信息
ARM0602
继续向后查找可以得到其他结构体的信息
ARM0603
将结构体内360 – 211进行比较,可以得到输入的六个数应为4 3 2 1 6 5