Solving a PyInstaller-compiled crackme

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:

pycrackme1

When we run in, it asks for a username:

enter_name

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

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”:

extracted_files

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.

get_hdr.png

Let’s paste it at the beginning of the black_box:

paste_hdr

After this step we are ready to save it as black_box.pyc and decompile:

decompiler

And here is the result:
https://gist.github.com/hasherezade/5c91433bc2461f59657921004c505e3e#file-black_box-py

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:

enter_py

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:
crackme1
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

About hasherezade

Programmer and researcher, interested in InfoSec.
This entry was posted in CrackMe, Tutorial and tagged , . Bookmark the permalink.

4 Responses to Solving a PyInstaller-compiled crackme

  1. Nice blog with excellent information. Thank you, keep sharing.

  2. RS Gamer says:

    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.

  3. RS Gamer says:

    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

  4. RS Gamer says:

    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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s