{"id":13056,"date":"2016-06-28T14:48:22","date_gmt":"2016-06-28T12:48:22","guid":{"rendered":"http:\/\/www.codilime.com\/?p=13056"},"modified":"2016-12-19T17:57:24","modified_gmt":"2016-12-19T16:57:24","slug":"whitehat11-wyginwys","status":"publish","type":"post","link":"https:\/\/codisec.com\/whitehat11-wyginwys\/","title":{"rendered":"WYGINWYS(what you get is not what you see)"},"content":{"rendered":"

Link: https:\/\/wargame.whitehat.vn\/Challenges\/DetailContest\/143<\/a>
\nPoints:\u00a0200
\nCategory:\u00a0Forensics<\/p>\n

Description<\/h2>\n

http:\/\/material.wargame.whitehat.vn\/contests\/11\/for1_206e72e52f2f73fa1a1080b70d528657.zip
\nnc 118.70.80.143\u00a07337<\/p><\/blockquote>\n

tl;dr<\/h2>\n

https:\/\/codisec.com\/veles\/.
\nZip archive containing disk image. Mount it with ntfs-3g. There is a binary file and after looking for deleted files you can also find a .pyc file. Turns out the python code is a custom RSA implementation. It also includes code to contact server and query\u00a0it for public key. After implementing function to decode files encrypted with python app we can use it to retrieve jpeg image from the first file we found on the disk image. The flag is visible on the image.<\/p>\n

Solution<\/h2>\n

The first part of this task was done by Jakub, the second by me. So I’m basing the initial part on his notes.<\/p>\n

In this task we got a zip archive. After unpacking it we see a single file:<\/p>\n

$ file image\r\n>\r\nimage: DOS\/MBR boot sector, code offset 0x52+2, OEM-ID \"NTFS    \", \r\nsectors\/cluster 8, Media descriptor 0xf8, sectors\/track 63, heads 255, \r\nhidden sectors 128, dos < 4.0 BootSector (0x0), FAT (1Y bit by descriptor); \r\nNTFS, sectors\/track 63, physical drive 0x80, sectors 20479, $MFT start cluster 853, \r\n$MFTMirror start cluster 2, bytes\/RecordSegment 2^(-1*246), clusters\/index block 1, \r\nserial number 060aabb3daabb0e92; contains Microsoft Windows XP\/VISTA bootloader BOOTMGR\r\n<\/pre>\n

Hmm, looks like a this image. Maybe we can mount it?<\/p>\n

$ sudo ntfs-3g image  \/mnt\/img\r\n$ ls -al \/mnt\/img\r\n> \r\ndrwxrwxrwx 1 root root   4096 Jun 24 22:28 .\r\ndrwxr-xr-x 3 root root   4096 Jun 25 04:19 ..\r\n-rwxrwxrwx 1 root root 220186 May 31 18:59 file\r\n$ file \/mnt\/img\/file\r\n>\r\n\/mnt\/img\/file: data\r\n<\/pre>\n

Ok, so we have a file. But it’s just some binary data, no idea what to do with it. Maybe we can get something more? After a few minutes of googling we know enough to try it.<\/p>\n

$ sudo umount \/mnt\/img \r\n$ ntfsundelete image\r\n>\r\nInode    Flags  %age     Date    Time       Size  Filename\r\n-----------------------------------------------------------------------\r\n16       F..!     0%  1970-01-01 01:00         0  <none>\r\n17       F..!     0%  1970-01-01 01:00         0  <none>\r\n18       F..!     0%  1970-01-01 01:00         0  <none>\r\n19       F..!     0%  1970-01-01 01:00         0  <none>\r\n20       F..!     0%  1970-01-01 01:00         0  <none>\r\n21       F..!     0%  1970-01-01 01:00         0  <none>\r\n22       F..!     0%  1970-01-01 01:00         0  <none>\r\n23       F..!     0%  1970-01-01 01:00         0  <none>\r\n34       FN..   100%  2016-06-24 19:08      6971  encrypt.pyc\r\n...\r\nFiles with potentially recoverable content: 1\r\nntfsundelete -u image -m encrypt.pyc\r\n<\/pre>\n

Nice, we got a .pyc file. We can now recover it using\u00a0uncompyle2<\/a>. At this point Jakub said the he has some python file to analyse, so naturally I jumped on a chance to work on it \ud83d\ude42<\/p>\n

Turns out encrypt.py is a custom implementation of RSA. The code is somewhat obfuscated, with a bunch of dead code, some unused variables with strange values, etc. After reading the main function it is pretty clear that running it will encrypt all files in the current directory subtree, which matches the list of extensions defined in the code (.doc, .docx, .mp3, .txt, .jpeg, …). So instead of going deeper into the code I just created a directory with a single .txt file containing aaaaaaa<\/code>. I’ve added a bunch of breakpoints in code using pdb and just printed out the exponent, the modulus and the data after each step of the algorithm (how it’s encoded to a number, padding, etc).<\/p>\n

It looks like the program is reading the file in 100 byte chunks, encrypting each chunk and writing it to a resulting .encrypted file. However, it also appends some additional data after each chunk. This additional bytes are generated by the below function:<\/p>\n

def init_spilt_string():\r\n    spilt_s = ''\r\n    printable = string.printable\r\n    len_ss = random_int(len(printable))\r\n    if len_ss <= 50:\r\n        A = '@'\r\n        spilt_s = printable[ord(A):printable.index(A)] + A\r\n        spilt_s += chr(15) + chr(21)\r\n    else:\r\n        A = ord('@')\r\n        spilt_s = printable[A:printable.index(chr(A))] + chr(A)\r\n        spilt_s += chr(15) + chr(21)\r\n    return spilt_s<\/pre>\n

Like the rest of the code this function is intentionally complicated, but all it really does is return the #$%&'()*+,-.\/:;<=>?@\\x0f\\x15<\/code>.<\/p>\n

Interestingly the encrypt.py<\/code> script also has a decrypt method.<\/p>\n

def decrypt():\r\n    privateEx = getEx()\r\n\r\ndef getEx():\r\n    s = socket.socket()\r\n    server = ('118.70.186.203 ', 7337)\r\n    s.connect(server)\r\n    data_req = 'id=AOXo==&getEx=1'\r\n    s.send(data_req)\r\n    Ex = s.recv(500)\r\n    return Ex<\/pre>\n

Ok, looks like all it does is contact a remote server and get us the RSA private exponent. And that’s really all we need – with private exponent we can decrypt the RSA and that is the only one-way function in the script. For everything else we can just write a reverse function.<\/p>\n

After some coding I have a script that can decrypt the simple text file I encrypted earlier. It also works with a larger “lorem ipsum” text file. Ok, let’s try something more difficult – a .png image of a cat. That one didn’t work out so well. It took me some debugging to find out that there is no logic in encrypt.py to add padding in case of RSA result being small enough to produce a hexstring at least 2 character shorter than expected).<\/p>\n

Finally I managed to write a working decryption script:<\/p>\n

import sys\r\n\r\n# set file names here\r\nin_filename = 'to_decrypt'\r\nout_filename = 'result'\r\n\r\n# RSA params\r\nfname = 'to_decrypt'\r\nn=105635707994215785064592688829431603295092019332941340218063024030214112435477388955472078061967366591274439713776563251824009429910796926009945639353007371855413993984379598868696195164099879394346788069941545731813097331015058786201697958822353538490747425524617437354269664766909763679684047454688278011031\r\ne=65537\r\npriv=88564621686225804143599949348031629074448419889036649215278253785863628513354908121326408001477927349804924920474953757949280896835049634504821293313553289355709328104008395204711274370387211439046775523078243154006826451358656526662979651456823488028759120748152472580144809988367304986290600684644152983809\r\n\r\nseparator = [ord(x) for x in \"#$%&'()*+,-.\/:;<=>?@\"]\r\nseparator.append(15)\r\nseparator.append(21)\r\n\r\ndef unrsa(data):\r\n    return pow(data, priv, n)\r\n\r\ndef to_hexlist(stuff):\r\n    result = []\r\n    for c in stuff:\r\n        num = ord(c)\r\n        result.append(\"{:02x}\".format(num))\r\n    return result\r\n\r\ndef hexstring2data(hex):\r\n   out = ''\r\n   for (a, b) in zip(hex[0::2], hex[1::2]):\r\n       out += chr(int(a + b, 16))\r\n   return out\r\n\r\ndef decrypt_pack(data, use_padding=True):\r\n    hexlist = to_hexlist(data)\r\n    crypted = int(''.join(hexlist), 16)\r\n    unc = unrsa(crypted)\r\n    uhex = \"{:x}\".format(unc)\r\n    if use_padding and len(uhex) < 200:\r\n        uhex = ((200 - len(uhex)) * '0') + uhex\r\n    return hexstring2data(uhex)\r\n\r\ndef decrypt(in_file, out_file):\r\n    with open(in_file, 'rb') as f:\r\n        data = f.read()\r\n    with open(out_file, 'wb') as outf:\r\n        stuff = []\r\n        sstep = 0\r\n        for i, x in enumerate(data):\r\n            if sstep==len(separator)-1 and ord(x) == 21:\r\n                sstep = 0\r\n                use_padding = i + 1 != len(data) # pad to 200 bytes, unless it's last chunk                             \r\n                outf.write(decrypt_pack(''.join(stuff[:-21]), use_padding)) # ignore 21 bytes of separator       \r\n                stuff = []\r\n                continue\r\n            if ord(x) == separator[sstep]:\r\n                sstep += 1\r\n            elif ord(x) == separator[0]:\r\n                sstep = 1\r\n            else:\r\n                sstep = 0\r\n            stuff.append(x)\r\n\r\nif __name__ == '__main__':\r\n    decrypt(in_filename, out_filename)\r\n<\/pre>\n

Running this script on the encrypted file we found on the disk image produced a JPEG image:
\n\"flag\"<\/p>\n

Later Jakub pointed out to me that the last step (padding) wasn’t really necessary. Without it the resulting jpeg was a bit garbled, but still easily readable.<\/p>\n","protected":false},"excerpt":{"rendered":"

Link: https:\/\/wargame.whitehat.vn\/Challenges\/DetailContest\/143 Points:\u00a0200 Category:\u00a0Forensics Description http:\/\/material.wargame.whitehat.vn\/contests\/11\/for1_206e72e52f2f73fa1a1080b70d528657.zip nc 118.70.80.143\u00a07337 tl;dr https:\/\/codisec.com\/veles\/. Zip archive containing disk image. Mount it with ntfs-3g. There is a binary file and after looking for deleted files you can also find a .pyc file. Turns out the…<\/span> <\/p>\n

Read more ›<\/div>\n

<\/a><\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[13],"tags":[4],"yoast_head":"\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n