As a security analyst, you’ve received a PCAP file linked to a compromised user. Your objective is to examine the network traffic to discern the activities of the attacker, incorporating both DFIR analysis and threat intelligence findings.
A single internal Linux host pulled a bash dropper from an nginx server over plain HTTP on port 80, using wget. The script probed for a writable, executable directory, ran a cleanup script that stopped a competing miner and stripped suspicious crontab entries, then picked a binary matching the CPU architecture and executed it as a hidden file. The binary was UPX-packed. Unpacked and checked against VirusTotal, it is a Redtail cryptominer sample, so the purpose of the intrusion was resource hijacking. The whole chain is readable from the PCAP because the attacker never encrypted the transfers.
network_capture.pcap → tshark or Wireshark. HTTP in cleartext, so TCP stream following recovers both scripts and the binaryTimestamp note: The PCAP is from 2025-02-06. The
Dateheaders on the HTTP responses (GMT) give the transfer times.
Start with the conversation table. The capture holds a single internal host talking to one external server, so there is no ambiguity about who the victim is.
tshark -r network_capture.pcap -q -z conv,tcp
List the HTTP requests. Every GET comes from the victim with a Wget/1.21.2 user agent and goes to the same nginx server, which serves all the files in the case.
tshark -r network_capture.pcap -Y http.request -T fields -e frame.number -e ip.dst -e http.request.uri -e http.user_agent
The same conversation data shows the server side of each stream. Plain HTTP, so the default port.
tshark -r network_capture.pcap -Y http.request -T fields -e tcp.dstport | sort | uniq -c
Order the requests by frame. The first GET is the server index page; the first real payload request after it, at frame 137, is a short extensionless path. Following that stream shows a bash script, so it is the dropper that drives everything else.
tshark -r network_capture.pcap -Y http.request -T fields -e frame.number -e http.request.uri
Gotcha: The format mask is two characters. Don’t count the index page as a downloaded file; the dropper is the first thing fetched after it.
Follow the TCP stream carrying the dropper (stream 10) and read the script. The first thing it defines is a helper that tries wget, falls back to curl, and finally to a raw /dev/tcp socket. Every later download in the script goes through it.
tshark -r network_capture.pcap -q -z follow,tcp,ascii,10
The same script loops over candidate directories (including /tmp, /var/tmp and /dev/shm) and creates .testfile, then .testfile2. The second file is written with dd and a block size of 2M and a count of 1, with truncate -s 2M as the fallback.
tshark -r network_capture.pcap -q -z follow,tcp,ascii,10 | grep testfile
The dropper stores the output of a single command in an ARCH variable and later greps it for x86_64, amd64, i[3456]86, armv8/aarch64 and armv7. That variable assignment holds the command.
tshark -r network_capture.pcap -q -z follow,tcp,ascii,10 | grep ARCH=
The first branch of the architecture check matches x86_64, and the victim’s next requests confirm it: after the clean script, the GET for the binary lands at frame 447. The script renames it to a hidden .redtail before executing it.
tshark -r network_capture.pcap -Y 'http.request.uri contains x86_64' -T fields -e frame.number -e http.request.uri
The dropper downloads and runs a second script, clean (stream 22), before installing its own binary. Its first actions are to disable and stop a service named c3pool_miner, then it wipes download-and-execute lines from every crontab. The disable line is the answer.
tshark -r network_capture.pcap -q -z follow,tcp,ascii,22 | grep -E 'systemctl'
Export the x86_64 response from stream 35 by following it raw and stripping the HTTP header, then run strings on the carved binary. UPX leaves its banner in the file, including the version.
tshark -r network_capture.pcap -q -z follow,tcp,raw,35 > x86_64.hex # decode and strip the HTTP header to get the ELF
strings x86_64 | grep 'UPX'
Tip:
--export-objects httpproduced nothing for this large chunked response. Following the stream raw and trimming the header worked.
Decompress a copy of the binary with upx -d, then compute the Shannon entropy of the unpacked file over its bytes. This also gives the SHA256 (61c128f1...656c) used for the VirusTotal lookup in the next question.
upx -d -o unpacked x86_64
python3 -I -c "import sys,math,collections;d=open(sys.argv[1],'rb').read();c=collections.Counter(d);print(-sum(v/len(d)*math.log2(v/len(d)) for v in c.values()))" unpacked
Look up the SHA256 of the unpacked binary on VirusTotal and read the names list. The first submission date is 2025-02-06. The names list is not ordered, so the first-submission name was not read directly. It was reasoned: of the names listed, only one matched the *******.******* format, and HTB accepted it.
bin/vt 61c128f1...656c --json
The sample is a Redtail miner and the dropper first removes a rival miner, so the objective is cryptocurrency mining on the victim’s hardware. That is Resource Hijacking. This was reasoned from the malware’s behaviour rather than read from an artifact, and HTB accepted it.
| Phase | Action |
|---|---|
| Command and Control | Victim fetches the sh dropper from the attacker’s nginx server over HTTP with wget |
| Discovery | Script looks for a writable directory by creating test files and reads the CPU architecture with uname -mp |
| Defense Evasion | clean script disables and stops the c3pool_miner service and removes download-and-execute lines from crontabs |
| Command and Control | Architecture-matched binary is downloaded and renamed to hidden .redtail |
| Execution | Packed ELF is run in the background |
| Impact | Redtail cryptominer hijacks the host’s CPU |
| Type | Value |
|---|---|
| IP (victim) | 192.168.1.152 |
| IP (attacker, port 80) | 94.156.177[.]109 |
| URL | hxxp[://]94[.]156[.]177[.]109/sh |
| URL | hxxp[://]94[.]156[.]177[.]109/clean |
| URL | hxxp[://]94[.]156[.]177[.]109/x86_64 |
| File | .redtail |
| SHA256 (unpacked) | 61c128f1...656c |
| Service (disabled by script) | c3pool_miner |
| Technique | ID | Description |
|---|---|---|
| Ingress Tool Transfer | T1105 | Dropper and miner pulled over HTTP with wget, curl or /dev/tcp |
| Command and Scripting Interpreter: Unix Shell | T1059.004 | Bash dropper and cleanup scripts |
| System Information Discovery | T1082 | uname -mp to pick the binary |
| Service Stop | T1489 | systemctl disable and stop on c3pool_miner |
| Obfuscated Files or Information: Software Packing | T1027.002 | UPX 4.23 packed miner |
| Resource Hijacking | T1496 | Redtail cryptominer |
Plain HTTP downloads of shell scripts and ELF files from a bare IP are detectable at the proxy or firewall. The victim fetched sh, clean and an extensionless x86_64 straight from an IP address with a wget user agent. Alerting on executable content types from servers with no hostname would have caught the chain at the first request.
The dropper hunts for places to run, so mount options matter. It reads /proc/mounts for noexec and tries /tmp, /var/tmp and /dev/shm. Mounting those with noexec forces it to search for another writable directory, and a find / from a service account is itself a noisy signal.
Crontab wiping and service stops are tamper signals. The cleanup script removes lines containing wget, curl and /dev/tcp from every cron location and stops a competing miner. Monitor writes to /var/spool/cron and /etc/cron.*, and systemctl disable calls on unfamiliar units.
A hidden dotfile executed from a world-writable path should stand out. .redtail is renamed and run from the working directory. File-creation alerts for hidden ELF files in temp directories, plus CPU-usage baselining on servers, would surface the miner.
The attacker turned a Linux host into a cryptominer with three unencrypted HTTP downloads and a bash script that cleaned out any competing miner first. Nothing was hidden on the wire: the capture contains the full dropper, so network-level detection of script and ELF downloads from raw IPs would have stopped it.