这个题目和 2022 华东北赛区的 bigduck 题目打法很像,可以放在一起学习,都可以用 environ 或者 setcontext


可知开启 NX、PIE、Canary、Full RELEO、Glibc-2.35
程序分析:main
简单逆向修改了一下函数名,目前看来是没有太大的逆向难度的,整个菜单算是比较规整的:
1 | void __fastcall __noreturn main(const char *p_Invalid_choice, char **p_n5, char **a3) |

add:
1 | unsigned __int64 add() |
delete:
1 | unsigned __int64 delete() |
这里存在 UAF,*((_QWORD *)&unk_4060 + idx) = 0;是将数组下标置零了,而并非我们常说的指针
其实这里我当时也想当然得认为没有 UAF,也侧面反映了我没有认真审计代码(其实我当时写这道题目的时候有一瞬间的感觉认为这里存在 UAF,尽管看见置零了,但是总感觉怪怪的,不过也没多在意)
1 | for ( i = 0; i <= 79 && *((_QWORD *)&unk_4060 + i); ++i ) |
从 add 当中的 for 循环可以看出来这里其实说的是数组的下标,而真正的内容指针应当是:
1 | *((void **)&unk_4060 + idx) |
是进行两次解引用的,和 free 掉的应当是一个东西
edit:
1 | unsigned __int64 edit() |
edit 存在堆溢出漏洞,在修改的时候没有限制修改大小,在这里就存在着堆重叠的漏洞
show:
1 | unsigned __int64 show() |
这里 show 函数存在 printf 截断,如果没有 \x00 就会一直打印下去。
EXP 思路:
封装函数:
1 | add_idx = 1 |
这里没什么好说的,继续往下看吧:
构造堆重叠泄露 libc:
1 | add(0x408,b'aaaa') # chunk0 |
我们前面分析到 edit 存在堆重叠的漏洞,那么我们就可以利用常用的手法构造堆重叠:修改 chunk1 的 size 封盖 chunk2, 然后释放 chunk1,chunk1 这个 0x820 大块就会被分到 UnSortedbin 当中

为了不影响结构,我们重新申请 0x408 大小的 chunk1,切割剩下的仍是 chunk2 放回 UnSortedbin
1 | add(0x408,b'a')#1 |
这样子就可以利用 UnSortedbin 泄露 libc 了:

泄露 heap_base:
1 | add(0x408,b'aaaa') #4 & 2 |
在 glibc-2.29 之后都会有指针保护,简单来说就是 当前 fd = 当前堆地址 << 12 ^ 原 fd 指针


0x562ccb3682f0 = 0x562ccb368 ^ 0x562ccb368460

那我们就可以利用基地址以 000 结尾的特性,接收 5 位 fd 指针然后左移就可以获取到 heap_base 了
泄露 environ 获取栈地址:
我们接下来想要利用劫持程序流,在栈上劫持一个地址去填充我们 ORW 的地址,进而获取 shell,在此之前我们就需要先获取栈上的地址,那么就可以利用泄露 environ 的手法泄露栈地址

我们通过 pwndbg 可以查找到 environ 的地址,之后利用 heap_base 定位一下 chunk6

我们可以利用 environ 伪造一个 fd 指针,的那个下一次申请的时候就会申请到 environ 附近的地址:
1 | environ = libc_base + libc.symbols['environ'] |
之后删除 chunk6,利用 chunk5 的堆溢出修改 free_chunk6 的 fd 指针并重新申请:
1 | delete(6) |
之后我们在 0x16CC 处下断点:这里是 add 函数即将退出执行 ret 的地方,可以确认用来下一步跳转的栈地址
1 | .text:00000000000016CC |

可以在当前断点寻找返回地址,然后篡改这个返回地址为 ORW 执行地址,那么下一步就剩下构造 ORW 了:
1 | edit(6,0x500,b'a'*0x40f) |
构造 ORW:
1 | rdi = libc_base + 0x2a3e5 |
第二次 Tcache Poisoning
1 | add(0x408,b'aaaa')#7 |
和第一次 Tcache Poisoning 的手法一样,依旧是 free 两个堆之后利用堆溢出修改 chunk8 的 fd 指针
之后利用 printf 的特性来接收 orw 的返回值。
总 EXP:
1 | from pwn import * |

说些什么吧!