Skip to content

PyInstaller Binaries

Stealers, RATs and loaders written in Python ship as PyInstaller EXEs: a small bootloader with the whole program appended. Look at the EXE alone and you see a generic C loader. The malware is in the archive, and PPEE opens it for you: which Python, which script runs, and everything that was bundled with it.

How PPEE detects PyInstaller

PyInstaller ends the file with a cookie, MEI\x0C\x0B\x0A\x0B\x0E, followed by the archive's size, where its table of contents is and the Python version. PPEE finds the cookie in the last 4 KB of the file; the Detected line gives its offset.

Everything is read from the table of contents. Nothing is decompressed, extracted or run.

Views

View Shows Use it to
Summary Python version and library, where the archive is, what it holds, the scripts run at start Find the one script that matters
Entries Every entry: name, kind, size, stored size, storage, file offset See what was bundled; each row opens its bytes in the hex view

Entry kinds: script (run at start), module, package, PYZ archive (the program's other modules), binary (DLL/PYD), data file, runtime option, dependency.

Real sample: a Python malware sample

At a glance

Sample
e21e0977284f9eacbb69e04b132f923586837d2b405cee03bdca81d2944bf4c5.exe (18 MB)
Question
Which Python script is the program, and what did it bundle?
You'll use
Analysis → PyInstaller → Summary and Entries
$ ppee-cli --analysis e21e0977….exe
PyInstaller
  Detected: PyInstaller archive cookie (MEI\x0C\x0B\x0A\x0B\x0E) at offset 0x1265B3B, near the end of the file

PyInstaller > Summary  (built from the archive cookie and table of contents)
Python
  Version: 3.10
  Library: python310.dll
  Cookie: PyInstaller 2.1 or later

Archive
  Location: 0x44A00, 18.1 MB [offset 0x44A00]
  Entries: 170 [Analysis > PyInstaller > Entries]
  Contents: 1 PYZ archive (bundled modules), 153 binary (DLL/PYD), 5 module, 1 runtime option, 10 script (run at start)

Scripts run at start
  The program's: main
  PyInstaller's: pyiboot01_bootstrap, pyi_rth_inspect, pyi_rth_multiprocessing, pyi_rth_setuptools, pyi_rth_pkgutil,
                 pyi_rth_cryptography_openssl, pyi_rth_pythoncom, pyi_rth_pkgres, pyi_rth_pywintypes
PS C:\> ppee-cli.exe --analysis C:\MalwareSamples\e21e0977284f9eacbb69e04b132f923586837d2b405cee03bdca81d2944bf4c5.exe.sample
C:\MalwareSamples\e21e0977284f9eacbb69e04b132f923586837d2b405cee03bdca81d2944bf4c5.exe.sample: 19291319 bytes, PE32+

Analysis (derived views, not PE structures):

PyInstaller
  Detected: PyInstaller archive cookie (MEI\x0C\x0B\x0A\x0B\x0E) at offset 0x1265B3B, near the end of the file

PyInstaller > Summary  (built from the archive cookie and table of contents)
Python
  Version: 3.10
  Library: python310.dll
  Cookie: PyInstaller 2.1 or later

Archive
  Location: 0x44A00, 18.1 MB [offset 0x44A00]
  Entries: 170 [Analysis > PyInstaller > Entries]
  Contents: 1 PYZ archive (bundled modules), 153 binary (DLL/PYD), 5 module, 1 runtime option, 10 script (run at start)

Scripts run at start
  The program's: main
  PyInstaller's: pyiboot01_bootstrap, pyi_rth_inspect, pyi_rth_multiprocessing, pyi_rth_setuptools, pyi_rth_pkgutil, pyi_rth_cryptography_openssl, pyi_rth_pythoncom, pyi_rth_pkgres, pyi_rth_pywintypes
  Each script and module is stored as compiled Python bytecode (zlib-compressed when Storage says so); the PYZ archive holds the rest of the program's modules.

PyInstaller > Entries (170)  (built from the archive's table of contents)
Name                                                   Kind                           Size     Stored size  Storage  File offset
struct                                                 module                         271      206          zlib     0x44A00  [offset 0x44A00]
pyimod01_archive                                       module                         3125     1850         zlib     0x44ACE  [offset 0x44ACE]
pyimod02_importers                                     module                         22887    9689         zlib     0x45208  [offset 0x45208]
pyimod03_ctypes                                        module                         3620     1769         zlib     0x477E1  [offset 0x477E1]
pyimod04_pywin32                                       module                         1053     634          zlib     0x47ECA  [offset 0x47ECA]
pyiboot01_bootstrap                                    script (run at start)          838      595          zlib     0x48144  [offset 0x48144]
pyi_rth_inspect                                        script (run at start)          1503     872          zlib     0x48397  [offset 0x48397]
pyi_rth_multiprocessing                                script (run at start)          1069     673          zlib     0x486FF  [offset 0x486FF]
pyi_rth_setuptools                                     script (run at start)          717      468          zlib     0x489A0  [offset 0x489A0]
pyi_rth_pkgutil                                        script (run at start)          910      591          zlib     0x48B74  [offset 0x48B74]
pyi_rth_cryptography_openssl                           script (run at start)          273      229          zlib     0x48DC3  [offset 0x48DC3]
pyi_rth_pythoncom                                      script (run at start)          264      214          zlib     0x48EA8  [offset 0x48EA8]
pyi_rth_pkgres                                         script (run at start)          4516     2100         zlib     0x48F7E  [offset 0x48F7E]
pyi_rth_pywintypes                                     script (run at start)          265      211          zlib     0x497B2  [offset 0x497B2]
main                                                   script (run at start)          19400    19416        zlib     0x49885  [offset 0x49885]
Crypto\Cipher\_ARC4.pyd                                binary (DLL/PYD)               10752    4768         zlib     0x4E45D  [offset 0x4E45D]
Crypto\Cipher\_Salsa20.pyd                             binary (DLL/PYD)               13824    6270         zlib     0x4F6FD  [offset 0x4F6FD]
Crypto\Cipher\_chacha20.pyd                            binary (DLL/PYD)               13312    6193         zlib     0x50F7B  [offset 0x50F7B]
Crypto\Cipher\_pkcs1_decode.pyd                        binary (DLL/PYD)               13312    5774         zlib     0x527AC  [offset 0x527AC]
Crypto\Cipher\_raw_aes.pyd                             binary (DLL/PYD)               35328    17910        zlib     0x53E3A  [offset 0x53E3A]
  ...

Windows screenshot: PyInstaller Summary: Python 3.10, the 18.1 MB archive at 0x44A00, 170 entries, and main set apart from PyInstaller's own scripts

PyInstaller Summary: Python 3.10, the 18.1 MB archive at 0x44A00, 170 entries, and main set apart from PyInstaller's own scripts

In the GUI, the navigator strip above the Summary already shows the shape of the file: a thin PE at the left, then one long band for the appended archive. The location (0x44A00) and every script name are links.

How to read it:

  • main is the program. Ten scripts run at start, but nine are PyInstaller's own bootstrap and runtime hooks (pyiboot*, pyi_rth_*), the same in every build. PPEE sets them apart so you go straight to the program's script.
  • The runtime hooks tell you the libraries: cryptography_openssl, pythoncom and pywintypes mean the program uses crypto and Windows COM.
  • The Entries view confirms it: Crypto\Cipher\_raw_aes.pyd, _chacha20.pyd, _Salsa20.pyd, _ARC4.pyd (PyCryptodome) are bundled, so the program encrypts or decrypts data itself: worth checking in main.
PyInstaller > Entries (170)  (built from the archive's table of contents)
Name                         Kind                   Size     Stored size  Storage  File offset
main                         script (run at start)  19400    19416        zlib     0x49885
Crypto\Cipher\_ARC4.pyd      binary (DLL/PYD)       10752    4768         zlib     0x4E45D
Crypto\Cipher\_Salsa20.pyd   binary (DLL/PYD)       13824    6270         zlib     0x4F6FD
Crypto\Cipher\_chacha20.pyd  binary (DLL/PYD)       13312    6193         zlib     0x50F7B
Crypto\Cipher\_raw_aes.pyd   binary (DLL/PYD)       35328    17910        zlib     0x53E3A
…

Windows screenshot: PyInstaller Entries view: bootstrap modules, the runtime-hook scripts, main, and the bundled PyCryptodome cipher modules

PyInstaller Entries view: bootstrap modules, the runtime-hook scripts, main, and the bundled PyCryptodome cipher modules

The Entries view in table order: PyInstaller's modules, then the ten scripts run at start with main last (19,400 bytes), then the bundled binaries starting with the PyCryptodome ciphers. Each row opens its bytes in the hex view.

Next step

The main row gives the script's offset and stored size. Decompile it with a Python bytecode tool of your choice (the Python version above tells you which one fits). PPEE doesn't decompile; an assistant can read the decompressed script in memory through its #entry:main layer.

In triage and MCP

  • triage_pe shows the same facts under runtimeAnalysis, and overlay.detectedAs reads PyInstaller archive (MEI cookie at the end).
  • list_container lists the entries with filtering and paging, says what each holds (content, and mismatch when the name says otherwise), and gives each a path (file.exe#entry:NAME) that any tool opens, decompressed, in memory. Ask an assistant: "Which Python script does e21e0977….exe run, and which crypto libraries does it bundle?"

References