2021 DJBCTF Writeup
PWN
big_family
程序存在off by null漏洞,但是对申请的size有限制,申请范围在0x10~0x46之间,所以对于利用方式也有所限制。
申请这个范围,意味着size位在0x21-0x51之间,刚开始做的时候没想到能申请到0x51的size,故一直没想到办法,通过这个博客学习到了一种之前没有实战过(但想到过)的利用思路:http://blog.eonew.cn/archives/1212。通过劫持main_arena上的top来申请到想要的位置,但是在这之前,我们先需要通过合理的布局来构成chunk overlapping。
如何来构成chunk overlapping?
先来让unsorted bin中有堆块
利用off by one的前提一般都是需要有unsorted bin中的堆块,但是由于这道题申请size的限制,所以我们申请的堆块都会在fastbin中,这时候就需要一些其他操作来让其进入到unsorted bin中。
我们需要利用scanf来让fastbins中的chunk进入到unsorted bin中。也就是利用scanf接收时会调用malloc进行申请堆块,而当我们输入的长度大于0x400(largebin的范围),在申请的时候就会先去执行malloc_consolidate,在这个函数内会调用clear_fastchunks来清空fastbin,并且根据size从小到大依次让fastbin chunk进入到unsorted bin中,并且判定是否可以合并,如果有相邻的fastbin则发生合并。而在之后遍历unsorted bin的时候,又会先从unsorted bin中脱链根据大小来进入到small bin或者largebin,如果大小不合适的话,就会存在于这两者其中的一个。
所以在通常情况下,执行scanf的表象就是fastbin中的元素都进入到smallbin中去了。
注意:在触发malloc_consolidate后,进入unsorted bin的fastbin的下一个堆块的prev_inuse = 0,并且被写入了prev_size信息。
如何利用?
由于这次加入了scanf来作为构造的限制,所以一般的off by one的方法都不再适用了。这次我们需要的就是我之前就在博客中所提及的堆收缩(poison_null_byte)的方法。之前用的情况是在无法控制prev_size的时候,而这次是在于如果某个堆块的size在0x101,那么这个堆块一定是在free状况下的,因为我们最多只能申请0x51的size,这样的话我们就无法控制再次free这个堆块来触发unlink。
所以利用poison_null_byte的方法从而来让堆块收缩,这样的话,由于写prev_inuse和prev_size都是根据当前堆块的size来计算下个堆块的位置来写的,在收缩之后,由于计算得出的下个堆块的位置错误,当我们再次申请那部分(被收缩)的堆块的时候,prev_inuse和prev_size无法写入正确的位置。
接下来我们只要让下面位置的堆块(通过malloc_consolidate从而标记过prev_inuse = 0的堆块)进入到unsorted bin,由于prev_inuse = 0,系统根据prev_size去找前面的堆块去合并,从而触发unlink。
leak libc
有了chunk overlapping之后,稍微构造一下,就能通过unsorted bin在未free的堆块上写一个main_arena + 88,然后用自带的show函数就可以leak了。
劫持main_arena
又有了libc之后,我们可以考虑修改0x51size的chunk的fd到main_arena上的fastbinsY那块区域,我们可以构造,让fastbinY上有内容,且当这个内容的开头为0x56的时候(利用开启PIE后的随机性,有1/3的概率,至于为什么是这个数,可以看一下我house of storm里面写的分析),那么我们就可以成功劫持到这块区域,其实这里可以利用的指定size的fastbinsY的限制挺多的。
比如说这道题(结合调试来看):
max限制
1.我们选择的size至少要小于0x51,因为我们最多只能申请0x51,如果劫持0x51的话,在第二次申请的时候,fastbinsY中0x51size的内容开头就会变成0x7F,不符要求
2.其次我们必须要小于0x41,因为0x41的内容和0x51是相邻的,而我们又需要错位来绕过size判定,而他又是与0x51相邻的话,那么size那部分会有0x51size的内容。
所以综合以上两点,我们能够选择的只有0x21和0x31的size用于劫持,这里选择的是0x21的size,他的位置在main_arena + 0xD。
min限制
3.长度的限制,如果选择使用劫持较小的size,比如选用0x21的size就要考虑到这个问题,也就是能否修改到main_arnea -> top的内容,有较大的可能性(如果申请size的时候要求小于0x3B + 8 = 0x43就修改不到了)
由于这些东西都是在调试中发现的问题,再去改前面申请的size(毕竟在做题的时候不会想这么全面),所以刚开始的时候没有发现这个问题,浪费了很长时间,而且在看ex师傅在那道题目的exp的时候也看不懂为什么size要变来变去的,直到自己亲手调试了才会明白,建议各位师傅可以亲手试试,下次做题的时候就会流畅很多了。
修改main_arena -> top
劫持之后我们就可以尝试修改main_arena -> top的内容了。
通过调试找到main_arena的位置,如果你劫持的是main_arnea + 0xD的0x18的size的fastbinY[0],那么就要相隔0x3B个数据再写劫持的位置。
确定劫持top的位置
由于在malloc的时候会检测top chunk的size位是否足够,如果不足够则会重新申请一块区域,所以我们一定要确保选择劫持的top位置,有足够大的size。
比如,如果我们要劫持__free_hook的话,那么我们可以考虑__free_hook - 0xb58位置,但实际上这个位置距离__free_hook太远了,在这道题显然不适用。
所以这道题劫持的是__malloc_hook,并且选择__malloc_hook - 0x28的这个位置,这个位置的size信息也恰好足够大,符合申请调用。
接下来只需要几次申请(先把fastbinsY和unsorted bin中的内容都申请完),就可以从top chunk中进行申请,然后我们就可以申请到malloc_hook的位置。
不过这道题,直接上one_gadget无法打通,需要用一个小技巧,也就是用realloc_hook来调栈,大概也可以用触发double free的方法吧(未测试)。
最后稍微吐槽一下,这个libc版本好像不是很大众的吧,居然不给libc。
EXP
|
|
easy_note
libc2.27,没开pie保护且是Partial RELRO
题目信息
这道题和一般的堆题不太一样,他是用mmap申请了一个大堆,然后之后写堆块内容都是从他的那块上来,不过也不彻底,还是有一个malloc用于储存临时的结构数据。
他的这个堆块的结构大概是这样的:
offset | name |
---|---|
0x0 | size |
0x8 | canary(4 bytes) |
0x10 | content_ptr |
程序开了另外一个线程,用于检测他自己随机的canary是否被更改。
利用方法
并且程序存在可以自己输入size的功能,这就造成了溢出,但是由于他这个canary的保护,直接修改content_ptr是不可行的。
接下来我们就要把注意力转移到如何泄露出canary的方向上,在show函数中,程序根据这里的size去输出content_ptr的内容,而我们通过堆溢出又正好可以修改这个size的信息,那么我们就可以把size改大,然后泄露canary的内容,在下次溢出的时候保证canary不变的同时来修改content_ptr来实现任意读写。
由于程序没有开FULL RELRO,所以我们这里可以考虑把content_ptr修改成got表上的free函数,从而show一次就可以leak出libc了。
刚开始的时候考虑修改**__malloc_hook为one_gadget来传参,结果意料之外的是居然没有一个one_gadget可以成功getshell,甚至在利用realloc来调栈的方法都没有一个可行,所以只能考虑更为考虑的system**函数。
然后我们通过修改free函数为system,这样在下次调用free的时候,由于在堆块头部的信息就是申请的size大小,这是我们可控的内容,利用类似ret2text的思想,传入sh的16进制内容,成功getshell。
这里还有一种方法,就是利用scanf函数在申请大于0x400的chunk后,使用完毕会free掉,如果我们让scanf函数的内容中存在sh(类似 ;sh; )。那么也可以达到getshell的目的。但是要注意,scanf调用的free函数,不会走got表,所以要修改__free_hook才能有用。
EXP
|
|
easyrop
SROP模版题
(保护全红,栈溢出0x400)
刚开始的时候路走歪了,没想到SROP,其实看到这么大栈溢出,应该自然的就知道是SROP了,毕竟良心出题人还是少的,非必须也不会给这么大(相对应的看到很小的栈溢出就想到栈迁移)。
以为是要考察在打远程的情况下可以利用fd = 0或1都能利用sys_write来输出内容,来泄露栈地址来操作,可惜的是没给libc,这种方法虽然可行,但是概率大概是1/100(大概),也就没有继续尝试下去了,有兴趣的朋友可以看看。
|
|
之前接触的SROP都是堆题利用setcontext来ROP,也算是少数的接触syscall的SROP吧(虽然这才是正规的SROP),之前博客也有一道SROP的题目,我写了个非预期。
exp没什么好说的,由于有RWX段,我们可以直接写shell,然后跳到那里去执行,不过记得给栈留点地方,不然shellcode用栈的时候会覆盖到一部分内容。
EXP
|
|
virtual
观察主程序,发现有一个循环接收内容:
逻辑大概是这样的,读入一串code(长度最多为0x100),然后通过handle函数来解析读取的数据内容,这种读入一共只有两次。
所以不难想到,如果要控制程序命中率100%的话,我们第一次的code必须要leak libc,而第二次就是尝试改相应的其他位置。
执行函数分析
handle函数非常复杂,代码逻辑我根据题目名字猜测是个类似虚拟机的东西,由于伪代码逻辑混乱,故尝试自己把代码进行调整
首先关注到两个亮眼的位置,malloc和free
从这里猜测data + 6存放的是指针的信息,而data + 5也是指针信息,data + 4是堆块的结束位置,在free的时候三者都被清空。
在5这个位置,发现可以输出和输入信息,其中的read功能可以用来leak,这也是唯一的leak点。
在4这个为位置,通过第二个传入的内容,会有不同大小的修改内容
在3这个位置,会有不同程度的增加内容
没啥用
在1这个位置,有不同程度的对data + 5指针进行修改,且在修改之前有个检测
在0这个位置,有不同程序的对data + 5的数据进行修改,且修改的内容受到了调控
漏洞点
代码中存在的两个检测如下图
check65写错了,按照我的理解,应该是如果data + 6 > data + 5那么就退出,这就导致了接下来漏洞的发生,程序可以通过减少data + 5来向前溢出。
如何利用?
首先可以观察到一个特征就是,在这个switch中,返回值就代表着这个操作所需的参数长度,且以字节为单位,这可以方便我们编写程序和了解程序流程。
第一次构造
我们先考虑第一次的code要如何构造,第一次构造我们需要泄露libc,而我们又有show函数,所以我们要考虑如何绕过tcache,让一个堆块进入到unsorted bin中,如果要绕过tcache的话,那么意味着我们要让tcache填满,但是对于这道题来说,我们一次性最多只能申请一个堆块,如果要申请下一个必须先把这个堆块释放掉。
所以我们只能考虑如何来一次修改tcache struct中的counts数组对应idx内容大于7。
首先我们可以通过向前溢出一部分距离(受限于最多执行0x100的code),但是溢出的距离不足以来修改到tcache struct的内容,但是我想到了一个巧妙的方法。
因为data结构实际上也是储存在堆中的,在我们向前溢出的过程中,也可以溢出到那部分的内容,但是我们只能修改一次(因为我们要修改的是data + 5,而向前溢出的指针也是这个),所以我考虑修改data + 5的倒数第二个字节,让他减少1,这样就相当于减少了0x100(32个操作,64个字节),在这样的方法下,我们足以碰到counts数组,接下来只需要修改它为0xFF,那么我们在接下来free的时候就不会进入到tcahce而是进入到unsorted bin,然后我们再次申请一块在tcache中不存在的堆块,那么就会去unsorted bin中申请,这个申请得到的数据上存在unsorted bin残留的main_arena指针,我们通过再次申请然后show一下就可以拿到libc上的地址了。
在这之后,我发现距离0x100还有一段空间,于是我决定不浪费这些空间,在第一段payload中干一些第二段的事情。
第二次构造
由于有在第一次构造的一些帮忙,在第二次构造的时候,实际上我们使用的空间是大于0x100的。所以我们可以考虑直接往前溢出,直接溢出到tcache struct,然后修改某个的内容为**__free_hook的地址,再次申请就可以拿出来__free_hook**的地址了,拿到之后我们把他改成system,再free一次,就可以getshell了。
但是好像还发现一个问题,我们没有在堆块写入system执行内容,解决这个问题非常容易,只需要在__free_hook - 0x8的地方开始申请地址,那么就它之前0x8字节写入shell的内容即可,后0x8个字节来修改**__free_hook**。
总结
这道题的难点主要在于要理清程序的结构,合理的调试和清晰的分析。最终才能够构造出巧妙的exp来getshell。个人感觉这道题出的很不错,学到了!。
PS:这道题虽然是glibc2.27,但其实是新版本的2.27,对于tcache来说增加了key用于检测double free。建议调试过程中使用2.29来调试,与这个版本几乎没差别。
EXP
|
|
RE
A-Maze-In
有个迷宫,但是我也没搞懂怎么样的迷宫,根据程序逻辑,写了个DFS就秒掉了(带了个记忆化搜索)。
|
|
Matara Okina
又到了我最喜欢的APK环节了(毕竟这个调试环节弄了这么久)。
可惜这道题没用到,拖到JEB看了一下,发现了下面这个函数
看到有个加密环节,虽然不知道怎么调用到这个函数,但是先把这个加密给处理了吧。就是一个简单的对称异或加密:
|
|
得到内容:Android_scheme_is_FUN 搜索scheme,发现这是一种安卓中有的一种交互协议,用于从浏览器中跳转到这个应用,配置信息在Manifest中
找到关键信息:
|
|
构造连接:sh0w://p4th/70/1nput?secret=Android_scheme_is_FUN 在模拟器的浏览器中访问,就会跳转到应用中执行,并且给出了flag格式
这里input就真是整个链接…所以逆向so也没啥用。
**flag{**sh0w://p4th/70/1nput?secret=Android_scheme_is_FUN_1635b71e036d}
anniu
下载之后有一个灰色的按钮,用一些控件助手,把按钮解禁即可得到flag。
warmup
第一次做这种数独的题目,观察程序逻辑,发现是16*16的数独。
谷歌找到一个解数独的网站:https://sudokuspoiler.azurewebsites.net/Sudoku/Sudoku16
发现网站是要一个一个输入,有点慢,利用fd抓包之后,发现网站上传了一个数独的数据。
编写程序输出内容(0xFF相当于为空,也就是要填的,再把输出内容"256"替换成"“即可):
|
|
fd改包之后,重新发送,就得到结果了。
得到的数据和题目要求的格式不太一样(0-9)(a-f),手动转换实在是太慢了,编写程序自动转换:
|
|
flag{765c98e78644507b8dfb1552693e467871026d26ba03c175}
e
这道题因为ida调试不起来一直没做,没想到这么简单。
用gdb可以调试,用gdbserver来与ida连接(重度ida依赖,做pwn的时候就是重度gdb依赖)
先下个断点
启动调试,跟进去
一直单步到jmp eax,跳转到另一个区域
进入到第二个call
这个函数的代码相当复杂,但是我们需要注意的就是什么时候输出NONONO
观察到:
如果进入下面的分支就会输出NONONO,所以猜测上面的分支就是会输出正确的flag。
所以如何让v6 == true呢?
发现v6就在我下的断点那一行赋值了(红色那行),点进去那个调用的函数
盲测是strcmp。
由于是gdbserver调试,所以不能直接在伪代码看内容,所以我们转回汇编
发现流程图也是非常清晰的分支,在x86就是栈传参,所以eax就是比较的内容之一,查看数据
尝试输入DDDJJJBBBRRREEE
成功!
flag{DDDJJJBBBRRREEE}
UnrealFlag
这道题虽然拿了一血,但其实还可以更快,因为找工具用了大部分的时间。
主要思路是参照:https://bbs.pediy.com/thread-255724.htm。这篇文章的
用IDA载入关键文件FindFlag-Win64-Shipping.exe,查找字符串关键词index offset(时间有点久)
双击进去之后按X查找引用
发现有两个引用,我们都过去看看,并且都下断点
这个if有点长,找到他上面的下断点,其实大概猜到这里已经不是了。
设置调试,并且跑起来
程序成功跑起来了,接下来就是一些盲目的寻找
发现图中黄色的函数里面有点玄机
因为他这个函数内部似乎有打印key的一个异常输出
我们可以走到他附近看看,由于这块的伪代码效果不是很好,我们直接去汇编看,每一个调用函数都进去看看
会走到这样的一个函数
发现这里有个memcpy,而且copy的size也是0x20,虽然这部分没有走到,不过我大胆地猜测这里应该就是用于前面异常报错的部分,所以查看对应的a1内容,先按*****设置为数组
并且Shift + E导出
在之前的教程里面用的工具在这道题里面似乎不行,所以我一直没搞清楚要用什么来解包,然后查看umodel的官网,本来想找找怎么输入AES的格式是怎么样的,也没有找到,最后出题人告诉我AES输入的格式是前导0x的16进制字符串(其他软件都是base64)
还有就是记得要用最新版的
https://github.com/ProgramingClassroom/UModel
选择PAK包之后,输入AES秘钥
就可以成功解包了
找到flag.uasset,并双击打开,这是一个材质文件
但是似乎有些变形了,有些字符看不清楚,不过多试几次就能试出来。
WEB
虎山行
非预期,似乎直接RCE了,看到文件的时候我还一脸懵逼。
刚开始对这个系统进行了搜索,发现了先知社区的一篇文章,不过有些繁琐,而且也不能直接RCE,故手动寻找了这个系统的文件。
发现系统存在文件install.php来让我们可以对文件进行手动的安装,所以就尝试对这个文件进行分析,之前也了解过dz论坛的一些漏洞,发现在安装文件中出现问题的可能性还是蛮大的,经过分析发现下处漏洞:
发现在安装文件的此处,没有对输入的信息进行任何的过滤,这导致我们可以直接通过输入的内容来往该文件中写入shell,例如我们可以构造payload,使得数据内容闭合,并且把后续的内容都注释掉。
|
|
看到挺多莫名奇妙的文件的,估计是因为我是非预期的缘故。
MISC
十八般兵器
下载之后得到一个压缩包文件,发现压缩包存在密码,从旁边的注释信息得到密码进行尝试
解压后得到文件:
猜测是对文件的隐写,且因为其实jpg文件,尝试用JPHS来得到隐藏内容
对每个兵器都进行读取:
可以得到多个文件,把下面的数字都连接起来,前十种兵器对应10进制,后八种对应8进制,
最后转字符串信息就可以得到
flag{CTFshow_10_bA_Ban_b1ng_Q1}
请问大吉杯的签到是在这里签吗
下载得到一个二维码文件,尝试扫码,发现没有重要信息。
于是尝试观察文件,发现在末尾处有一个PK压缩包,尝试解压,有多个套娃,得到全部的二维码之后,对其内容进行分析,发现在第二个二维码处有提示,于是猜测第二个二维码存在隐写。
通过Stegsolve打开进行查看
发现在0通道和1通道切换的时候,图片内容会变化,猜测有隐写内容,但是查看无果。
再继续查看的时候,发现这些内容都被显示出来
猪圈密码解密,得到
flag{dajiadoaidjb}
牛年大吉
丢进binwalk跑了一下,发现有一张图片和一个压缩包信息。
使用
|
|
进行分离,得到压缩包和图片: 修改其扩展名使得能被windows识别:
解压压缩包需要密码,密码在图片头里面,说实话这真的不好猜
解压后得到
flag{CTFshow_The_Year_of_the_Ox}
碑寺六十四卦
发现文件很大,相对于size来说不匹配,猜测有隐写。
根据提示把图片反色,并查看
发现存在一个png文件,导出后查看
把开头的无用内容删除后,得到:
发现文件
猜测从上到下依次对应着base64编码的索引,之前UNCTF2020也考过一道类似的。
手动对应了一下,发现开头就是flag,加强了信心。
最后得到
flag{Le1bnizD0uShuoH4o}
AA86
刚开始无思路,在仔细看题目之后,决定去装个DOS看看,发现内容可以在DOS中执行,最后得到flag
flag{https://utf-8.jp/public/sas/index.html}