conda create -n uncompile python=3.6
conda activate uncompile
# We will use pyinstxtractor-ng
# git clone https://github.com/countercept/python-exe-unpacker.git
#cd python-exe-unpacker
#python pyinstxtractor.py ~/Downloads/Aimful/aimful-kucoin.exe
git clone https://github.com/pyinstxtractor/pyinstxtractor-ng.git
pyinstxtractor-ng --one-dir main.exe
cd main.exe_extracted
pip install uncompyle6
uncompyle6 -o output *.pyc
cd output && ls -lah

Reverse engineering

Basic information about binaries. There are two main versions of the program in question: aimful-kucoin.exe and aimful-binance.exe. They are both Windows executables. From the FAQ section of the discord server, the following information is available:

In what language was this bot written?

  • Python.
  1. Extract the contents of the binary
git clone https://github.com/countercept/python-exe-unpacker.git
cd python-exe-unpacker
python pyinstxtractor.py ~/Downloads/Aimful/aimful-kucoin.exe

Install python decompiler called decompyle3 based on uncompyle6:

git clone https://github.com/rocky/python-decompile3.git
cd python-decompile3
pip install -e .
  1. Attempt to decompile the contents of the binary
cd ./aimful-kucoin.exe_extracted/PYZ-00.pyz_extracted
decompyle3 ./kucoin.client.pyc
 
> ...
> ImportError: Ill-formed bytecode file ./kucoin.client.pyc
> <class 'ValueError'>; bad marshal data (unknown type code)

By searching for the file header “e3000000” on google, we can find this article: https://timonpeng.com/tips-of-pyinstaller-executable-file-decompilation/

  1. Inspect the struct file header bytes
xxd < struct | head -5

Output:

00000000: 420d 0d0a 0000 0000 7079 6930 1001 0000  B.......pyi0....
00000010: e300 0000 0000 0000 0000 0000 0008 0000  ................
00000020: 0040 0000 0073 3800 0000 6400 6401 6402  .@...s8...d.d.d.
00000030: 6403 6404 6405 6406 6407 6708 5a00 6408  d.d.d.d.d.g.Z.d.
00000040: 6409 6c01 5400 6408 640a 6c01 6d02 5a02  d.l.T.d.d.l.m.Z.

Mainly we are interested in the first 16 bytes:

420d 0d0a 0000 0000 7079 6930 1001 0000

You can find that the first byte of the main program is e3, therefore, the contents before e3 in the struct file are filled to the front of the main program file.

cp ./aiumful-kucoin ./aimful-kucoin.pyc
 
set pyc_file ./aimful-kucoin.pyc
 
# pad file with extra 4 bytes at the beginning
printf '\x00\x00\x00\x00' > $pyc_file.new
cat $pyc_file >> $pyc_file.new
mv $pyc_file.new $pyc_file
# replace the binary header with the 16 bytes above
printf '\x42\x0d\x0d\x0a\x00\x00\x00\x00\x70\x79\x69\x30\x10\x01\x00\x00' | dd of=$pyc_file bs=1 seek=0 count=16 conv=notrunc
  1. Attempt to decompile the binary
decompyle3 ./aimful-kucoin.pyc

Output:

# decompyle3 version 3.7.6
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.12 (default, Oct  9 2021, 17:28:41)
# [Clang 12.0.0 (clang-1200.0.32.29)]
# Embedded file name: dist\obf\aimful-kucoin.py
# Compiled at: 1995-09-27 10:18:56
# Size of source mod 2**32: 272 bytes
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'PYARMOR\x00\x00\x03\x07\x00B\r\r\n\x06*\xa0\x01\x00\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x94\xa5\x01\x00\x00\x00\x00\x18>\x11
... binary obfuscated ...

From here we can deduce that the binary is obfuscated with the pytransform library using pyarmor_runtime(). Quick search reveals there are some tools that can de-obfuscate the binary data.

git clone https://github.com/u0pattern/PyArmor-Deobfuscator.git
# copy PyArmorDeobfuscator.py to the working directory
# ...
 
decompyle3 ./aimful-kucoin.pyc > ./aimful-kucoin-obf.py
pip install uncompyle6
python ./PyArmorDeobfuscator.py -f ./pyimful-kucoin-obf.py -o ./aimful-kucoin.py

Output is:

[-] _pytransform.dll file not found [-]

At this point we need to start looking at the source code of the decompiler script. From the comment in the source code we can see that we need the following:

# please make sure you have _pytransform.dll and __init__.py in /dist/pytransform/ directory !!!

https://forum.tuts4you.com/topic/41945-python-pyarmor-my-protector/?tab=comments#comment-203008 From another article online, I found that using the extracted files, we can create the following directory structure:

.
|-- some-python-bytecode.pyc
`-- pytransform
    |-- __init__.py
    |-- _pytransform.dll
    |-- _pytransform.dylib

1 directory, 5 files

The DLL was already included in the directory. For running on OSX I downloaded the pytransform library and placed it in the same directory as the script. https://pyarmor.dashingsoft.com/platforms.html

The contents of __init__.py are:

from pytransform import pyarmor_runtime
pyarmor_runtime('/path/to/runtime')

as per docs at https://pyarmor.readthedocs.io/en/latest/understand-obfuscated-scripts.html

  1. Another attempt
python ./PyArmorDeobfuscator.py -f ./pyimful-kucoin-obf.py -o ./aimful-kucoin.py

Output is:

ImportError: File name: './aimful-kucoin-obf.pyc' doesn't exist

Rename aimful-kucoin.pyc to aimful-kucoin-obf.pyc and re-run the script. The output is the same as the previous one, with a slight difference because this time we are using uncompyle6 to decompile the file.

# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.12 (default, Oct  9 2021, 17:28:41) 
# [Clang 12.0.0 (clang-1200.0.32.29)]
# Embedded file name: dist\obf\aimful-kucoin.py
# Compiled at: 1995-09-27 10:18:56
# Size of source mod 2**32: 272 bytes
from pytransform import pyarmor_runtime
pyarmor_runtime()
__pyarmor__(__name__, __file__, b'PYARMOR\x00\x00\x03\x07\x00B\r\r\n\x06*\xa0\x01\x00\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x94\xa5\x01\x00\x00\x00\x00\x18>\x11\xb8\n\x00\x0
... binary obfuscated ...