Jump to content
Tuts 4 You

[HelpMe] pyArmor Obfuscated Malware


Recommended Posts

Somebody has any suggestion for decompiling pyArmor Obfuscated code (main.pyc)?

I have not experience in python decompiling.

Someone attacked our entities with this malware and I want to study the actual malicious code.


You can download this malware at ...



Malware Source:




Link to comment
Share on other sites

Extreme Coders

Some observations about the sample:

  1. Python version used => 3.7.8
  2. Pyarmor version => r40.14
  3. Pyarmor mode => Super Mode
  4. The sample uses PyInstaller encryption with key "SvbQ0ZZC5HTuwipn". Pyc files inside the pyz directory can be decrypted by following the steps here (the first snippet which uses AES CFB mode)
  5. Other than main.pyc, the files of interest are (AutoRun, Browsers, ComMod, Config, FileManager, Flash, Installer, Rerun, SNS, Sandbox, Screenshot, Shell, StringEncode, WiFi, Zip) which can be found in the outpyz directory
  6. The sample doesn't use the binding feature of PyArmor, Hence the techniques linked in the above comment can be directly applied. It's possible to replace the Python37.dll with our own, compiled from source. However make sure to use Python 3.7.8 as mentioned in (1)
  7. Because of (6) you can also import the individual pyc files (5) in a Python 3.7 REPL to have a look around. However make sure to do this in a VM with network disabled.

    Python 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)] on win32
    >>> import Sandbox
    >>> dir(Sandbox)
    ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'check', 'pyarmor', 'wmi']

    >>> import ComMod
    >>> dir(ComMod)
    ['Log', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'b264decode', 'b264encode', 'b64decode', 'b64encode', 'base64decode', 'base64encode', 'compress', 'decode', 'decompress', 'dumps', 'encode', 'get_time', 'loads', 'os', 'pyarmor', 'time']

    >>> import FileManager
    >>> dir(FileManager)
    ['ComMod', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'allDisk', 'curpath', 'delete', 'download', 'dumps', 'exists', 'files', 'getsize', 'isdir', 'isfile', 'listdir', 'localtime', 'pyarmor', 'remove', 'requests', 'stat', 'strftime', 'upload']

    >>> import Screenshot
    >>> dir(Screenshot)
    ['ComMod', 'CreateProcessAsUser', 'NORMAL_PRIORITY_CLASS', 'STARTUPINFO', 'Shell', 'WTSQueryUserToken', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'b64decode', 'b85decode', 'os', 'pyarmor', 'randint', 'requests', 'run_as_active_session_user', 'sc', 'sleep', 'sys', 'windll']

    >>> import WiFi
    >>> dir(WiFi)
    ['Shell', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'get_nearby_wifi', 'get_saved_wifi_list', 'get_wifi_info', 'pyarmor']

    >>> import Browsers
    >>> dir(Browsers)
    ['ChromeDecode', 'Cipher', 'CryptUnprotectData', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'algorithms', 'base64', 'browsers', 'copyfile', 'ctypes', 'datetime', 'default_backend', 'json', 'modes', 'os', 'pyarmor', 'sqlite3']

    >>> import main
    >>> dir(main)
    ['AutoRun', 'Browsers', 'C', 'ComMod', 'Config', 'FileManager', 'Flash', 'Installer', 'Rerun', 'SNS', 'Sandbox', 'Screenshot', 'Shell', 'StringEncode', 'Thread', 'WiFi', 'Zip', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'ctypes', 'dumps', 'freeze_support', 'loads', 'os', 'protect_pytransform', 'pyarmor', 'random', 'requests', 'sleep', 'sys', 'whnd', 'win32api', 'win32con']


  8. While you can dump the code objects from memory by following (6) it will not be directly decompilable as in super mode, the opcodes are remapped. Infact, Pyarmor has it's own implementation of the PyEval_EvalFrameDefault in pytransform.pyd function which executes the remapped opcodes.
  9. Additionally, in super modes the co_consts of each code object are moved to be the top level code object and stored in a tuple. The co_const is then replaced with integer indicating the index in the tuple where the code object was moved. the At runtime, pyarmor automatically refers to the correct code_object by following the index.
  10. Reconstructing the original pyc file by undoing (9) is a lot easier than (8). Depending on what you're looking for it may be enough to just dump the code object to get readable strings in addition to running the individual pyc files in a REPL.
  • Like 4
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Create New...