Reverse pyarmor obfuscated python script using memory dump technique

Obfuscated source code is unreadable for humans but not for memory

Liad Levy
3 min readNov 6, 2022

Creating programs in a high-level programming language such as Python is great, but when you try to hide source code, it may be difficult. I will show an easy way to reverse an obfuscated application using a memory dump technique and what information can be extracted.

Why obfuscate an application?

As a developer, sometimes you want to make the application unreadable to a human but still could be executable by a computer for a number of reasons. Hence, preventing undesired eyes from looking into your application code and getting insights into how your program works.

Setup

  1. Use Linux environment (memory dump script depends on /proc filesystem)
  2. Python3
  3. pip (install the third-party dependencies)

Installing PyArmor

To use PyArmor, you can install it through pip:

pip install pyarmor

Creating a basic web server

Simple web server using uvicorn and FastAPI modules

Basic obfuscation with PyArmor

Let’s first create a simple Python script named main.py:

pyarmor obfuscate main.py

This will create a dist folder with the pytransform directory that contains the secret and an obfuscated main.py. More information how it works could be found in the pyarmor docs

main.py after obfuscation

Creating the dump script

This script will create a dump file using a process id (PID)

# memdump.py#https://gist.githubusercontent.com/Dbof/b9244cfc607cf2d33438826bee6f5056/raw/aa4b75ddb55a58e2007bf12e17daadb0ebebecba/memdump.py#! /usr/bin/env python3
import sys
import re

if __name__ == "__main__":

if len(sys.argv) != 2:
print('Usage:', sys.argv[0], '<process PID>', file=sys.stderr)
exit(1)

pid = sys.argv[1]

# maps contains the mapping of memory of a specific project
map_file = f"/proc/{pid}/maps"
mem_file = f"/proc/{pid}/mem"

# output file
out_file = f'{pid}.dump'

# iterate over regions
with open(map_file, 'r') as map_f, open(mem_file, 'rb', 0) as mem_f, open(out_file, 'wb') as out_f:
for line in map_f.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
mem_f.seek(start) # seek to region start
print(hex(start), '-', hex(end))
try:
chunk = mem_f.read(end - start) # read region contents
out_f.write(chunk) # dump contents to standard output
except OSError:
print(hex(start), '-', hex(end), '[error,skipped]', file=sys.stderr)
continue
print(f'Memory dump saved to {out_file}')

Now let’s run the obfuscated server

Run the obfuscated server and extract the PID

python3 dist/main.py
# ....
# Started server process [104269]

after gaining the PID we can dump the process to a file that we can examine.

Dump the process

python memdump.py 104269

Then we need to convert the binary to readable strings

strings 104269.dump > process.dump

Now when we have a readable process dump we can extract lots of information we couldn’t get from just examining the obfuscated project

What can we extract?

  • Environment variables
PASSWORD=SecretPassword
LESSCLOSE=/usr/bin/lesspipe %s %s
TERM=xterm-256color
LESSOPEN=| /usr/bin/lesspipe %s
USER=root
DISPLAY=:0
SHLVL=1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
  • dist-packages (third-party modules)
/usr/local/lib/python3.8/dist-packages/pydantic
/usr/local/lib/python3.8/dist-packages/fastapi
/usr/local/lib/python3.8/dist-packages/uvicorn
  • Strings
CantHideStringPasswordFromMemory
EasyToDumpMe
  • Partial functions logics
__main__z
main:appz
0.0.0.0i
host
port
reload
debug
uvicorn

Conclusion

To wrap it up, PyArmor allows you to obfuscate your Python source code to make it hard for unwanted eyes to look into your program files, yet using memory dump techniques, it’s possible to extract lots of useful data to reverse the obfuscated code.

In Part 2 we will talk about how to hide unnecessary strings from a memory dumper so stay tuned...

--

--