Janice from accounting is beside herself! She was contacted by the SOC to tell her that her work credentials were found on the dark web by the threat intel team. We managed to recover some files from her machine and sent them to the our REM analyst.
Janice ran a program she believed only took screenshots. Static analysis showed a Go binary that pulls in a screen-capture library and an FTP client library. It records keystrokes to a text file, saves periodic screenshots as image files, and ships the lot to an attacker-controlled FTP host with credentials hardcoded in the binary. The keystroke log held the password Janice typed when she changed it, which explains how her work credentials ended up for sale. The screenshots then showed what she had open the last time the tool ran.
unzip to give Loggy.exe and the recovered files from Janice’s machinefile, sha256sum, strings and objdump. It was never runTimestamp note: none of the questions ask for timestamps. The screenshot filenames carry an epoch value, which is the order to use when deciding which capture is the latest.
Unpack the archive, confirm with file that Loggy.exe is a PE executable, and hash it. Hash the extracted binary, not the zip.
unzip danger.zip && file Loggy.exe && sha256sum Loggy.exe
Go binaries embed the toolchain version as a plain string, and a mass of github.com/... import paths is the other giveaway. A go1. grep over the strings gives the exact build version.
strings Loggy.exe | grep -E 'go1\.[0-9]+'
Tip: The answer format is the language name followed by the version, so expand
goto the full language name.
Janice’s story is that the tool only takes screenshots, so look for the third-party library that does it. The import paths in the binary’s strings include a screen-capture package, which is the legitimate-looking cover for the rest of the behaviour.
strings Loggy.exe | grep -i 'github.com'
The format mask is nine characters, and the malware both captures screens and logs keys, so it has to write them out somewhere. WriteFile fits the mask and the behaviour. This answer was reasoned from the mask and the observed output files, not read from a named call site in the disassembly.
The same strings pass shows a fixed filename that is written to repeatedly. It sits next to the keystroke-handling code, and it is the file recovered from Janice’s machine as the keystroke log.
strings Loggy.exe | grep -iE '\.(txt|png)'
The screenshots are named with an epoch timestamp, so the highest value is the last run. Open that image: the window in the foreground is the answer, shown as Solitaire & Casual Games.
ls screenshot_*.png | sort
Tip: The answer is the app’s short name, so take it from the window title rather than the full Store listing text.
Read keylog.txt from the recovered files and find the stretch where she typed a username followed by a password change. The log repeats keys and does not preserve letter case, so the capitalisation of the password was reasoned rather than read directly. The deduplicated keypresses pointed to this value and the platform accepted it.
cat keylog.txt
Gotcha: Keystroke logs drop case and double up held keys, so the raw text will not match the real password character for character. Deduplicate the keypresses first.
Of the imported packages, one is an FTP client. A screenshot library and a keyboard hook only collect data; an FTP client is what moves it off the host.
strings Loggy.exe | grep -i 'github.com'
From Q3 the FTP library is the exfil path, so find its caller. The function main.sendFilesViaFTP contains the hardcoded dial string with the port appended, gotthem.htb:21. The answer is the host part only.
strings Loggy.exe | grep -E ':21'
The login credentials are not a single string. Go strings are pointer-and-length pairs packed together in .rdata, so strings shows them run together. Disassemble main.sendFilesViaFTP and follow the arguments to the FTP Login call: the username is at 0x650828 with length 11 and the password at 0x653417 with length 17. The lengths split the packed text correctly.
objdump -d Loggy.exe | grep -A60 'main.sendFilesViaFTP>:'
Gotcha: Without the lengths from the disassembly you cannot tell where one string ends and the next begins. Check the argument order of the
Logincall too, since username comes first.
| Phase | Action |
|---|---|
| Execution | Janice runs a binary presented as a screenshot tool |
| Collection | Screenshots are taken with the kbinani screenshot library and saved as image files |
| Collection | Keystrokes are appended to keylog.txt |
| Credential Access | Janice’s password change is captured in the keylog |
| Exfiltration | main.sendFilesViaFTP logs in to gotthem.htb:21 with hardcoded credentials and uploads the files |
| Type | Value |
|---|---|
| SHA-256 | 6acd8a362def62034cbd011e6632ba5120196e2011c83dc6045fcb28b590457c |
| File | Loggy.exe |
| File | keylog.txt |
| Domain | gotthem[.]htb |
| FTP credentials | NottaHacker:Cle@rtextP@ssword |
| Go dependency | github.com/jlaffaye/ftp |
| Go dependency | github.com/kbinani/screenshot |
| Technique | ID | Description |
|---|---|---|
| Screen Capture | T1113 | Periodic screenshots through the kbinani screenshot library |
| Input Capture: Keylogging | T1056.001 | Keystrokes recorded to keylog.txt, including Janice’s new password |
| Data Staged: Local Data Staging | T1074.001 | Captures kept on disk until the upload |
| Exfiltration Over Alternative Protocol: Exfiltration Over Unencrypted Non-C2 Protocol | T1048.003 | Plain FTP upload to gotthem.htb with hardcoded credentials |
A “screenshot tool” that imports an FTP client is not a screenshot tool. Dependency lists are cheap to read on a Go binary, because the import paths are embedded as strings. Pairing a capture library with a network client library is a quick triage signal before any disassembly.
Plain FTP out of a workstation should be blocked or alerted on. The malware used an unencrypted FTP session on port 21 to a single host. Egress filtering that only allows web traffic from user endpoints would have stopped the upload, and a DNS or proxy alert on a rarely seen domain would have flagged it.
Hardcoded credentials in malware are a gift to the defender. The FTP username and password sit in the binary, so they can be shared as IOCs and used to find other victims on the same drop server. Go’s packed strings hide them from strings, so pull them out from the call site instead.
Keyloggers turn every password change into a leak. Janice’s new password was written to disk in the clear. Credential-theft exposure from endpoint malware is why leaked credentials show up on the dark web, so reset passwords after any keylogger find and enforce MFA so a stolen password is not enough.
A tool sold as a screenshot utility logged Janice’s keystrokes and screens and sent them to an attacker’s FTP server using credentials baked into the binary. The binary’s own import list gave the capability away, and its hardcoded credentials gave defenders a ready-made indicator.