Ooh, shiny

Around Marchuary 2023 Malware Unicorn wrote about her journey with an infostealer that her niece had caught. The original story got deleted forever ago so my summary must do. So, her niece had downloaded a "Minecraft hack", but instead got an infostealer. MU eventually got a copy of the "hack" and started to reverse engineer it. It turned out to be python3.exe + Python script compiled as an exe. Well, a .pyc, but those can be decompiled quite easily. The script collects bunch of stuff and then uploads them to a Discord webhook. According to comments you can send DELETE query to webhook and it'll cease to exist, rendering the infostealer fairly harmless. The story doesn't tell if she ever deleted the webhook, but she did identify the person (these kids have really poor opsec...) and file a police report. Not really because of the infostealer, but because the skid had also tried to swat her niece.

Sounds like fun (..until the police report), I want to play too.

Gathering sample

Fast start would be getting a sample or three from e.g VX Underground, but I don't want to waste time with something that is already inactive for some reason. I.e I want a live case. I joined a bunch of Minecraft Discords listed on Disboard.org for shits and giggles with a throwaway Discord account, but never really bothered to check in if I had gotten any DMs. I even tried to search Youtube for Minecraft hacks, but I didn't find anything sufficiently shady. Goddammit, I want shady mega.nz links, not semi-professional stuff with their own domains. Roblox seemed to have slightly shadier results, but I wasn't in the mood of trying those.
Then one evening I saw this on a Discord I'm fairly active at:


Cool, live sample, right on my door step. I was never given a proper sample, though, just "It's X, they're all the same". Google gives mostly public sandbox runs (sample available for paying customers) and Reddit thread for getting rid of the malware and stuff. Eventually I find a link to Abuse.ch MalwareBazaar which has link to "try my game" Blogspot and also a sample. Finally.

Static analysis

Just in case, launch a VM to someone else's computer, download the sample there, get to work. binwalk -e gets me quite a lot of files, mostly Javascript-shaped. I wanted Python, first step of power fantasy broken. :( Practically all the JS files are publicly available fairly common library files, but then there was few files referred to as "jsc" which were not ASCII. So some kind of NodeJS equivalent of pyc. First good suggestions in Google are nexe-decompile and nexe_unpacker. One fails immediataly and the other never finishes. While looking for the sample I had found the presumed developer's Github (again, poor opsec) where they have forked Vercel/pkg for some reason. So maybe the script was packed to .exe with pkg too. Google finds pkg-unpacker and unpkg. The first one fails, the latter required a bit of bubblegum until it accepted the .exe (it threw an error, commented out few lines, success). But still no human-readable code of the Good Stuff.

So I guess I have to find a decompiler for the bytecodenode. I hoped this would be somewhat as doable as Java, but no such luck. The only solution seemed to be ghidra_nodejs which I have to build myself and which may work only on specific version of NodeJS. Long story short, I managed to get Ghidra load the plugin, I think. It somewhat recognized that the .jsc may be something that could be decompiled but nothing really happened.

Also no Discord webhook in sight in plain view. Or any good C2 shaped URL.

Dynamic analysis

Running out of ideas which are not shaped like "run the .exe". For shits and giggles I tried running it in wine in the cloud VM. Don't know what I was hoping to achieve, but it doesn't matter because it failed to launch. It wanted a DLL I didn't have (Ubuntu 22.04 had too old Wine) and when retrying in 22.10 I got complaints about running too old Windows version because NodeJS no longer supports Win7. That complaint can be disabled but then it crashes on something else.

W7 - Security by obsolence

I have Win7 installation for fake tech support calls. Dropped the exe into that, still didn't get it to run.

Funnily enough, I did get a call from "tech support" a few weeks earlier, and he mentioned the "really old Windows version" too. W7 isn't that old. :(

Side quest: Cuckoo

So anyway, if I'm going to run the exe, I want proper logging of what's happening. While there's the public sandboxes, all of them hide the Good Stuff (dropped files, pcaps) behind paywall, priced at "contact sales". So I need my own sandbox. Cuckoo at least used to be very popular, but they never made it out of Python2 and now the project is inactive. There's few alternatives, such as FAME and DRAKVUF but neither were really shaped for KVM/libvirt, either the support was completely missing or it was described as "works, maybe, depends on star alignments on a good day". Fortunately Cert-EE has picked up the ball and is working on Cuckoo 3. Don't know how it compares to the original, but I take it. Setting it up requires a bit of work because all the required docs are all over the place, and also because the libvirt module doesn't actually start the VM (maybe I should submit a PR, but sounds a lot of work for one line). Also when the agent doc says "must be this exact Win10 build", it means that. First I tried with newest Win10 ISO, but after running the patcher it fails to boot because the kernel is corrupted. Fortunately the agent docs link to good ISO. Eventually I get a successful run with a report.

Half of the stuff is actually Powershell doing Powershell things, because why wouldn't Microsoft drop bunch of red herrings to DFIR people. Especially the habit of creating randomly named DLL is my favourite.

Side quest: PS logging

The execution seems to stop abruptly, I see that it runs Powershell, but the report doesn't say what actually happened. -Command - means "input comes from stdin", and is therefore not logged on the report),. But it's okay, turning on PS logging is fairly easy with GPOs. But still nothing juicy, it only collects data about the computer, such as UUID and PC vendor and stuff. And then it exits.

I had hidden the virtualization flags already in libvirt, but the execution continued only after I switched vendor from QEMU to Lenovo.

        <sysinfo type="smbios">
        <bios>
          <entry name="vendor">LENOVO</entry>
        </bios>
        <system>
          <entry name="manufacturer">LENOVO</entry>
          <entry name="product">Thinkpad</entry>
          <entry name="version">Carbon</entry>
        </system>
        <baseBoard>
          <entry name="manufacturer">LENOVO</entry>
          <entry name="product">20BE0061MC</entry>
          <entry name="version">0B98401 Pro</entry>
          <entry name="serial">W1KS427111E</entry>
        </baseBoard>
        <chassis>
          <entry name="manufacturer">LENOVO</entry>
          <entry name="version">2.12</entry>
          <entry name="serial">65X0XF2</entry>
          <entry name="asset">40000101</entry>
          <entry name="sku">Type3Sku1</entry>
        </chassis>
        </sysinfo>

Side quest: Fake Internet

There's no way I'm letting any malware to talk to the Internets, not from my IP (not even prepaid), not until I know exactly what's going to happen. What would even be the value in that if I can't intercept the traffic. So I run the VM in isolated network, it can only reach the host which runs DNS that returns "A <host.ip>" to everything, with very minimal Apache and mitmproxy. I also looked into installing inetpub or Fakenet-NG, but neither really added value to what I had, so I didn't. I installed mitmproxy's CA to the VM but the damn thing ships with it's own CA bundle and refuses to connect to my very legit mallory.tk server.

RAM dump

However, even if the connection doesn't succeed, maybe there's already clues in memory about what it would do if it did succeed. So, virsh dump Win10VM memdump.mem --memory-only to dump VM memory. and volatility3 -f ../mallory.mem windows.pstree.PsTree and volatility3 -f ../mallory.mem windows.memmap.Memmap --pid 4468 --dump to get the process memory. And then skimmed through it with less. Didn't get much, but I did see a few instances of

    GET /socket.io/?id=...&EIO=4&transport=websocket HTTP/1.1
    Host: mallory.tk

Should've expected this from the socket.io library included in the JS files. In theory socket.io supports transport=polling for pure HTTP and transport=websocket, but unfortunately this one refuses to talk transport=polling, so I need something more than browser/curl to poke it. Regardless, no clues about what actually goes in the socket.

DoS potential

Socket.io server itself does have an occasional vulnerability. Github spams about those regularly because I haven't bothered to unsubscribe from the notices about a local student radio, which uses Socket.io among other stuff. What's even cooler is that the newest notification contains link to commit that fixes the vuln and commit message includes PoC.

But DoS is fairly boring and easy to patch if the dev happens to figure out how his toy breaks, so I didn't actually try this.

Giving debugger a stab

Usually malware do some kind of anti-debugger trickery, but why not give it a try anyway. First attempt was with windbg, which was painful to install on airgapped VM because Microsoft wants to force people to use MS Store. It also turned out to be rather crappy debugger. But it gave a clue that NodeJS uses OpenSSL to handle TLS (pausing the process usually happened in rc4_options, somehow, and Google says rc4_options is OpenSSL thing). Found x64dbg which looked better on screenshots, and it was awesome. Honestly, 30 minutes from starting x64dbg first time I've binary patched the infostealer and made it to accept mitmproxy. And this is how it happened:

Fake Socket.io server

So now that the infostealer talks to me, I can see what kind of data it sends. For a socket.io server I use some Python example script, which looks like it logs to console what kind of data it receives but I only got dis/connections. But it doesn't really matter because I can also tcpdump the traffic, since the local connection between Apache and the Python script is unencrypted. And the pcap shows us:

Another DoS idea which is hopefully more difficult to fix: Deflate's compression rate for /dev/zero is 1000:1, So one kilobyte of compressed zeros inflate to 1M of zeros. 1M to 1G... Although if (and probably it is) this is socket.io's feature, it probably has protection against this kind of attack.


So, a bit of a lackluster ending. No Discord webhooks, just a socket.io endpoint to troll. Some people enter false data to phishing sites, so I figured I could do same here. But writing the data generator is boring, so we'll see when I get that done.

Bonus: I chatted about infostealers at a LAN/demoscene party's Discord, two days later someone joined and offered "crosshair-master.rar". Our infostaler-discussion was the very previous discussion in that channel. Hit page up and you'd see my and someone else's offer to poke any fun exes. It was the exact kind of "powerfantasy.exe" I've been looking for. Unfortunately I was occupied by CSGO while this happened so I was late to the party and the webhook was already gone. Won't go further into the playthrough here because someone else has already done that, but in a nutshell: binwalk -e; strings|grep discord; optionally decompile pyc

.

Story continues: Playing with Fire 201