June 16, 2026
How I Analyzed a Real Malware Network File To Uncover The Web Shell
One of the stories from my BTLO Walk through Series
Steosumit
4 min read
Imagine opening a packet capture (PCAP) with nothing but a hunch that something went wrong. Packet analysis can feel like looking at a wall of matrix code, but if you know how to pull the right threads, a chaotic stream of bytes transforms into a clear story of an attack.
Recently, I went through this exact journey during an investigation. Here is the step-by-step breakdown of how I isolated a target, discovered a malicious web shell upload, and unmasked the reconnaissance tools used by the attacker.
Step 1: Broad Strokes and the Noise Level
Every good PCAP investigation starts with a top to down approach. I immediately jumped into the Statistics Tab and pulled up Endpoints. Two IP addresses immediately stood out due to a massive, disproportionate volume of packets: 10.251.96.4, 10.251.96.5
Clearly, these two were having a very intense conversation. To figure out the nature of this chatter, I filtered for traffic originating from the .5 node:
ip.src == 10.251.96.5ip.src == 10.251.96.5The display filled with a sea of HTTP 404 Not Found errors. When you see a massive spike in 404 responses between two specific nodes, it almost always points to one thing: directory enumeration. Someone was knocking on every door to see what was open.
Step 2: Flipping the Script on Port Scanning
Next, I wanted to isolate the TCP handshake attempts — specifically [RST] and [SYN] flags—to see if a port scan was underway. I applied this filter to isolate the conversations between the two hosts while stripping away standard established traffic:
(ip.addr == 10.251.96.4 and ip.addr == 10.251.96.5) and (tcp) and not (tcp.flags.reset == 1 or tcp.flags.ack == 1)(ip.addr == 10.251.96.4 and ip.addr == 10.251.96.5) and (tcp) and not (tcp.flags.reset == 1 or tcp.flags.ack == 1)The results confirmed a textbook port scan.
But then I took it a step further. I wanted to see if there was any normal, non-handshake TCP traffic happening. I added a condition to exclude SYN packets as well:
(ip.addr == 10.251.96.4 and ip.addr == 10.251.96.5) and (tcp) and not (tcp.flags.reset == 1 or tcp.flags.ack == 1 or tcp.flags.syn == 1)(ip.addr == 10.251.96.4 and ip.addr == 10.251.96.5) and (tcp) and not (tcp.flags.reset == 1 or tcp.flags.ack == 1 or tcp.flags.syn == 1)The display went entirely empty. This meant that all traffic passing from .5 to .4 consisted strictly of RST, SYN, or ACK flags. Initially, I suspected .5 might have been infected and was outbound scanning. But as I dug deeper into the connection logic, the perspective shifted: the scanning was actually being directed from .4 to .5.
Step 3: Hunting for the Infiltration (The Web Shell)
Knowing that .5 was the target being actively scanned and probed, I shifted my focus to look for actual exploitation traffic. To gain a reverse shell or code execution, an attacker usually needs to upload a malicious file. That means looking for HTTP POST requests.
I filtered out the noisy SYN scan packets and isolated POST requests hitting the target:
(ip.dst == 10.251.96.5) and not (tcp.flags.syn == 1) and (http.request.method == POST)(ip.dst == 10.251.96.5) and not (tcp.flags.syn == 1) and (http.request.method == POST)Bingo. The filter isolated an upload event directed at /upload.php.
Following the TCP stream of this request revealed the name of the active web shell.
With the web shell payload identified, I needed to see if the attacker had successfully interacted with it. I changed my method filter to look for HTTP GET requests hitting the server:
(ip.dst == 10.251.96.5) and not (tcp.flags.syn == 1) and (http.request.method == GET)(ip.dst == 10.251.96.5) and not (tcp.flags.syn == 1) and (http.request.method == GET)Among the results, a highly critical packet stood out:
Packet No:_ 16134 | Time: 16:40:51.125681Z_
Source:_ 10.251.96.4 → Destination: 10.251.96.5 (Port 80)_
Info:
GET /uploads/dbfunctions.php?cmd=id HTTP/1.1
There it was. The attacker (.4) was passing system commands (?cmd=id) directly to their newly uploaded web shell (dbfunctions.php).
TIP: I used Gemini here — explaining the situational nuances of the target destination and the web shell behavior helped me rapidly iterate and generate the exact Wireshark filters needed to pinpoint this execution port
Step 4: Unmasking the Recon Tools via User-Agents
With the attack chain established (Scan → Upload → Execute), I was left with one final question: What specific tools did the attacker use during the initial reconnaissance phase?
I knew the recon originated from .4 and targeted .5, so I went back to the raw SYN requests:
ip.src == 10.251.96.4 and ip.dst == 10.251.96.5 and tcp.flags.syn == 1ip.src == 10.251.96.4 and ip.dst == 10.251.96.5 and tcp.flags.syn == 1After a period of mindlessly scrolling through thousands of identical TCP packets, I realized looking at raw handshakes wouldn't give me the tool signatures. I needed to look at the application layer metadata. I shifted my strategy to inspect the HTTP User-Agent strings hitting the target web server:
ip.dst == 10.251.96.5 and http.user_agentip.dst == 10.251.96.5 and http.user_agentBy filtering specifically for the User-Agents and cross-referencing the unique strings with a quick Google search, the automated tools left their distinct fingerprints right in the open.
Lessons Learned
When on Wireshark use the statistics to gain a foot on the problem and then start the analysis; thinking of the technologies that might be used during the attack execution. Like HTTP, TCP, User-Agents, etc…
Have you encountered similar web shell investigations? More one the way on AI Security and Blue Teaming.
#Series #Wireshark #PCAP #PCAPAnalysis #NetworkForensics #Cybersecurity #Security #BTLO #Labs