2020 XNUCA CTF,题量较大 也很难。后面如果有参考,会接着复现其他题目
koh_defile
程序分析
这道题在线下赛是一道 koh题目,得分标准为成功获得flag后,比较所用shellcode的长度。
程序总体流程是 使用 mmap创建了一个 0x4096的堆空间。然后读取用户输入的 shellcode到堆的 前 0x2047空间内。然后将该shellcode拷贝到堆的 后 0x2047堆空间内。然后创建了一个子进程,子进程会循环轮流跳到 堆空间的前后 两段 shellcode空间内执行。
1 | while (1) |
而另一个进程会先读取随机数到 key中,然后 不断比较 shared+4095处的数据与 key[i]的值。如果比较不正确,则会一直循环等待。如果比较正确,则会将shared+offset处的值修改为 0xcccccccccccccccc。同时接着判断 shared+0x4094处的值是否为1,为1则继续等待,不为1则继续执行。 比较正确128次,则结束比较。
1 | // This process is handler. |
比较完成后,程序最后会从 管道 pipefd[0]中读取 数据,与 key进行比较,比较成功则输出 flag。
1 | char answer[KEYLENGTH]; |
利用分析
通过上面分析,可以看到我们主要的目标就是 爆破出 key值,输入管道,来得到flag。
但是,我们有几个困难:
- 每一位
key,爆破成功后,输入的shellcode会被随机修改,所以需要我们对shellcode自行修复 - 如何判断爆破成功,当我们爆破成功后,堆空间会有
0xcccccccccccccccc,可以通过查找该字符串来判断爆破成功 - 需要在最后父进程读取管道前,将所有爆破的字符写入管道,这里我们利用 上面比较时
shared+0x4094这个标志位来lock父进程
所以,我们的shellcode需要依次完成如下功能:
爆破
shared+0x4095通过不断增加
bl的值,并赋值给[rdx+0xfff]即shared+4095处的值。然后调用check进行判断是否 比较爆破成功。
1 | loop_: |
- 寻找
0xcccccccccccccccc
1 | find_cc: |
修复 shellcode
通过第2步找到的
[rdx+rcx*8]位置为0xcccccccccccc,并将[rdx+rax*8]修复shellcode.
1 | mov r11,[rdx+rax*8] |
- 写入管道
1 | //write() |
EXP
1 | from pwn import * |
ParseC
第一次做解释器类型的题目,这类题目就是代码量大且程序逻辑十分复杂。我们需要理解程序的输入格式和程序的逻辑。但是,总结的问题感觉是 需要重点关注 malloc 和 free 这类函数,还有就是考虑栈溢出和堆溢出。这道题就是一道 UAF 类型的题目。
程序分析
程序给了源码,那么就可以对源码进行编译。后续加载源码进行调试,对于调试更加方便。程序总体是自己实现了一个 C 的解释器。通过动态调试,可以发现程序中结构体 symbol的成员作用。当 是一个 字符变量时, funcp会存储 字符存储的堆地址,value会存储 字符的长度,type会是 Str类型,name存储 变量名字。当是一个 数组变量时,value会存储数组的个数,funcp会存储数组的 堆地址。
1 | /* this structure represent a symbol store in a symbol table */ |
当对一个已经赋值的字符变量,再次输入字符时,其会先对新字符串申请堆空间,然后释放原有的字符串所在的堆空间。字符串的堆申请代码如下:
1 | else if (token == '"' ) { // parse string |
而当有更新字符变量内容时,其释放堆块代码如下:
1 | match('='); |
而这里存在一个逻辑漏洞,当将一个变量赋值给另一个变量时,其会共用一个变量对象。如下代码所示。这里 s0的初始堆空间为 0x20,然后 s1被赋值为 s0,那么 s1就会指向 s0所在的 0x20的地址。然后 s0被赋值了新的字符串,其会 先申请 0x30的堆空间,然后释放 0x20的旧堆,最后 s0指向了 0x30堆地址。而 这里 s1仍然指向了已经被释放的 0x20堆地址。所以,存在一个 UAF漏洞。
1 | s0="aaaaaaaa"; |
数组分配代码如下,数组中的每个成员都用一个 symStruct结构体表示,程序会为数组 申请一个总体空间,大小为 sizeof(struct symStruct) * length + 1,length表示 数组的成员个数。每个 symStruct结构大小为 0x50。
1 | else if (token == Array) { |
然后,程序中有 puts和 read功能,puts可以将字符或数组中的内容输出。read可以读取 0x20的字符,但是其 只会将 value的值赋值为 输入的 浮点数。根据调试,如果使用 read读取到 数组变量时,其会赋值到 数组成员结构 symStruct的 0x28的偏移处。
1 | case Puts: |
利用分析
- 泄露地址
由于存在 UAF漏洞,所以可以创建一个长为 0xc0的字符,随后使用 UAF对其释放8次,即可释放到 unsortedbin。然后使用 puts将 libc地址泄露出来;
- 劫持 free_hook
这里 getshell的方法就是 劫持 free_hook。初始想法是通过 tcache来劫持,但是这里我们使用 read函数输入时,并不能直接将 数据输入到 字符或数组堆块的 开始地址处,而是输入到 偏移 0x28的地址处。所以,我们需要先构造一个堆块错位。
先构造一个 0x20堆块的double free和一个 0x50的 double free,如下所示:
1 | tcachebins |
随后,将 0x20堆块 fd指向 0x50堆块,如下所示:
1 | tcachebins |
然后,分配 0x20堆块来 将 0x50堆块的 fd指向 其偏移 0x28的地址处:
1 | tcachebins |
最后,分配一个 数组空间 0x50,使用 read函数输入 free_hook-0x28地址进入 数组内,其会被存储到 0x50堆块偏移 0x78地址,那么此时 free_hook-0x28地址就进入了 0x50的 tcache链
1 | tcachebins |
然后,分配一个 0x50堆块后,再分配一个数组空间,使用 read函数输入 system地址,此时 free_hook就被改为 system地址
注意:由于这里只能输入 浮点数,所以我们需要将 free_hook和 system地址都转为 浮点数输入。
EXP
1 | from pwn import * |
C++
程序分析
利用分析
EXP
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/12/19/2020XNUCA/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!