I got this crackme from one of my readers, who asked me for the help in understanding how to solve it. As he wrote in the e-mail, it comes “from last year competition by the CheckPoint company”. I promised to make a writeup, so here it is :). I hope it will benefit others also.
The crackme is for the beginners, so don’t expect any fireworks ;). But it was relaxing and fun to solve.
The crackme can be found here (password: crackme), also available at HA: 8ee7382cfdf632c29df5f2d9d3286614
Overview
This is how the application looks:
When we run in, it asks for a username:
And when we give an invalid one, it responds with a text:
“Go away, you are not me”
The first important step in solving the crackme, is noticing how exactly was it made and what tools are to be applied. As the icon hints, it seems to be an application written in Python and converted into EXE. But let’s confirm it by looking inside. The main process runs another instance of itself:
Let’s attach the debugger to the child process and see the loaded modules:
We can find that indeed Python2.7 is loaded to interpret the code (the module is marked red on the picture).
At this moment we can confirm that this EXE is in reality a wrapper for a Python script. There are several applications that allows to achieve it. Depending on which application produced the wrapping, the output has a bit different format and requires a different decompiler.
The popular converters of Python scripts into EXE format, are, i.e. Py2Exe and PyInstaller. This time, PyInstaller was applied.
Tools required
-
- PyInstallerExtractor – allows to unwrap an EXE created by PyIntaller
- The resulting .pyc files need to be decompiled by an appropriate Python decompiler. Depending on the Python version you may use i.e.: Easy Python Decompiler, (for Python > 3.4 use i.e. https://python-decompiler.com/ ) or uncompyle6
Step 1 – Unwrapping the exe
Unpacking the EXE is easy with the appropriate tool. In this case I used PyInstallerExtractor, written in Python.
python pyinstxtractor.py pycrackme.exe
This is the output:
[*] Processing pycrackme.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 27
[*] Length of package: 2604972 bytes
[*] Found 20 files in CArchive
[*] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: black_box
[*] Found 196 files in PYZ archive
[*] Successfully extracted pyinstaller archive: pycrackme.exe
You can now use a python decompiler on the pyc files within the extracted directory
The script directly hints, that the next step will be to use a Pyhon decompiler and turn the obtained pyc
files into Python scripts.
It also hints about the possible entry point of the application. This information helps us to find where the code of our interest is located.
Step 2 – Decompiling the pyc
The produced output is stored in the directory corresponding to the name of the input executable. We can see there multiple modules extracted, but the interesting one seems to be the file called “black_box”:
The black_box is a pyc
file with a magic number removed, so we can just copy this part from some other pyc
file that we found in the extracted set, i.e.
Let’s paste it at the beginning of the black_box:
After this step we are ready to save it as black_box.pyc
and decompile:
And here is the result:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-black_box-py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# File: b (Python 2.7) | |
from time import sleep | |
from random import sample, getrandbits, randrange | |
from string import ascii_letters, digits | |
import sys | |
import random | |
import string | |
import hashlib | |
import base64 | |
import zlib | |
import locale | |
import binascii | |
import socket | |
from hashlib import sha256 | |
f = 'kukuriku' | |
k = 'elvis presley' | |
c = '789p8594po76n34n0p457s8947p85n0p0q31506093s02nn06654912r30p563o50q367p7q0o3o9qo6q37q6s061r182471246q1qq2o9138q8p3rpssp9rqnn246o638315o9s4oq3580n7o3oso71rr915o8p54n4o2o87s4s6q2pq8661868664pnp0o3853s443n1606907rsqro3oq670np348643s8o65778o5r248r0r7or5350r34p40714601snqps985255so43n278q69s582sq27n92554q919682n941rs05034r241pnqs1nrn40q44p147s73popp45268qqrns2o3os206q6s22ps6p441024r136n9373n32q965ss128p507sqpp78qo63orp2s9017p0379235ps8so96ns2n12169prn9nq7745snq47o71rs2173p39163pp45nn49p844o559p37sq390998267qn5n2712416qp7nq4n591s6867p8n5p9ro1062498678q88n1934s3788q8o7o8rrnr656ps162q72s0423277pp618roopr5q4np71q98p93o645qp6pp093n2sqn546s9171rqs59543rr9ns7n0p575r86p54n5626928320855139r672rrp449o601sn0ro69qpq5p8soppoorq6184590rn071303s6n32s82r857qn1n612q4or4pp8p40orpppn18nqr20279p90r30o12191nq4ns50oq11p8p271883srn3n655sn4177816pr659n3pqs6673so31qo5n985n4rnp0q7s1015q850s71psn99s3npsp01snps6516s4n8p16369r319oq2427628onrp882ooopr019r63q3pq4spnq559po661r2r250969r91rq57q4s6656n5op0n4q681qr3r8sn495555r40nqp27p98p199o5622ppq4q9q022s81p04r067op9151q38o6r65o7r399436r76ro6rsq87poo8557r89opq08s213rrqqsn3sn26q652412ss5r9s6308q612r8471r68po780roo2o22o9661q964824o754750s305sr0rr52oqss6367q7oo32o9rp05770p5ps9813r6324so7575068q4on9r833o1s4nr4ps1p2r6662p6pspo4pr9464or0487p0qs6289475o2o8r5rsnoosn5s751o15537q15343568nors5084sr8npq3o57r4r9s15rn3n0p0p803n35s668q55s00pr8sq6ro715r2sqs70ppqro6qs953nr9p44696n0o55sp9s04o8s9s496p6o23349op863838qrp5p5o1r4rs32297492p6s277r6r6sp303o673sp2or47qnr203qp411s61p308o76104pp5soqp08o9n87s9rs3n5sq016orn986o3o7s5qsp04q865q1o0nn8q1908877ro9pqqo979r460q60rr0210oopssopo32s3r023p9q79r994q32r2555nr8613nor196o3o0272o1sn95s95o698686q0pp0r04p324o2rr0o689s9p483p880592627780s376r1pn8120n98610571po9n7ooo593195r03s2495no223q037361p3qn950op4qq0s4so9q77orq05ss66r7ossqrp1n77rs63qs316nso13o1s19sso07060349qs86s1ss31r8poo9r2nss11530q17ss6688o656525078s784s75q95557nsoo4p54395qrs8no43ns8483sn0os39npn1s7nn38qs6op347n63po2s6p55o33q6rn23993sor0q77009679n44n39q09n0sos00070p6111' | |
exec base64.b64decode(zlib.decompress(binascii.unhexlify(c.decode('rot13')))) | |
g = 'lsdjfiownv9037la1sdf10' | |
p = 'byebye' |
The file got decompiled properly, and at this point we can rename it to py and run like any other Python script. We can see the same prompt as it was in the EXE:
Looking at the code we can see that is is mildly obfuscated. The important part is hidden in the variable “c” that is obfuscated by ROT13 and compressed by ZLIB:
c = '789p8594po76n34n0p457s8947p85n0p0q31506093s02nn06654912r30p563o50q367p7q0o3o9qo6q37q6s061r182471246q1qq2o9138q8p3rpssp9rqnn246o638315o9s4oq3580n7o3oso71rr915o8p54n4o2o87s4s6q2pq8661868664pnp0o3853s443n1606907rsqro3oq670np348643s8o65778o5r248r0r7or5350r34p40714601snqps985255so43n278q69s582sq27n92554q919682n941rs05034r241pnqs1nrn40q44p147s73popp45268qqrns2o3os206q6s22ps6p441024r136n9373n32q965ss128p507sqpp78qo63orp2s9017p0379235ps8so96ns2n12169prn9nq7745snq47o71rs2173p39163pp45nn49p844o559p37sq390998267qn5n2712416qp7nq4n591s6867p8n5p9ro1062498678q88n1934s3788q8o7o8rrnr656ps162q72s0423277pp618roopr5q4np71q98p93o645qp6pp093n2sqn546s9171rqs59543rr9ns7n0p575r86p54n5626928320855139r672rrp449o601sn0ro69qpq5p8soppoorq6184590rn071303s6n32s82r857qn1n612q4or4pp8p40orpppn18nqr20279p90r30o12191nq4ns50oq11p8p271883srn3n655sn4177816pr659n3pqs6673so31qo5n985n4rnp0q7s1015q850s71psn99s3npsp01snps6516s4n8p16369r319oq2427628onrp882ooopr019r63q3pq4spnq559po661r2r250969r91rq57q4s6656n5op0n4q681qr3r8sn495555r40nqp27p98p199o5622ppq4q9q022s81p04r067op9151q38o6r65o7r399436r76ro6rsq87poo8557r89opq08s213rrqqsn3sn26q652412ss5r9s6308q612r8471r68po780roo2o22o9661q964824o754750s305sr0rr52oqss6367q7oo32o9rp05770p5ps9813r6324so7575068q4on9r833o1s4nr4ps1p2r6662p6pspo4pr9464or0487p0qs6289475o2o8r5rsnoosn5s751o15537q15343568nors5084sr8npq3o57r4r9s15rn3n0p0p803n35s668q55s00pr8sq6ro715r2sqs70ppqro6qs953nr9p44696n0o55sp9s04o8s9s496p6o23349op863838qrp5p5o1r4rs32297492p6s277r6r6sp303o673sp2or47qnr203qp411s61p308o76104pp5soqp08o9n87s9rs3n5sq016orn986o3o7s5qsp04q865q1o0nn8q1908877ro9pqqo979r460q60rr0210oopssopo32s3r023p9q79r994q32r2555nr8613nor196o3o0272o1sn95s95o698686q0pp0r04p324o2rr0o689s9p483p880592627780s376r1pn8120n98610571po9n7ooo593195r03s2495no223q037361p3qn950op4qq0s4so9q77orq05ss66r7ossqrp1n77rs63qs316nso13o1s19sso07060349qs86s1ss31r8poo9r2nss11530q17ss6688o656525078s784s75q95557nsoo4p54395qrs8no43ns8483sn0os39npn1s7nn38qs6op347n63po2s6p55o33q6rn23993sor0q77009679n44n39q09n0sos00070p6111' exec base64.b64decode(zlib.decompress(binascii.unhexlify(c.decode('rot13'))))
To understand it better what happens here, we should dump the content after the deobfuscation, rather than executing it. In order to do so, I slightly modified the script. I removed the code responsible for executing the second stage, and substituted it with the function that writes the decompressed result into a file. This is my modified version:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-black_box_patched-py
Now, once we execute this script, we get the next stage dumped. And this is how it looks:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-decoded-py
from hashlib import sha256 from time import sleep import socket, sys PASSWORD = "36949" HASH = sha256(PASSWORD).hexdigest() USER = 'Nigel' CODE = "807290" IPADDR = '104.25.199.31' PORT = 587 def login(): print "" username = raw_input("Enter First Name: ") if username.rstrip(' \n\t') != USER: print "Go away! You are not me..." sys.exit() print "Hello %s, Good to see you!" % USER while True: password_guess = raw_input("Enter 5-digit password: ") print "[DEBUG]: calculating sha-256 hash" print "[DEBUG]: comparing with %s's hash: %s" % (USER, HASH) print "[DEBUG]: performing anti-brute-force delay..." sleep(5) if sha256(password_guess).hexdigest() == HASH: print "Password OK!" break else: print "Wrong password!" while True: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) s.connect((IPADDR, PORT)) s.send(str(CODE).decode('hex')) s.close() print "%s, two-factor authentication is required. A one-time code was sent to your email address" % USER code_guess = raw_input("Enter code: ") sleep(5) if code_guess == CODE: print "Success! The code is what you're looking for :)" break else: print "Wrong code!" login()
The script is not further obfuscated.
Once we read it, it’s pretty straight-forward what to do next. So, the username was Nigel. Then, we have to give his password that is 36949 and finaly his code: 807290. This was my final conversation with the crackme confirming that the code is valid.
python decoded.py
Output:
Enter First Name: Nigel
Hello Nigel, Good to see you!
Enter 5-digit password: 36949
[DEBUG]: calculating sha-256 hash
[DEBUG]: comparing with Nigel's hash: 6912863904dab1ddc332a928bf6df7f365bf1131906f3424aa931c6c85595c34
[DEBUG]: performing anti-brute-force delay...
Password OK!
Nigel, two-factor authentication is required. A one-time code was sent to your email address
Enter code: 807290
Success! The code is what you're looking for :
Exactly the same results we get when we talk with the original EXE:
So, the final answer is 807290.
Conclusion
This crackme can be solved very easily if we know the few tricks. The most important was to find what are the proper tools to be applied. Once we got them, we could easily decompile the code and read the answer.
Appendix
- “TelegramRAT evades traditional defenses via the cloud” – example of a malware using similar packaging
Nice blog with excellent information. Thank you, keep sharing.
I have extracted compiled files from exe using Pyinstaller Extractor script.
In resultant folder, i have got were pyc files bundled in pyz folder. Then i decomplied files using uncompyle6 from pyc to py. Then updated code in py files and recompiled back to pyc files.
My question is how can i convert these pyz bundled folder back to ‘ex’ so that it can be executed in windows?
I tried pyinstaller , but could not find any spec file or entry py file to begin this process. What are the steps to achieve this required result.
This is the snapshot of my extracted exe files folder.
gui
pyi_rth__tkinter
pyi_rth_multiprocessing
pyi_rth_pkgres
pyi_rth_win32comgenpy
pyiboot01_bootstrap
pyimod01_os_path
pyimod02_archive
pyimod03_importers
PYZ-00.pyz
struct
PYZ-00.pyz_extracted
CMD Output when files were generated:
Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: pyi_rth_multiprocessing
[+] Possible entry point: pyi_rth_pkgres
[+] Possible entry point: pyi_rth_win32comgenpy
[+] Possible entry point: pyi_rth__tkinter
[+] Possible entry point: gui
i tried magic code trick , but not successful. Please guide me in the right direction.
Thanks a lot for looking into this.
Hello Sir, i followed your tutorial but i got stuck at one point.
I have successfully carried out these steps:
1. extracted pyz bundle from exe file.
2. decompiled the pyc files in pyz folder (over 1000 files) to py source code
3. updated code in the required files and compiled those files back to pyc
4. Now i am trying to open the “gui” and copied the header of other file to this and saved as gui.pyc
But i am not able to decompile this file using this command
uncompyle6 gui.pyc
as i am getting this error:
Traceback (most recent call last):
File “c:\python27\lib\site-packages\xdis\load.py”, line 192, in load_module_fr
om_file_object
co = marshal.loads(bytecode)
ValueError: bad marshal data (unknown type code)
Traceback (most recent call last):
File “c:\python27\lib\runpy.py”, line 174, in _run_module_as_main
“__main__”, fname, loader, pkg_name)
File “c:\python27\lib\runpy.py”, line 72, in _run_code
exec code in run_globals
File “C:\Python27\Scripts\uncompyle6.exe\__main__.py”, line 9, in
File “c:\python27\lib\site-packages\uncompyle6\bin\uncompile.py”, line 179, in
main_bin
**options)
File “c:\python27\lib\site-packages\uncompyle6\main.py”, line 248, in main
linemap_stream, do_fragments)
File “c:\python27\lib\site-packages\uncompyle6\main.py”, line 148, in decompil
e_file
source_size) = load_module(filename, code_objects)
File “c:\python27\lib\site-packages\xdis\load.py”, line 107, in load_module
fast_load=fast_load, get_code=get_code)
File “c:\python27\lib\site-packages\xdis\load.py”, line 205, in load_module_fr
om_file_object
% (filename, kind, msg))
ImportError: Ill-formed bytecode file gui.pyc
; bad marshal data (unknown type code)
Sir any help would be appreciated.
Thanks a lot.
here is directory of my complete project :
https://www.dropbox.com/sh/uaw6zukoil7yra8/AAD_JIP2EHR54-SNOX2ATgsXa?dl=0
Hello sir,
i have successfully decompiled the main entry file. e.g. black_box file in this example. Now i want to create exe file so that it can contain files from PYZ-00.pyz_extracted folder into exe itself.
What are the commands to perform this operation.
Looking forward to your reply.
Pingback: BYOB (Build Your Own Botnet) in action. - Perception Point
Pingback: (Python) pyinstxtractor does not produce any .py or .pyc files – how to convert the blank file to a .pyc? – Ask python questions
HI,
This would be genuinely valuable information Thank you very much for sharing.