Reverse pyarmor obfuscated python script using memory dump technique
Obfuscated source code is unreadable for humans but not for memory
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
- Use Linux environment (memory dump script depends on /proc filesystem)
- Python3
- 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
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...