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 许可协议。转载请注明出处!