[NepCTF 2025] time 多线程栈
程序分析:
1 | unsigned __int64 inputname() |
我们输入的 name 会被存入到全局遍历那个,然后程序会执行 fork 函数创建子进程。
创建成功会返回子进程的 pid,然后父进程使用 wait 函数等待子进程结束。
此时子进程会执行 execve 函数执行 /bin/ls / -al 命令输出根目录的文件列表,执行完毕后子进程结束。
子进程结束后,父进程输出 good luck 并返回到 main 函数继续执行。此时 main 函数循环调用 filename 函数
我们可以用 filename 函数输入文件名:
1 | __int64 filename() |
程序会将我们的输入与 flag 进行比较,若输入非 flag 字符串,则返回 True 允许向下继续执行。
当我们输入非 flag 的时候,程序会启动线程并调用 start_rountine 函数:
1 | pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, 0LL); |
继续分析 start_routine 函数:
1 | unsigned __int64 __fastcall start_routine(void *a1) |
根据输出,我们可以推断出它根据文件名读入文件内容,并计算MD5后输出。
然后将文件内容读入到 buf 变量,并执行 printf(name) 函数输出name触发格式化字符串漏洞。
条件竞争利用方式:
file 变量是全局的。
漏洞在于程序处理多线程的方式:
- main 主线程调用 filename(),检查全局的 file 变量。
- 如果通过了检查,main 线程会创建一个运行 start_routine 的新线程。
- main 线程立即循环回去,并阻塞在 filename() 内部的 scanf(“%s”, file); 处等待下一次输入。
- 与此同时,新创建的线程计算文件名的哈希,进行一些初始化,然后才调用 open(file, 0)。
- 在 main 线程检查文件名和后台线程使用文件名之间,存在一个时间窗口。如果你输入一个安全的文件名通过检查,然后在后台线程执行到 open() 调用之前立即输入 “flag”,后台线程就会打开 “flag” 而不是那个安全的文件。
EXP1:单发测试 Oneshot
我们知道,文件内容被读取到了 buf 上但是并没有输出,程序执行了 printf(name)存在格式化字符串漏洞
我们的目标是去泄露格式化字符串漏洞栈上变量 buf 的内容。
我们先创建一个 hint.txt 用于竞争,内容为 flag{123}
首先我们需要知道 buf 变量的偏移:
- buf 的起始位于 [rsp + 0x80],而栈上的参数从 rsp 开始,偏移是 16 个字,0x80 / 8 = 16
- 在 x64 的调用约定当中,前六个参数在寄存器中,这意味这他从第 22 个参数开始:16 + 6 = 22
为了保证足够的长度,索性直接泄露 40 个字节的长度
1 | p.sendlineafter(b'please input your name:\n', b'%22$p%23$p%24$p') |
我们在分析inputFilename函数时发现,如果我们输入的文件名为flag,则不会执行start_routine函数。
条件竞争绕过检查
前面的分析中,我们发现每次输入文件名后程序并不是直接执行start_routine函数,而是通过pthread_create 创建新的线程执行。
这就存在时间竞争漏洞,逻辑如下:
- 我们在inputFilename函数中输入合法的文件(非flag)从而通过程序的检查。
- 程序通过pthread_create 启动一个新线程,新线程执行start_routine函数,访问全局变量 file 文件名
- 主线程进入下一次循环,在新线程执行前/执行期间,再次篡改这个全局变量file。
- 导致子线程原本应该依赖未修改值file读入文件的内容,结果读入修改后的file的文件内容,从而造成逻辑绕过
1 | p.sendlineafter(b'please input your name:\n', b'%22$p%23$p%24$p') |
- 第一句
p.sendline(b'hint.txt')相当于输入了一个安全的、允许读取的文件名。这会让主线程的filename()函数通过检查,并去创建一个新的后台线程。 - 第二句
p.sendline(b'flag')紧跟其后发送。此时主线程刚刚创建完线程,正循环回来再次调用scanf等待输入。这句flag会被瞬间读入,从而覆盖掉全局变量file。 - 如果这两句发送得足够快,后台线程在执行
open(file)时,file就已经被改成了flag。
1 | from pwn import * |
EXP2:
1 | from pwn import * |
更新: 2026-04-18 21:02:22
原文: https://www.yuque.com/idcm/wnemg9/uibythm5enq15zak