This challenge is a simple format string read and write. In this case, full
RELRO is enabled so we can’t overwrite the GOT. We can leak a stack address and
use it to calculate the address of RIP.
This challenge is also a format string challenge using snprintf. The binary
checks if the parsed format string contains a “username-password” pair that was
randomly generated, then checks if the string ends with “:y”.
The generated pairs are stored in the heap. We can use %s to get a
username:password pair since there are addresses on the stack pointing to the
generated strings.
Since the binary appends :n to the end of our string, we need to somehow get
rid of it. snprintf takes an integer argument to indicate how many characters
to write to the output string. Extra characters after 0x80 are truncated, hence
we can use %c to push :n out of the string and put :y just before it.
from pwn import *
exe = ELF("./flagmin_patched")
context.binary = exe
defconn(): if args.LOCAL: p = process([exe.path]) if args.GDB: gdb.attach(p) pause() else: p = remote("chals.f.cyberthon23.ctf.sg", 43040)
return p
defmain(): p = conn()
# good luck pwning :) p.sendline(b"%8$s") p.sendline(b"%16$s:%91c:y")
p.interactive()
if __name__ == "__main__": main()
Passgen
In this challenge, our goal is to leak the seed and hence guess the password
the binary generated.
In IDA, we can see that the seed is located in bss. In addition, session and
dest are located above the seed in bss too. Our input to the binary is stored
in dest, while the generated password is stored in session.
We also have a one-byte overflow in strncpy when the binary reads in our
name. Using this, we can overwrite the null byte between dest and seed,
hence leaking the seed. Then, we can use the seed to initialize rand() and
generate the same password as was generated in the binary.
#include<stdlib.h> #include<string.h>
intmain(int argc, char **argv) { char v4[88]; char out[33]; int seed = atoi(argv[1]); srand(seed); strcpy(v4, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#""$%^&*()-_=+[]{};:,.<>?"); for (int i = 0; i < 32; ++i) { out[i] = v4[rand() % 87]; } out[32] = 0; puts(out); }
This is a Windows ret2win challenge. Most of the challenge is actually just red
herring/serves to make the challenge more approachable but tedious. We can use
IDA to analyse the executable.
In essence, the vulnerability is a buffer overflow in static region that allows
us to modify a reference to a error handler function, and change it to the win
function. There’s only 1 strlen check to pass which we can easily bypass
using null bytes.
This challenge involves overwriting the GOT using an array OOB. I struggled a
lot with trying to find a good function to overwrite, because the write is per
5 bytes and might overflow into other functions. Hence a good function must be
chosen to prevent the binary from crashing as we win the game and call our win
function.
endwin would have been a good candidate to overwrite, since it has a lot of
not so useful functions after it (exit, printw). But the win function
itself also calls endwin, and after I managed to overwrite endwin safely I
realized that it would result in infinite recursion. If I tried to skip past
the endwin call, it would result in stack alignment issues.
Hence, the next best function to overwrite was exit. However, to write
completely to exit, it’s necessary to overwrite the last byte of endwin. At
the point of our write, endwin hasn’t been resolved yet, so the last byte is
always 0xc0. Besides that, the rest of the payload should be fairly
straightforward:
from pwn import *
exe = ELF("./wordpocalypse")
context.binary = exe
defconn(): if args.LOCAL: p = process([exe.path]) if args.GDB: gdb.attach(p) pause() else: p = remote("chals.f.cyberthon23.ctf.sg", 43020)