A non-technical client recently purchased a used computer for personal use from a stranger they encountered online. Since acquiring the computer, the client has been using it without making any changes, specifically not installing or uninstalling any software. However, they have begun experiencing issues related to internet connectivity. This includes receiving error messages such as “Server Not Found” and encountering difficulties with video streaming. Despite these problems, checks with the Windows Network Troubleshooter indicate no issues with the internet connection itself. The client has provided a memory image and disk artifacts for investigation to determine if there are any underlying issues causing these problems.
A second-hand PC came with a DDoS bot already installed. The bot lived in the all-users StartUp folder, so it launched from explorer.exe at every logon, and it slept before running a debugger check as a cheap sandbox and analyst filter. It generated its C2 address with a small DGA, wrote the active domain to one file under C:\Users\Public, and kept the base64-encoded orders from the C2 in another. Those orders named a target site, an attack type and an expiry date, and the bot either flooded the target with HTTP GETs or pinged it in a loop. The owner’s “Server Not Found” and stuttering video came from the machine spending its time and bandwidth attacking someone else.
memdump.mem → Volatility 3 (windows.info, windows.pstree, windows.pslist)Files.ad1, an AccessData logical image → no AD1 reader was available on the analysis host, so items were extracted with a small parser (zlib chunks, item addresses relative to 0x200). FTK Imager opens it directlyFiles.ad1 → objdump -d -M intel. It is a compiled MinGW binary with obfuscated control flow, so expect slow readingTimestamp note: The memory capture time and the config expiry are UTC. The PE compile time is whatever the attacker’s build stamped, and it is not trustworthy (see Q6).
windows.info on the memory image reports a Windows 10 build 19041 x64 kernel. Volatility 3 does not use profiles, so the answer is the Volatility 2 style profile name for that build and architecture.
vol -f memdump.mem windows.info
The same windows.info output carries the SystemTime field, which is the moment the image was captured. Everything the config files say about “active” or “expired” later is measured against this time.
vol -f memdump.mem windows.info | grep SystemTime
windows.pstree shows one odd child of explorer.exe: a process run straight from a StartUp location rather than from Program Files or System32. Its name poses as a Windows security tool. That placement is also why it survives reboots, so it is a persistence mechanism as well as the running payload.
vol -f memdump.mem windows.pstree
The Path column of the same pstree row gives the full image path. It is the all-users StartUp folder under ProgramData, so the bot launches for every account that logs on.
vol -f memdump.mem windows.pstree | grep -i securitycheck
Dump the process with windows.pslist --dump or windows.dumpfiles against its PID to get the image from memory, then take the copy from Files.ad1 instead, because the disk file is the unmodified binary and gives the true file hash. Hash it with sha256sum.
vol -f memdump.mem windows.pslist --pid 2572 --dump
sha256sum work/ad1/SecurityCheck.exe
Read TimeDateStamp from the PE file header. The value is in 2037, which is in the future relative to the 2023 capture time, so the stamp was set by hand and is not a usable build date. Report it as stored.
objdump -x work/ad1/SecurityCheck.exe | grep -i 'time/date'
Gotcha: A compile time after the memory capture is a forged timestamp. It is still the answer to the question; do not “correct” it.
Disassemble the binary and look for the import of CreateMutexA near the start of the code. The string pushed as its name argument (at 0x40605e) is the mutex, which the bot uses as a single-instance check.
objdump -d -M intel work/ad1/SecurityCheck.exe | grep -B8 CreateMutexA
At the top of main, a Sleep call is followed by a call to the Win32 debugger check. The check exists to detect an attached debugger, and the sleep in front of it delays it past a short analysis window.
objdump -d -M intel work/ad1/SecurityCheck.exe | grep -A12 '<main>'
The argument pushed to the Sleep before the debugger check is 0xdbba0, which is 900000 milliseconds. Divide by 60000 for minutes. Sandboxes that cap analysis time never reach the real behaviour.
objdump -d -M intel work/ad1/SecurityCheck.exe | grep -B3 -A3 'Sleep'
The DGA routine at 0x4016f4 builds a http://XXXXXX.xyz host from the nine-character set lcb2a8rn5. Five of the six characters are picked randomly from that set and the third character is fixed as 0. Nine choices at five positions gives 9^5 domains. This count was derived from the disassembly rather than read from a log, so it is reasoned, not observed.
objdump -d -M intel work/ad1/SecurityCheck.exe --start-address=0x4016f4 --stop-address=0x401900
Take the same DGA structure and pin the first two characters to rb. The third is already fixed, which leaves three random positions: 9^3.
Pin the last three characters to rla instead. With the fixed third character that leaves the first two positions random: 9^2.
The bot writes its currently active domain to a text file under the Public profile, which every user can write to. Reading win.ini there shows a single domain, the one the DGA settled on.
cat work/ad1/Users/Public/Documents/win.ini
Gotcha:
config.inisounds like the place for an active C2, and it is the more obvious guess, but it holds the orders. The domain is in the file with the innocent-lookingwin.ininame.
The orders file is config.ini in the Public profile root. It holds a base64 blob that decodes to the target, the attack type and an expiry value.
cat work/ad1/Users/Public/config.ini
Read win.ini from Q13 again. The domain on its single line is the active C2, and it matches the XXXXXX.xyz shape produced by the DGA, with a 0 in the third position.
cat work/ad1/Users/Public/Documents/win.ini
The attack function at 0x40211d switches on an attack type value. Type 1 is the ICMP ping flood and type 2 is the HTTP GET flood, so the bot has two attack kinds.
objdump -d -M intel work/ad1/SecurityCheck.exe --start-address=0x40211d --stop-address=0x402400
Decode the base64 content of config.ini from Q14. The URL inside it is the site the bot is told to hit.
base64 -d work/ad1/Users/Public/config.ini
The decoded config.ini ends in an epoch value, 1693482358. Convert it to UTC. It falls after the capture time from Q2, so the attack order was still active when the memory was taken.
date -u -d @1693482358 '+%Y-%m-%d %H:%M:%S'
In the type-2 branch the loop runs its counter from 0 to 0x13, which is 20 GET requests, each followed by Sleep(1000). After the loop it sleeps for 300000 ms before the next round.
objdump -d -M intel work/ad1/SecurityCheck.exe --start-address=0x40211d --stop-address=0x402400 | grep -B4 -A10 'cmp.*0x13'
The type-1 branch builds its command from a format string, ping -n 16 %s, and then sleeps 60000 ms. The -n count is the number of ICMP echo requests per round.
objdump -s -j .rdata work/ad1/SecurityCheck.exe | grep -i 'ping'
The orders in config.ini are only base64-encoded, and the C2 address is a DGA name drawn from a space of 59049 possibilities. Nothing in the file or in the code paths read for this case authenticates the C2 server, so whoever registers the active DGA domain can hand the bot new orders. This answer is reasoned from that evidence, not from a full review of the network code.
| Phase | Action |
|---|---|
| Persistence | SecurityCheck.exe placed in the all-users StartUp folder, launched by explorer.exe at logon |
| Defense Evasion | 15 minute Sleep before an IsDebuggerPresent check |
| Execution | Mutex created as a single-instance guard |
| Command and Control | DGA picks a .xyz domain; active domain written to win.ini in the Public profile |
| Command and Control | Base64 orders from the C2 stored in config.ini |
| Impact | HTTP GET flood (20 requests per round) or ICMP flood (16 pings per round) against the target named in the orders |
| Type | Value |
|---|---|
| File | C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\SecurityCheck.exe |
| SHA-256 | 4062963405cc71c032ca51ffd409e832120fcfd496969f4ef548774323c72413 |
| Mutex | config_m2 |
| File | C:\Users\Public\Documents\win.ini |
| File | C:\Users\Public\config.ini |
| C2 domain | hxxp[://]cl0lr8[.]xyz |
| Attack target | hxxp[://]nbscl231sdn[.]mnj |
| Technique | ID | Description |
|---|---|---|
| Boot or Logon Autostart: Startup Folder | T1547.001 | Malware dropped in the all-users StartUp folder |
| Virtualization/Sandbox Evasion: Time Based Evasion | T1497.003 | 15 minute sleep before the main logic |
| Debugger Evasion | T1622 | IsDebuggerPresent check at the top of main |
| Dynamic Resolution: Domain Generation Algorithms | T1568.002 | DGA builds the C2 host name |
| Application Layer Protocol: Web Protocols | T1071.001 | C2 contact and orders over HTTP |
| Data Encoding: Standard Encoding | T1132.001 | Orders in config.ini are base64 |
| Endpoint Denial of Service: Service Exhaustion Flood | T1499.002 | Repeated HTTP GETs against the target |
| Network Denial of Service | T1498 | ICMP ping flood against the target |
A second-hand machine should be rebuilt, not inherited. The bot had been sitting in a StartUp folder since before the owner took the machine, and nothing about it was hidden. A clean reinstall removes it, and a quick check of the StartUp folders and Run keys would have found it.
A process that starts from a StartUp folder and poses as a security tool is worth a look. SecurityCheck.exe was a child of explorer.exe from ProgramData. Alert on new executables in the all-users StartUp path and compare names against what is actually signed software.
Slow network and failing lookups are a symptom, not a fault. The Windows troubleshooter saw no connectivity issue because the connection worked; it was just busy. Outbound request rates and DNS volume per host show a flood bot immediately, including lookups of random-looking .xyz names that fail.
Write-for-everyone folders make good malware state stores, and good detections. The bot kept its C2 and orders in C:\Users\Public. A file-creation rule for .ini files in that folder from an unsigned binary would flag this, and the base64 blob in config.ini can be decoded straight to the target and expiry.
The owner bought a working DDoS bot with their PC; it ran from the StartUp folder, picked its C2 with a DGA and attacked whichever site its base64 orders named. Persistence and C2 state sat in plain files, so the picture was recoverable from memory plus the disk image without running the sample.