分别有一个多线程竞争和mac相关的题目,涉及知识盲区,值得学习。
cfgo-checkin
程序分析
程序首先使用了 upx
加壳,去了壳之后能初步看出是一个go
程序,但是仍然很难看出具体逻辑。
运行程序,发现是一个走迷宫。提示走到100层,这里直接使用 bfs
能够走到100层。
然后,就相当于一个 blind-pwn
手动测一下。发现如果输入超出时,会发生崩溃,应该是有一个栈溢出。然后手动测一下,发现输入 'a'*0x70+p64(0xdeadbeef)
时,报错会报addr
为 deadbeef
。然后只修改 一个字节 0x10
时,发现会输出一个多余地址。这里怀疑这个 0x70
之后存储的是 我们输出的字符串的地址,也就是修改这个地址,可以泄露地址。
然后,继续测试,发现 0x78
和 0x80
的偏移处应该是存储的 输出的 size
。这里我们继续初始为 0x20
。然后再溢出到 0x110
偏移处报错,猜测这里是 返回地址。
利用分析
那么,这道题就是一个比较常见的 栈溢出。拥有一个任意地址输出和修改返回地址。
此外,发现栈初始化在 0xc000000000
这段内存中,而且不会变化,我们可以选择栈上一个 存有 程序基址的地址来泄露 程序的基地址。这里我们选择 0xc0000001c0
位置处。然后修改 返回地址的最低位为 \xce
从而返回到 漏洞函数开始处,继续栈溢出。
这里后面就是利用栈溢出 rop
来实现 getshell
。
EXP
1 | #coding: utf-8 |
roshambo
第一次做到多线程相关的题目,这道题有两种做法,都学习一下。
程序分析
程序总体逻辑有两种模式,第一种是 C,会创建用户,然后等待连接;第二种是 L,输入用户的 sha256值,就能够连接到 C模式下。
1 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) |
主函数功能如上所示。程序会开启两个线程,一个线程用于处理当前用户输入,另一个线程与两一个程序进行通信。这里我们主要查看 recv_client
里多线程运行的函数:
1 | void __fastcall __noreturn start_routine(void *a1) |
看一下 funcs
的主要功能:
1 | case 8LL: |
当我们选择 8时,程序会显示成功,同时会根据我们输入的size
来分配堆块,并输入。这里就存在漏洞,可以看到我们输入时是 read(0, ptr, size-1)
,如果我们申请的 size
为0,此时就会输入的 0xfffffff
大小,存在堆溢出漏洞。那么这里就可以是很常规的堆溢出做法。(这里根据赛后官方WP,可以看到这是一种非预期做法,而常规做法应该是 条件竞争)。
1 | case 3LL: |
这第二个洞,是我之前没有遇到的,这里会详细分析。我们可以看到 case 3时,会分配一个 堆块到 Ptr,然后 sleep(2)
后 free(ptr)
。而这里的 ptr
与我们的 case 8
的 释放的堆块是一致的。也就是如果有两个线程都完成 ptr=malloc()
后,ptr
指向的堆块是一致的,但是后面两个线程都会执行 free(ptr)
,那么也就是构成了一个 tcache double free
漏洞。
然后,这里还需要注意两个进程通信时,发送的数据格式如下(这里就直接使用 Nop 师傅的说明一下):
1 | +--------+--------+----------+--------+----------+ |
只有在status
为”[RPC]”,另一个client才会做出相应的动作,而至于name_Len
开始的位置,后续基本没有用到,可以不用管;对于option
,关注client
函数中的相应逻辑,重点关注case 8
:
利用分析
这里首先 说一下第一种堆溢出的做法。
- 构造空闲
tcache
首先使用 case 8
分别构造 多个 空闲tcache
,为后面修改 chunk size
做准备;
- 堆溢出泄露地址
然后,使用堆溢出修改一个 chunk size
为超过 tcache
,然后释放其到 Unsortedbin
,然后利用 堆溢出,将输入与 unsortedbin
的 fd
指针连接,这样后面就能够将 libc
输出。
- getshell
这里只能使用 orw
。利用 堆溢出 将之前 0x100
的 tcache
的 next
指向 __free_hook
,利用 setcontext+53
来 执行 orw
。
EXP
1 | from pwn import * |
第二种方法
上面的方法比较简单,算是非预期解。第二种方法就是利用多线程条件竞争。
1 | case 3LL: |
一个进程有两个子线程,一个线程处理由客户端或服务端传输的数据,一个线程处理由本程序发送的数据。
而这两个线程中,如果是接受到 case 3
的情况,即会 sleep(2)
然后 free(ptr)
;如果是本程序发送了 case 8
的情况,也会 free(ptr)
。那么如果我们先 接受了 case 3
完成了 堆块分配,然后进入 sleep(2)
;而此时 另一个线程又刚好进入 case 8
,完成了 堆块分配,然后直接 free(ptr)
,最后在 case 3
中 再 free(ptr)
,那么就构造了 tcache double free
漏洞。
利用方法
所以,这道题的利用方法,即是使 进程的 recv_client
线程 先进入 case 3
,再使线程 client
进入 case 8
。那么即可完成 double free
。我们利用此 double free
可以释放大堆块到 unsortedbin
中来泄露地址,并且 利用堆溢出来 getshell
。
1 | from pwn import * |
下面的题,并非 WM的题,不过是正在做WM时,看了下vn的题。所以这里就把有意思的点记下来。
2021-V&NCTF-little red flower
程序分析
抽点时间大概看了一下这道题,还挺有意思的。记录一下这道题。
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
程序漏洞很简单,开始就给了libc
地址。允许对任意地址写一个bit
。对堆块地址第一次写时,off
可以自己指定,存在堆溢出。最后是能申请一个大块。
利用分析
想了很久,这个任意地址一比特写有什么用,试了各种方法也不太靠谱。最后给了个hint
,让关注一下对 TCACHE_MAX_BINS
的攻击。
然后,就仔细研究一下。首先我们对 global_max_fast
这个的攻击应该很熟悉,把这个全局变量改小,可以将原本 fastbin
放置到 unsortedbin
中;把他改大,可以将大块放入 fastbin
中。
那么,这里 TCACHE_MAX_BINS
的作用是什么?源码可看到:
1 |
|
TCACHE_MAX_BINS
指定了 tcache_perthread_struct
中 tcache
链的数量。如果将其改大,那么 tcache
链将会增加,而每一个 tcache
链是按照 0x10
增加的。也就是 此时 我们释放一个大块,其会被放入 增大的 tcache
链中。举个例子,原本 TCACHE_MAX_BINS
的值为 0x40
,能存放的堆块大小为 (0x40*0x10)+0x10=0x410
,也即小于等于 0x410
的堆块会被放入 tcache
中。而如果我们将其改为 0xf0
,那么能存放的堆块大小变为 (0xf0*0x10)+0x10=0xf10
。
还需要注意,如何获得 TCACHE_MAX_BINS
这个全局变量的地址。从源码中可以看到 mp_
结构中,存储了 TCACHE_MAX_BINS
的值。而为了确定 mp_
结构体的位置,我们可以使用 search -p tcache_Perthread_struct_addr libc
命令,在 libc
中搜索哪个地址,存储了 tcache_perthread_struct
结构体的位置。
1 | static struct malloc_par mp_ = |
搜索结果如下,该地址 +0x8
即为 TCACHE_MAX_BINS
的地址,此时为 0x40
。
1 | pwndbg> search -p 0x5588ac539000 libc |
那么现在的总体思路,就是通过任意地址写一比特,将 TCACHE_MAX_BINS
改大。然后再通过 堆溢出漏洞,在一个适当的地址写上 free_hook
的地址。再申请此时 free_hook
地址所在的 tcache
链表所指向的大小,即可将 free_hook
分配出来。后续就是劫持 free_hook
执行 orw
。
这里,还需要注意如何确定 free_hook
写在哪个位置,又要申请多大的size
。根据我的调试,当把 TCACHE_MAX_BINS
改大后,tcache_perthread_struct
结构体中 counts[TCACHE_MAX_BINS]
的大小仍然为 (0x40*2)=0x80
。所以 0x80
后全是 entries
结构。
1 | typedef struct tcache_perthread_struct |
举个例子说明,当我们需要申请 0x1020
的tcache
时,我们需要将 (0x1020-0x20)/0x10*8=0x800
,也即 tcache_perthread_struct+0x800+0x90
的位置写上 一个堆块地址。还需要将 (0x1020-0x20)/0x10*2=0x200
的位置 写上为\x01\x00
。
EXP
1 | from pwn import * |
White_Give_Flag
想了很久,发现没啥可利用的点。这里前面把flag
读到了 0x300
到 0x500
之间的堆块偏移 0x10
处。然后释放了堆块,但是 flag
在堆块上保留了下来。
所以,这里就不断申请一个 0x300
到0x500
的堆块,然后使用 edit
修改前 0x10
字节,随后使用 泄露数据,看是否有 flag
。
这里有一个比较巧妙地知识,就是我们泄露时,需要用到一个 索引向上溢出,也即要保证我们地 read
返回 为0。这里可以利用 pwntools
的如下函数,断开连接,即可使 read
读取 EOF
,从而返回 0.
1 | from pwn import * |
hh
一个比较简单的vm
,重点官族如下两个指令:
1 | case 9u: |
指令 9,可以将我们的 输入 buf[code_ptr]
赋值给 栈上的数据 stack[ptr+1000]
。指令 D,可已将栈上的数据 stack[x+1000]
的值赋值给栈上 stack[idx]
,其中 idx是由我们指定的。
那么根据这两个指令组合,我们即可实现栈上任意地址写。我们可以先通过 指令 9,将我们的数据,写入栈上。然后再通过 指令D,将输入的数据依次 赋值给栈上的指定地址。
1 | from pwn import * |
ff
2.32 下的 UAF
漏洞,2.32
新加的保护,之前已经讲过,这里不做介绍。
大概思路,就是利用 UAF
构造一次 double_free
,来劫持 tcache_perthread_struct
,并在 tcache
链上留下 libc
地址。随后 通过 stdout
来泄露地址,并劫持 free_hook
,执行 orw
。
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/12/19/2020WMCTF/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!