Security notes
Investigating Koi Stealer malware using Wireshark
An end-to-end Wireshark investigation of a Koi Stealer packet capture, examining beaconing, HTTP payloads, internal network activity and indicators of compromise.
Originally published on Medium on 15 April 2025. Select a screenshot to open the full-size image.
In this write-up, I will analyze a pcap file from www.malware-traffic-analysis.net. I will use the pcap from the exercise: BIG FISH IN A LITTLE POND. The goal is to identify malicious behavior, and extract indicators of compromise (IOCs), but I mainly wanted to practice my Wireshark skills.
This article walks through an end-to-end analysis using Wireshark, starting with a high-level overview and progressively drilling into anomalies and patterns.
The scenario has the following LAN segment details:
- LAN segment range: 172.17.0[.]0/24 (172.17.0[.]0 through 172.17.0[.]255)
- Domain: bepositive[.]com
- Active Directory (AD) domain controller: 172.17.0[.]17 — WIN-CTL9XBQ9Y19
- AD environment name: BEPOSITIVE
- LAN segment gateway: 172.17.0[.]1
- LAN segment broadcast address: 172.17.0[.]255
Taking a look at the bigger picture
Before diving into individual packets, it’s useful to take a look at the traffic at a macro level. This high-level perspective allows us to establish context: What protocols are in use? Which endpoints are most active? Are there external connections? Which IP addresses are talking the most? By reviewing these different types of statistics, we can identify potential anomalies, prioritize areas for deeper inspection, and avoid drowning in packets.
First, I like to take a look at the Protocol Hierarchy to get a general impression of the protocols being used. One protocol that stands out to me is SMB — specifically, because it’s an older version of SMB. This doesn’t necessarily mean anything by itself but it could be easier be abused than SMBv2 or SMBv3. Also, the Active Directory Replication traffic could be interesting, depending on the context. Another protocol that jumps out is HTTP, due to it being an unecrypted protocol which can be used for exfiltration or command-and-control (C2) communication.
The next thing I like to do take a look at the Conversations and sort on Duration, which is the total time a network conversation lasted. In this case, you can see four “winners”, but one of them is especially suspect to me: 79.124.78[.]197.
Why aren’t the other IP addresses not immediately suspect to me? Because the 172.17.x.x addresses are part of the LAN segment range, and the other address (20.10.31[.]115) is a Microsoft IP address. It also noteworthy to remember the Stream ID (IP stream, not the TCP or UDP stream) for later use, which is 22 in our case. The IP stream tracks all packets between two IP addresses, regardless of the protocols.
Looking at the Endpoints tab we can see a suspicious packets-to-bytes ratio. There are many packets being sent, but with few bytes. This could suggest beaconing (C2-traffic):
The final item I like to inspect from the Statistics tab, is the list of HTTP requests. In this case, we can clearly observe some suspicious POST requests from the same IP address:
Our first general impression from looking at the bigger picture is that a significant portion of the traffic seems to originate from a suspicious IP: 79.124.78[.]197. This IP address is issuing strange POST requests and warrants further investigation.
Dissecting the IP stream
Looking at the IP stream by using the filter “ip.stream eq 22”, we can clearly see malicious traffic originating from our infected host: 172.17.0[.]99. We can see multiple POST requests, but also a GET request over HTTP:
By opening Statistics > Flow Graph from here, we can change the Y axis to display bytes in order to show the amount of data transferred over time. This is useful for identifying C2 beaconing behaviour. I use an interval of 1-second in the image below. In this case we can see periodic bursts, possibly indicating beaconing behavior associated with C2 communication:
Furthermore, we can add a Time Delta column by adding a column with the “frame.time_delta_displayed” field (credits to Active Countermeasures for this):
Looking at the Time and Time Delta columns we can see a pattern. We can see a Time Delta of around 60 seconds every 12 packets or so, showing the time difference between the current packet and the previous packet in the packet list. We can also notice a consistent pattern between the source and destination ip-addresses after the 60 seconds intervals (see column in the middle). This means that 172.17.0[.]99 is periodically reaching out to the external server (79.124.78[.]197). Malware often “checks in” to receive instructions from its C2-server, so this is important to keep in mind.
Investigating HTTP traffic
Looking at the HTTP traffic with the filter “ip.addr == 79.124.78[.]197 && http”, we can observe binary data being transferred, which is a red flag in this case. This is likely due to some form of malware evasion:
Taking a closer look at the data, we can observe the following string “3130327c33303136383231332d336265302d613735312d383161302d636633623232386338363534” (edit: to clarify, the following screenshot is data from one of the other frames in the image above, hence you see 40 bytes instead of 94):
This string can be decoded with Cyberchef by using a “from Hex” recipe, which gives us the following result “102|30168213–3be0-a751–81a0-cf3b228c8654”:
This looks like some sort of UUID that the C2 server is using. We can search for this string in the packet bytes to confirm this, because the string is identifiable in every POST request:
Other identifiers we can extract from the POST request from the same IP stream (by using (ip.stream eq 22 ) && (http.request.method == “POST” ))) are:
101|30168213–3be0-a751–81a0-cf3b228c8654|5LHtVruc|f3f3oMg2lz4XGyHy0LidzFiNvSftke//k+COyrO9aBI=
111|30168213–3be0-a751–81a0-cf3b228c8654|XHfhyxOtsArXQLiP|=¬ª|U7÷#±/ónåH -øî1
Rm¹UÂOÖyBÓÌ\vK¹sâl4ð]7ø´1
Z (I am not sure what this last part is. If someone can help me, that would be great.)
102|30168213–3be0-a751–81a0-cf3b228c8654
The general structure seems to start with the following: <Code>|<UUID>|<Session ID>. Only frame 1668 seems to be using 101 as a code in the whole packet capture, and only frame 1672 uses code 111. This is probably due to something that the malware is doing at the start:
All the following POST foot requests are using code 102, with a consistent length of 443:
Opening a TCP stream from here shows this to us as well:
The user-agent is also highly suspicious because it is from an older version of Internet Explorer. Which is in this context also a clear indicator of malware:
Infiltration in progress
Given the fact that there is SMB traffic observed, it could be wise to look for possible lateral movement or enumeration in the pcap. Using the following display filter: “kerberos || smb2 || smb || dcerpc”, we can filter for interesting traffic:
As you can see, this is a conversation between our infected host and 172.17.0[.]17, which is the Domain Controller (DC). The bindRequest(7) “<ROOT>” sasl from our infected host to the DC is possibly interesting, because this shows a succesful LDAP bind. So I’ve marked it in Wireshark so we can inspect the whole pcap without a filter. Interestingly enough, we can see a dynamic DNS update before the bindRequest happens coming from the host:
This seems to me like a dynamic DNS server dynamic update record injection (see: DNS Server Dynamic Update Record Injection — Virtue Security), but I am not 100% sure about it. It deletes records and afterwards updates the A record to 172.17.0[.]99. Although this behavior can occur in legitimate domain environments, it can also be leveraged by malware to masquerade as a trusted host or prepare for internal lateral movement:
We can also observe LANMAN traffic when filtering for SMB which is another red flag. LANMAN is a legacy protocol and is often abused by bad actors for enumeration. The only reason to enable it is for compatability reasons with legacy systems. When filtering with “lanman” as display filter, it shows short-lived communication from 2 minutes, which is possibly due to enumeration:
Let’s get back to the host again. Filtering data traffic for 172.17.0[.]99 by using “(ip.addr == 172.17.0[.]99) && (data)”, shows us another important IP address: 46.254.34[.]201. This one we haven’t seen before yet:
Looking it up in VirusTotal shows us that it is related to the KOI malware. It also shows the same URL we can observe when opening the TCP stream from the same packet from above. This is obviously a red flag:
The first connection to this IP address happens at 17:35:04. This is short-lived communications which ends at 17:36:46. Which probably indicates that another layer of infection happened at that time:
So to recap, we now have three IP addresses involved with malicious traffic:
172.17.0[.]99 — compromised host
79.124.78[.]197 — C2 server
46.254.34[.]201 — suspicious traffic to a compromised domain
Suspicious TLS traffic
When further investigating the 46.x.x.x IP-address, we can filter for the TLS Client Hello and the TLS Server Hello with “ip.addr == 46.254.34[.]201 && (tls.handshake.type == 1 || tls.handshake.type == 2)”. From here we can gather the JA3 fingerprints and look them up. JA3 fingerprints can be useful for identifying TLS traffic:
The JA3 fingerprint values are:
3c4eb72b882d4d1442c67ce73f1292a9 — TLS Client Hello
15af977ce25de452b96affa2addb1036 — TLS Server Hello
Both of these are related to malware, although this is not a strong indicator by itself because bad actors can spoof this.
Conclusion
This pcap analysis strongly indicates malicious activity consistent with KOI Stealer infection. The internal host 172.17.0[.]99 communicates periodically with the C2 server 79.124.78[.]197 using obfuscated POST requests over HTTP. The pattern of communication suggests beaconing behavior. In addition, the dynamic DNS update and LDAP bind to the Domain Controller (172.17.0[.]17) may indicate preparatory steps for lateral movement. The connection to 46.254.34[.]201 also increases confirms the presence of KOI Stealer or related malware. I hope you found this article useful — feel free to share your thoughts.
IOCs:
IP Addresses
172.17.0.99 — Infected host inside LAN
79.124.78.197 — C2 server
46.254.34.201 — Malicious external server
User-Agent
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)
HTTP URIs
/foots[.]php
/index[.]php?id=&subid=qIOuKk7U
/index[.]php
bellantonicioccolato[.]it
POST Payload patterns
- 101|30168213–3be0-a751–81a0-cf3b228c8654|5LHtVruc|f3f3oMg2lz4XGyHy0LidzFiNvSftke//k+COyrO9aBI=
- 111|30168213–3be0-a751–81a0 cf3b228c8654|XHfhyxOtsArXQLiP|=¬ª|U7÷#±/ónåH -øî1 Rm¹UÂOÖyBÓÌ\vK¹sâl4ð]7ø´1 Z
- 102|30168213–3be0-a751–81a0-cf3b228c8654
Dynamic DNS Update
A-record update to 172.17.0.99
JA3 Fingerprints
3c4eb72b882d4d1442c67ce73f1292a9 — TLS Client Hello
15af977ce25de452b96affa2addb1036 — TLS Server Hello
Sources
Malware-Traffic-Analysis.net — 2024–09–04 — Traffic analysis exercise: Big Fish in a Little Pond (this is just a page to the file, not the file itself, don’t worry)
https://blog.nviso.eu/2021/11/15/detecting-dcsync-and-dcshadow-network-traffic/
https://activecm.github.io/threat-hunting-labs/beacons/
DNS Server Dynamic Update Record Injection
Application Layer Protocol: DNS, Sub-technique T1071.004 — Enterprise | MITRE ATT&CK®