湖湘杯决赛/TCTF2019/京津冀线下/安洵杯线上/SWPUCTF 部分pwn writeup
前言
现在越来越懒得更博客了,趁着还没到考试周把前段时间做的题记录一下,分别是湖湘杯AWD,京津冀线下,安洵杯的一道heap和SWPUCTF。
湖湘杯
pwn1
程序分析
libc为2.23,保护全开,一共有四个功能,Store,Delete,Post和Exit,输入错误选项会调用_fprintf_chk(stderr, 1LL, "unknown options, foolish %s", byte_202100);
Store是在bss上新建一个node,每个node包括chunk_addr,in_use,size等内容,固定分配0x68大小的chunk,chunk_size为输入的长度。
1 | 00000000 node struc ; (sizeof=0x10, mappedto_8) |
1 | int Store() |
Delete会清空node,释放chunk。
1 | int Delet() |
Post首先让用户选择一个idx的node,之后选择Alice/Bob/Jenny/Danny(实际四个选项最后对应的函数功能一致),根据给的这里的idx2,选择调用对应index的函数指针。函数指针数组的内容如下,这里没有限制idx_2的最小值,因此可以下溢,访问到0x202020之前的数据作为函数指针
1 | int __fastcall Post(__int64 IO_FILE) |
漏洞利用
到这里我们调试可以发现Got表在函数指针数组的上方,因此可以访问任意got表函数,我们再看下调用函数的参数及后面发生的事情。固定调用三个参数为IO_FILE,chunk_addr以及chunk_size。
1 | ((void (__fastcall *)(__int64, __int64, unsigned int))funcs_1375[idx_2])( |
第一个参数是IO_FILE的其实很有限,我当时想的是想办法将IO_FILE的缓冲区关联到一块可控区域,不过尝试了几个函数都不行,最后去看别的pwn了,赛后陆晨学长说是setbuf的洞,搜了一下才发现确实之前有出过类似的题目,linux man一下sebuf,大概
1 | DESCRIPTION |
大概意思就是说缓冲区有三种模式,无缓冲/行缓冲/满缓冲,sebuf等于下面的形式,我们可以做个实验,给个char*的buf,sebuf(stdout,buf);使用printf输出的时候并没输出到终端上,而是进入了buf数组。
到这里这里的利用其实已经有头绪了,我们将stderr(固定参数1,可以通过看汇编找前面rdi的来源确定)同chunk_addr关联起来,再输入一个invalid choice触发_fprintf_chk((__int64)stderr, 1LL, (__int64)"unknown options, foolish %s", (__int64)byte_202100);
当我们输入的name长度为0x50时,实际输入到heap的长度为25+0x50=0x69,从而在heap中可以off-by-one。
这里注意fflush两次才能让数据全部进入到heap,这也是学长试验出来的,后面构造ub的overlap,Delete之后用另一个调用Alice或其他函数泄露libc,最后get shell
1 | unsigned __int64 __fastcall Alice0(__int64 IO_FILE, __int64 chunk_addr, unsigned int chunk_size) |
exp.py
1 | #coding=utf-8 |
pwn2
程序逻辑
一道典型的菜单题,漏洞在于Edit的off-by-null,libc是2.29,断网环境+不熟悉导致开局就先放弃了这个,后面研究了一下2.29的新保护机制发现这个根本用不上那些。
程序有add、delete、edit、show。直接用Off-by-one释放一个大的块作为Ub,泄露地址之后往一个0x240的块fd写free_hook即可。
exp.py
1 | #coding=utf-8 |
pwn3
漏洞利用
pwn3其实也是一道2.29的题,没给libc所以开始先做的这个,后面发现是2.29又换题看了。
功能包括Add Delete Edit,还有一次leak的机会,Add固定长度为0x48,leak的对象是chunk_list[0]。Edit里是循环判断idx,idx可以为负数前溢出,Delete里有double free。
利用思路是UAF分配到chunk0的size部分,改成0x420(large bin),在其后布置另一个fake chunk,释放chunk0即可泄露地址,再UAF即可get shell。
exp.py
1 | #coding=utf-8 |
TCTF2019
babyheap
前言
补充一道前两天做2.29的题目,理解跟2.23和2.27的不同
程序逻辑
依然是菜单题,功能有Add、Update、Delete、View
Add的size范围为0到0x1000,Update存在off-by-null,程序保护全开
漏洞利用
正常有off-by-null我们要构造overlapping chunk,一般来说构造的堆分布如下:
chunk0->0x88
chunk1->0x68
chunk2->0xf8
chunk3(in case to consolidate)
释放chunk1再分配得到chunk1,off-by-null改掉chunk2的prev_size,free(chunk0)和free(chunk2)即可得到一个chunk0~2的大的块,进而overlapped chunk1,这种方式在这里就不太行了,这是因为2.29在unlink前添加了新的检查。
对于我们的chunk2,释放的时候会先检查其prev_inuse位,这里被我们改成了0,因此继续判断,根据prev_size寻找到了unlink的目标chunk0(p),之后的判断chunksize(p) == 0x90,而prev_size == 0x90+0x70,二者不相等,触发error,从而unlink失败。
1 | //glibc-2.29 ./malloc/malloc.c _int_free() |
同样在malloc_consolidate中也有相同的检查
1 | //glibc-2.29 ./malloc/malloc.c static void malloc_consolidate(mstate av) |
为了绕过prev_size==chunk_size的检查,我们要自己构造fake_chunk,同时满足这个check以及unlink的检查,这样的构造在有heap地址的时候比较容易,对于手里没地址的情况比较复杂,这个在Balsn CTF2019有题,Ex师傅也写了篇博客分析glibc2.29下的通用off-by-null的通用利用思路,之后会填坑。
首先分配并释放一个unsorted bin,再分配之后利用残留的libc可以得到libc地址。
释放两个chunk 1 和 2,2的fd为chunk1的地址(tcache_list),分配得到2,show(2)即可泄露heap1地址,进而得到堆地址。
再往后我们还是构造之前的堆分配布局,但不一样的是我们在chunk0中构造fake_chunk,合并的从012变成fake_chunk+1+2。
构造的fake_chunk需要满足以下条件:
- size: == offset between fake_chunk && chunk2
- fd: some_addr_that_store_fake-chunk-addr - 0x18
- bk: fd - 0x8
因为我们手里有了heap地址,所以满足上面的条件很容易,最后overlap之后即可通过update修改fd到free_hook从而get shell
exp.py
1 | #coding=utf-8 |
京津冀
前言
让我很难受的一场,ret2_dl_resolve手里没有脚本加上很久没做,中间卡住了就不断换题,最后一道没出,菜的有点过分了。
pwn1
程序逻辑&漏洞利用
第一眼看没什么,看汇编可以发现其实还是溢出了,有点像DDCTF的题,ebp-4的值作为addr,跳转到addr-4的值继续执行。
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
1 | .text:08048448 mov ecx, [ebp+var_4] |
开始的时候我们有0x14字节的输入,可以布置一次read,但是不足以栈迁移到bss。就是在这里卡住了,开始一直找Gadgets跳过去,换题再回来之后发现不需要这样,直接read的地址改成0x14字节后面的地址,这样可以接力部署gadgets和值,也就是说输入为gadgets+padding+data,gadgets负责栈迁移,后面就是ret2_dl_resolve,这种复杂的东西还是要学会用roputils或者自己写模板脚本。
exp.py
1 | #coding=utf-8 |
pwn2
程序逻辑 & 漏洞利用
pwn2 其实是最近很流行的exit利用的一种,xctf的时候我成功利用过一次,当时比赛的时候由于ld.so没给,libc和ld的偏移也没确定,远程失败,这次大概猜到最后的利用链,但是还是没想明白怎么泄露地址。
题目为libc 2.27,一共有7次写的机会。
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
泄露方法是分配一块极大size的heap,这种利用在之前的Hitcon出现过,最后map的地址用来存放堆,给定一个size,map和libc的偏移是一定的。我们可以分配一块极大的heap,通过两次写改掉_IO_2_1_stdut_的_flags和_IO_write_base泄露libc。
后面需要了解exit的调用过程,在HCTF-the end中可以看到最后调用_dl_fini,一直跟下去,会发现有几次调用_rtld_global+3848
,前面lea rdi设置的参数在_rtld_global+2312
因此用3次写函数指针的后三位为syetem函数后三位(本身就是一个libc相关的地址),再两字节改参数为’sh’,最后即可调用system(“sh”),这里的_rtld_global是ld中的符号,我是直接调试的,题目给了ld,也可以用ld.sym再计算偏移(和heap以及libc的偏移也是固定的)
exp.py
1 | #coding=utf-8 |
pwn3
程序逻辑&漏洞利用
pwn3的环境是2.29,有Malloc和Edit,看到没有free就想到了House-of-Orange,Edit可以指定读取的size,存在堆溢出。
这里不太方便的是全局只有一个buf用来存储chunk,后面新的会覆盖原来的。我们用Edit溢出到top_chunk修改其size,最后释放一个较大的块得到一个tcache,再往后我们没办法通过Edit编辑tcache,因为我们分配的这个较大块已经是另一块map的heap,无法前溢。因此我们要想办法通过不释放一个大于top_chunk_size的情况下让top_chunk掉下来。
这里用scanf的时候输入大量数据,scanf内部会malloc一块区域存储数据,导致top_chunk进入了tcache,进而我们可以Edit编辑tcache。
改fd到bss的stdout的位置,再两次分配到stdout泄露地址。
同样的方式改malloc_hook到one_gadget
exp.py
1 | #coding=utf-8 |
安洵杯
前言
本来是想做mips pwn的,但是我可能因为IDA版本太高用mipsrop搜不到gadgets,搭了个环境就放弃辽qwq,其他四道题还算简单,这里记录一道heap
heap
edit里可以off-by-one,通过pintf泄露出Proc_base和libc,unlink即可
1 | #coding=utf-8 |
SWPUCTF
login
32位print,buf在bss中,需要找两个二级指针,覆写retn_addr和arg位置即可
1 | #coding=utf-8 |
p1Kkheap
程序逻辑 & 漏洞利用
题目开了沙箱禁了execve,double free,libc 2.27,只能free3次,有点像SCTF的one_heap,给了4次free,不过这里有edit要好得多。程序还有Show,所有操作次数要小于等于18次。
首先double free,再Malloc三次让tcache的count为0xff(>7),再释放一个这个大小的chunk即可进ub,泄露libc。
之前double free的时候有一次任意地址分配的机会,我们分配到tcache_perthread_struct,从而可以通过Edit控制得到tcache bins,题目给了块rwxp的区域,我们分配到那里写shecllode再将mallo_hook覆写为mmap_addr,最后trigger到orw读取flag。
1 | #coding=utf-8 |