An Analysis of the Kuluoz Malware
Dec 24, 2021 “A look at the old Kuluoz malware”#reverse engineering #malware #kuluoz #security
The Kuluoz malware family first emerged in 2012, so it’s relatively old and doesn’t use advanced techniques. Since it’s Christmas eve and everybody is home, I decided to unwrap a malware sample before everyone else unwraps presents tomorrow.
For this post we will use a Windows 32-bit executable which presents itself as an invoice from FedEx. The sample:
$ sha256sum FedEx_Label_ID_Order_83-27-4534US.exe
4f96daf417ddaba8faaf2b45fe5cc044a12f611875e99eef53255b11ded9668c
It even has a misleading icon, which I’m sure tricked a few boomers:
I originally downloaded this sample from kernelmode.info
, which identified it
as Kuluoz. The site is now offline unfortunately, but savy readers can figure
out where to obtain this file.
Static Analysis
Before running this malicious code, let’s try and reverse it using our brain.
Pre-Analysis
For pre-analysis, readpe
and pescan
fail to find anything interesting,
which isn’t unusual. Manalyze
failed to find anything as well, but maybe I
used it wrong.
The “cloud” tools fared better, and actually gave me hits: here are the Hybrid Analysis and VirusTotal reports.
To its credit, ClamAV raised the alarm:
$ clamscan FedEx_Label_ID_Order_83-27-4534US.exe.disabled
Loading: 12s, ETA: 0s [========================>] 8.59M/8.59M sigs
Compiling: 2s, ETA: 0s [========================>] 41/41 tasks
/home/cam/sandbox/FedEx_Label_ID_Order_83-27-4534US.exe.disabled: Win.Trojan.Dapato-1688 FOUND
----------- SCAN SUMMARY -----------
Known viruses: 8585273
Engine version: 0.104.1
Scanned directories: 0
Scanned files: 1
Infected files: 1
Data scanned: 0.04 MB
Data read: 0.04 MB (ratio 1.00:1)
Time: 14.868 sec (0 m 14 s)
Start Date: 2021:12:24 16:51:30
End Date: 2021:12:24 16:51:45
Interestingly ClamAV claims it’s a copy of the Dapato dropper, so I’m not sure what the authoritative source or lineage of this sample is. But this is my blog, so I get to say it’s Kuluoz.
Stage 1
Let’s load this into a disassembler to see what this bad boy has (NB: labels and variable names in this post are my additions):
Well, that’s not helpful. If you’re wondering, the rough pseudo-code equivalent is:
int main(int a, int b, int c) {
mw_init_1();
mw_init_2(0x13);
mw_init_3();
(*extracted_payload)(a, b, c, 0x78, 0x16, 0x12);
return 0xc;
}
where extracted_payload
is an instruction stream containing the decrypted second stage of the malware.
mw_init_1
through mw_init_3
do some ugly bit mixing that I don’t feel like
reversing, so let’s try dumping it the dumb way, cause I ain’t reversing THAT:
Fine, I’ll bring up the VM and we’ll dump it by hand.
If you would like to follow along at home past this point, I highly recommend you
set up a Virtual Machine
for this.
Full disclosure: I no longer use the linked VM setup and do everything on a
Linux laptop with Gnome Boxes,
which just works™.
Dynamic Analysis
I didn’t have any issues with anti-analysis measures in this sample, so maybe that’s why I could just dump the second stage with x64dbg:
Stage 2
Sorry, but I don’t have any interesting analysis for stage 2 of this sample. It looks like manually unrolled encryption code. It’s probably machine-generated so there’s no point in reversing it. I just decided to watch for what it unpacks and executes, which is stage 3.
After all this I discovered that
UnpacMe
could have done everything up to this point automagically. But hey – this is
about the learning, and I prefer open source software when possible.
The unpacked stage 3 is the b18ed7...a23cd5
“unpacked child” in UnpacMe.
Stage 3
For stage 3 things get interesting. The payload (the actual malware) is deployed via hollow process injection. There is no per se Windows executable to dump, so I manually dumped the machine instructions in good ol' x64dbg. The code for the malicious payload is its final state, but will first be injected into a benign-looking system process. Ergo, the payload is a separate “stage”.
To deploy the payload, stage 3 creates a suspended svchost.exe
process. After
that the malicious code then maps a new section (i.e. address mapping) to the
suspended process' address space. The loader then copies the final
payload to suspended process' memory space.
To have the process execute the malicious code instead of its legitimate
code, its PEB is modified to
point to the malicious code. This is done with APIs such as
ZwMapViewOfSection
, ZwUnmapViewOfSection
, ZwReadVirtualMemory
, and
friends.
After everything is set up, the payload is executed within svchost.exe
with
ZwResumeThread
. After resuming the hollowed-out process, Stage 3 exits:
The Payload
The payload uses a few methods to avoid detection, none of which hamper our ability to analyze the code. For example, minimal effort is made to hide the C2 domains by constructing strings on the stack, byte-by-byte:
In addition, the sample uses dynamic import resolution with
GetProcAddress
to resolve the Win32 library calls such as
ShellExecuteA
and URLDownloadToFile
. Nothing special there, just standard
obfuscation/anti-detection.
Then the malware bit of the software… finally. The core loop of the “bad” program is straightforward. There are only 4 commands the client malware understands, one of which is duplicated. The commands are idle, run executable, update executable (plus a duplicate), and uninfect the computer. It’s simple, but enough to cause harm to a computer.
The malware polls the C2 server over HTTP using InternetReadFile
.
At the risk of oversimplifying things, the pseudo-code is something like:
infected = is_infected();
while (true) {
sleep_minutes(2);
n_bytes = fetch_c2_command(user_agent_str, buffer);
got_command = false;
if (n_bytes >= 4) {
got_command = true;
command = first_four_bytes(buffer);
if (command == "idl=") {
idle();
} else if (command == "upd=" || command == "rrm=") {
// The "rrm=" command has the same code,
// but is put here for brevity
path = exclude_first_four_bytes(buffer);
download_and_exec_file(path);
delete_current_file();
} else if (command == "run=") {
path = exclude_first_four_bytes(buffer);
download_and_exec_file(path);
} else if (command == "rem=") {
delete_current_file();
remove_malware();
} else {
got_command = false;
}
}
if (got_command) {
if (!infected) {
infect_computer();
installed = true;
}
}
}
To infect a computer, the malware places a urlmon
entry in the registry at
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
, with an
exe dropped at %APPDATA%\urlmon.exe
. This gives it persistence across boots.
Takeaways
The malware here was fairly simple, but it was fun to analyze. Here are some takeaways:
- Reverse engineering stuff is fun!
- Don’t open
.exes
with a PDF icon, grandpa. - The obligatory IOCs:
urlmon
found withinHKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
%APPDATA\urlmon.exe
hxxp://infopepsigoood[.]ru/kleo/index.php?r=gate&id=[0-9a-zA-Z]*&group=[0-9a-zA-Z]*(&.*)?\
- Shout-out to Open Analysis for their tools and great videos.