{"id":2165,"date":"2016-07-07T18:29:33","date_gmt":"2016-07-07T16:29:33","guid":{"rendered":"https:\/\/www.asafety.fr\/?p=2165"},"modified":"2016-07-25T00:32:26","modified_gmt":"2016-07-24T22:32:26","slug":"wargame-ndh-2016-write-up-crypto-supercipher","status":"publish","type":"post","link":"https:\/\/www.asafety.fr\/en\/cryptologie\/wargame-ndh-2016-write-up-crypto-supercipher\/","title":{"rendered":"[WARGAME NDH 2016] Write-Up \u2013 Crypto: SuperCipher"},"content":{"rendered":"<p><\/p>\n<p style=\"text-align: center;\"><strong>Write-up of the challenge \u201cCrypto\u00a0\u2013 SuperCipher\u201d of Nuit du\u00a0Hack 2016\u00a0Wargame<\/strong><\/p>\n<p>The weekend of 02-03 july 2016\u00a0is the WARGAME of the\u00a0<strong><a href=\"https:\/\/nuitduhack.com\/fr\/\" target=\"_blank\">Nuit du Hack 2016<\/a><\/strong>\u00a0as a <strong>Jeopardy CTF<\/strong>. Having had the opportunity and the time to participate with some colleagues and friends, here\u2019s a write-up resolution of the challenges which we could participate.<\/p>\n<ul>\n<li>Category:\u00a0<strong>Crypto<\/strong><\/li>\n<li>Name: <strong>SuperCipher<\/strong><\/li>\n<li>Description : <em>So easy<\/em><\/li>\n<li>URL : sp.wargame.ndh<\/li>\n<li>Points : <strong>10<\/strong><b>0<\/b><\/li>\n<\/ul>\n<p style=\"text-align: center;\"><strong>tl;dr : <strong>Download and decompile home.pyc file, then extract timestamp of &#8220;secret&#8221; file (seed) to uncipher<\/strong><\/strong><\/p>\n<p>This challenge was in the form of a web interface, allowing:<\/p>\n<ul>\n<li>Upload a file to encrypt: a ZIP archive output with two files &#8220;secret&#8221;\u00a0and &#8220;key&#8221;<\/li>\n<li>Upload an encrypted file, specify a key, and retrieve the plaintext\u00a0version<\/li>\n<li>Download &#8220;<a href=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/flag.zip\">flag.zip<\/a>&#8221; that had been encrypted via this service<\/li>\n<\/ul>\n<p>It is a pure and simple encryption service on line.<\/p>\n<p>Watching the contents of the &#8220;flag.zip&#8221;<\/p>\n<pre>root@kali 12:22 [~\/ndh2k16\/SuperCipher] # unzip flag.zip\r\nArchive: flag.zip\r\n creating: secret\/\r\n inflating: secret\/key\r\n extracting: secret\/secret<\/pre>\n<p>Could it be so simple? See the contents of these files:<\/p>\n<p>secret\/secret :<\/p>\n<pre>\/8bAieboX5pFq1sI6js92nrI6huZoxLZ5A==<\/pre>\n<p>secret\/key :<\/p>\n<pre>QUhBSEFILVRISVMtSVMtTk9ULVRIRS1LRVk=<\/pre>\n<p>Decode the key:<\/p>\n<pre>root@kali 12:24 [~\/ndh2k16\/SuperCipher] # cat secret\/key | base64 -d\r\nAHAHAH-THIS-IS-NOT-THE-KEY<\/pre>\n<p>Ok, well we had a &#8220;secret&#8221; but the key is still unknown &#8230;<\/p>\n<p>Page performing the encryption \/ decryption processing is called &#8220;home.py&#8221;. Sounds like Python. Try to recover the compiled version (<strong>home.pyc<\/strong>) script: bingo! It was the compiled version.<\/p>\n<p>With the compiled version (*.pyc) script, it is necessary to recover the original Python source code. For that different solutions exist as &#8220;<strong>uncompyle2<\/strong>&#8220;, &#8220;<strong>decompyle ++<\/strong>&#8220;, etc. In our case we opted for a solution on Windows &#8220;<strong><a href=\"https:\/\/sourceforge.net\/projects\/easypythondecompiler\/\" target=\"_blank\">Easy Python Decompiler<\/a><\/strong>&#8221; (which ultimately uses either of the previous binary).<\/p>\n<p>Launch tool, drag&#8217;n&#8217;drop the * .pyc file and a &#8220;<strong>home.pyc_dis<\/strong>&#8221; file with the Python code is automatically generated.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"attachment_2166\" style=\"width: 310px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/decompyle.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-2166\" class=\"size-medium wp-image-2166\" src=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/decompyle-300x252.png\" alt=\"decompyle\" width=\"300\" height=\"252\" srcset=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/decompyle-300x252.png 300w, https:\/\/www.asafety.fr\/wp-content\/uploads\/decompyle.png 459w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><p id=\"caption-attachment-2166\" class=\"wp-caption-text\">decompyle<\/p><\/div>\n<p>Python source code:<\/p>\n<pre># Embedded file name: home.py\r\nfrom bottle import route, run, template, request, static_file\r\nimport time\r\nimport random\r\nimport base64\r\nimport zipfile\r\nfrom StringIO import StringIO\r\nimport os, sys\r\nfrom Crypto.Cipher import AES\r\nfrom Crypto import Random\r\n\r\nclass AESCipher:\r\n\r\ndef __init__(self):\r\n self.key = 'FOOBARBAZU123456'\r\n self.pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)\r\n self.unpad = lambda s: s[:-ord(s[len(s) - 1:])]\r\n\r\ndef encrypt(self, raw):\r\n raw = str(raw)\r\n raw = self.pad(raw)\r\n iv = Random.new().read(AES.block_size)\r\n cipher = AES.new(self.key, AES.MODE_CBC, iv)\r\n return base64.b64encode(iv + cipher.encrypt(raw))\r\n\r\ndef decrypt(self, enc):\r\n enc = base64.b64decode(enc)\r\n iv = enc[:16]\r\n cipher = AES.new(self.key, AES.MODE_CBC, iv)\r\n return self.unpad(cipher.decrypt(enc[16:]))\r\n\r\n\r\n@route('\/')\r\n@route('\/home.py')\r\ndef hello():\r\n return '\\n&lt;h1&gt;SuperCipher&lt;\/h1&gt;\\n&lt;h2&gt;Chiffrer :&lt;\/h2&gt;\\n&lt;form action=\"\/home.py\/secret.zip\" method=\"post\" enctype=\"multipart\/form-data\"&gt;\\n Select a file: &lt;input type=\"file\" name=\"upload\" \/&gt;\\n &lt;input type=\"submit\" value=\"cipher\" \/&gt;\\n&lt;\/form&gt;\\n&lt;br \/&gt;\\n&lt;h2&gt;Dechiffrer&lt;\/h2&gt;\\n&lt;form action=\"\/home.py\/uncipher\" method=\"post\" enctype=\"multipart\/form-data\"&gt;\\n Password: &lt;input type=\"password\" name=\"key\" \/&gt;\\n Select a file: &lt;input type=\"file\" name=\"upload\" \/&gt;\\n &lt;input type=\"submit\" value=\"uncipher\" \/&gt;\\n&lt;\/form&gt;\\n'\r\n\r\n\r\n@route('\/home.py\/secret.zip', method='POST')\r\ndef cipher():\r\n seed = int(time.time())\r\n random.seed(seed)\r\n upload = request.files.get('upload')\r\n upload_content = upload.file.read()\r\n content_size = len(upload_content)\r\n mask = ''.join([ chr(random.randint(1, 255)) for _ in xrange(content_size) ])\r\n cipher = ''.join((chr(ord(a) ^ ord(b)) for a, b in zip(mask, upload_content)))\r\n b64_cipher = base64.b64encode(cipher)\r\n aes = AESCipher()\r\n key = aes.encrypt(seed)\r\n secret = StringIO()\r\n zf = zipfile.ZipFile(secret, mode='w')\r\n zf.writestr('secret', b64_cipher)\r\n zf.writestr('key', key)\r\n zf.close()\r\n secret.seek(0)\r\n return secret\r\n\r\n\r\n@route('\/home.py\/uncipher', method='POST')\r\ndef cipher():\r\n key = request.forms.get('key')\r\n upload = request.files.get('upload')\r\n try:\r\n aes = AESCipher()\r\n key = aes.decrypt(key)\r\n random.seed(int(key))\r\n upload_content = base64.b64decode(upload.file.read())\r\n content_size = len(upload_content)\r\n mask = ''.join([ chr(random.randint(1, 255)) for _ in xrange(content_size) ])\r\n plain = ''.join((chr(ord(a) ^ ord(b)) for a, b in zip(mask, upload_content)))\r\n return plain\r\n except:\r\n return 'Uncipher error.'\r\n\r\n\r\n@route('\/&lt;filename:path&gt;')\r\ndef download(filename):\r\n return static_file(filename, root=os.path.join(os.path.dirname(sys.argv[0])), download=filename)\r\n\r\n\r\nrun(host='0.0.0.0', port=8080)<\/pre>\n<p>Note that a key is set in the object class &#8220;AESCipher&#8221;<\/p>\n<pre>self.key = 'FOOBARBAZU123456'<\/pre>\n<p>In addition, the method of &#8220;encryption&#8221; (which produces the &#8220;secret.zip&#8221; output), initializes its encryption process via the current timestamp:<\/p>\n<pre>@route('\/home.py\/secret.zip', method='POST')\r\ndef cipher():\r\n seed = int(time.time())\r\n random.seed(seed)<\/pre>\n<p>We also have access to the complete deciphering function:<\/p>\n<pre>@route('\/home.py\/uncipher', method='POST')\r\ndef cipher():\r\n key = request.forms.get('key')\r\n upload = request.files.get('upload')\r\n try:\r\n aes = AESCipher()\r\n key = aes.decrypt(key)\r\n random.seed(int(key))\r\n upload_content = base64.b64decode(upload.file.read())\r\n content_size = len(upload_content)\r\n mask = ''.join([ chr(random.randint(1, 255)) for _ in xrange(content_size) ])\r\n plain = ''.join((chr(ord(a) ^ ord(b)) for a, b in zip(mask, upload_content)))\r\n return plain\r\n except:\r\n return 'Uncipher error.'<\/pre>\n<p>The idea is based on the content of &#8220;flag.zip&#8221; including its &#8220;secret&#8221; to be decrypted. The &#8220;seed&#8221; that was served at the time to encrypt this message containing the flag, can be deducted from the &#8220;<strong>last modified date of the secret file<\/strong>&#8221; (command &#8220;stat&#8221; in Linux):<\/p>\n<div id=\"attachment_2168\" style=\"width: 293px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/timestamp.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-2168\" class=\"wp-image-2168 size-medium\" src=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/timestamp-283x300.png\" alt=\"timestamp\" width=\"283\" height=\"300\" srcset=\"https:\/\/www.asafety.fr\/wp-content\/uploads\/timestamp-283x300.png 283w, https:\/\/www.asafety.fr\/wp-content\/uploads\/timestamp-300x318.png 300w, https:\/\/www.asafety.fr\/wp-content\/uploads\/timestamp.png 486w\" sizes=\"auto, (max-width: 283px) 100vw, 283px\" \/><\/a><p id=\"caption-attachment-2168\" class=\"wp-caption-text\">timestamp<\/p><\/div>\n<p>&#8220;secret&#8221; was last modified the\u00a0<strong>June 30, 2016 at 17h 17 min 52s<\/strong>. We need to convert this date to a\u00a0timestamp and arbitrarily initialize the &#8220;seed&#8221; of the decryption function with this value:<\/p>\n<pre>t = datetime.datetime(2016, 06, 30, 17, 17, second=52)\r\nseed = int(time.mktime(t.timetuple()))\r\n#seed = 1467299872<\/pre>\n<p>For the final resolution, we need to create a new Python script &#8220;flag.py&#8221; inspired from the\u00a0decryption function, with\u00a0our &#8220;seed timestamp&#8221;:<\/p>\n<pre>import random\r\nimport base64\r\nimport time\r\nimport datetime\r\nfrom Crypto.Cipher import AES\r\nfrom Crypto import Random\r\n\r\nclass AESCipher:\r\n\r\ndef __init__(self):\r\n self.key = 'FOOBARBAZU123456'\r\n self.pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)\r\n self.unpad = lambda s: s[:-ord(s[len(s) - 1:])]\r\n\r\ndef encrypt(self, raw):\r\n raw = str(raw)\r\n raw = self.pad(raw)\r\n iv = Random.new().read(AES.block_size)\r\n cipher = AES.new(self.key, AES.MODE_CBC, iv)\r\n return base64.b64encode(iv + cipher.encrypt(raw))\r\n\r\ndef decrypt(self, enc):\r\n enc = base64.b64decode(enc)\r\n iv = enc[:16]\r\n cipher = AES.new(self.key, AES.MODE_CBC, iv)\r\n return self.unpad(cipher.decrypt(enc[16:]))\r\n\r\nt = datetime.datetime(2016, 06, 30, 17, 17, second=52)\r\nseed = int(time.mktime(t.timetuple()))\r\n#seed = 1467299872\r\naes = AESCipher()\r\nkey = aes.encrypt(seed)\r\n\r\naes = AESCipher()\r\nkey = aes.decrypt(key)\r\nrandom.seed(int(key))\r\nupload_content = base64.b64decode(\"\/8bAieboX5pFq1sI6js92nrI6huZoxLZ5A==\")\r\ncontent_size = len(upload_content)\r\nmask = ''.join([chr(random.randint(1,255)) for _ in xrange(content_size)])\r\nplain = ''.join(chr(ord(a)^ord(b)) for a,b in zip(mask, upload_content))\r\nprint \"Flag : \" + plain<\/pre>\n<p>Run :<\/p>\n<pre>root@kali 12:16 [~\/ndh2k16] # python flag.py\r\nFlag : ndh_crypt0sh1tn3v3rch4ng3<\/pre>\n<p>Flag :\u00a0<strong>ndh_crypt0sh1tn3v3rch4ng3<\/strong><\/p>\n<p>Thank you to all the team of the NDH2K16 for this event and for the whole organization!<\/p>\n<p>Greeting to\u00a0<a href=\"http:\/\/www.information-security.fr\/\" target=\"_blank\">nj8<\/a>, <a href=\"http:\/\/0xbadcoded.com\/\" target=\"_blank\">St0rn<\/a>, <a href=\"http:\/\/www.georgestaupin.com\/\" target=\"_blank\">Emiya<\/a>, Mido, downgrade,\u00a0Ryuk@n and\u00a0rikelm, ?\u00a0\/\/ Gr3etZ<\/p>\n<p><strong>Sources &amp; resources :<\/strong><\/p>\n<ul>\n<li><a href=\"https:\/\/sourceforge.net\/projects\/easypythondecompiler\/\" target=\"_blank\">Easy Python Decompiler<\/a><\/li>\n<\/ul>\n<p><\/p>","protected":false},"excerpt":{"rendered":"<p>Write-up of the challenge \u201cCrypto\u00a0\u2013 SuperCipher\u201d of Nuit du\u00a0Hack 2016\u00a0Wargame The weekend of 02-03 july 2016\u00a0is the WARGAME of the\u00a0Nuit [&hellip;]<\/p>\n","protected":false},"author":1337,"featured_media":2112,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[58,57,56,524,523,526,527,421,525],"tags":[501,474,505,216],"class_list":["post-2165","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptanalyse","category-cryptographie","category-cryptologie","category-ctf","category-events","category-ndh","category-ndh2k16","category-reverse-engineering","category-wargame","tag-aes","tag-cryptography","tag-pyc","tag-python"],"_links":{"self":[{"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/posts\/2165","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/users\/1337"}],"replies":[{"embeddable":true,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/comments?post=2165"}],"version-history":[{"count":7,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/posts\/2165\/revisions"}],"predecessor-version":[{"id":2177,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/posts\/2165\/revisions\/2177"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/media\/2112"}],"wp:attachment":[{"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/media?parent=2165"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/categories?post=2165"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.asafety.fr\/en\/wp-json\/wp\/v2\/tags?post=2165"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}