This commit is contained in:
David Westgate 2024-06-14 20:18:24 -07:00
parent 47f34e486b
commit a13fff1568
10 changed files with 76 additions and 77 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
_*/
*bin
*pcap
*python-netfilterqueue
*env

BIN
final/captures/final_1.pcap Normal file

Binary file not shown.

BIN
final/captures/final_2.pcap Normal file

Binary file not shown.

View File

@ -117,6 +117,8 @@ To help with validating the transparency of pitap, I have made a few simplificat
3) Connected periphrals to the pitap so I can interface with it
4) Put the pitap on a dedicated power supply to avoid brownouts
![uml](./photos/validation/uml.png)
#### Pitap adapter info
The following `iwconfig -a` shows us the pitap does not have any assigned IP addresses on our network. Aside from a link local Ipv6 assigned to `br0`. I do not fully understand the implication of this, but I do not think it matters. Overall, this is a good sign
@ -129,7 +131,7 @@ The router admin panel shows us the reolink appears with the expect IP and MAC (
![router-panel.png](./photos/validation/router-panel.png)
#### Router and ARP table
The route table shows that all LAN traffic is itself going through a bridge (`br0`) on the router, which I then assume uses some sort of MAC address table to determine which physical port to communicate on.
The route table (on my router) shows that all LAN traffic is itself going through a bridge (`br0`) on the router, which I then assume uses some sort of MAC address table to determine which physical port to communicate on.
The arp table shows us the IP devices and their associated MAC addresses, and we confirm that none of the MAC addresses associated with the pitap appear here.
@ -137,11 +139,11 @@ The arp table shows us the IP devices and their associated MAC addresses, and we
Though things look good, at this point I face a final roadblock in confirming my network tap is transparent. I would like to view the MAC address table/Content Addressable Memory which as I understand would show associations between MAC addresses and physical ports on my router, entirely at the link layer. This table could prove or disprove that the link layer in my router has not 'learned' any MAC address of the pitap and that true transparency is achieved. If it was not, I would go back and re-visit my configuration step.
The issue is that my lousy ISP provided router does not seem to make viewing this possible, either in the web UI or terminal. The terminal claims to provide the command `brctl` but it is broken and returns no output. The terminal also provides an elevated `sh` command which could possibly help but that is locked down by an unknown password.
The issue is that my lousy ISP provided router does not seem to make viewing this possible, either in the web UI or terminal. The terminal claims to provide the command `brctl` but it is broken and returns no output. The terminal also provides an elevated `sh` command which could possibly help but that is locked down by an unknown password. Likewise, reolink does not provide any kind of shell access for me to check its MAC address table.
The next step would be for me to flash a new firmware like OpenWRT on my router to get this information. I can not do that at this time as I run a game server with active players on my network and any issues with the firmware upgrade risk causing an extended outage or brick my router. When I aquire another router in the future, this upgrade will then be possible.
In conclusion, I know pitap is transparent at the network layer, and I think it is also transparent at the link layer but I can not prove it.
In conclusion, I know pitap is transparent at the network layer, and it may also transparent at the link layer but I can not confirm or deny that.
#### Connecting to reolink
I am able to access the stream again on the reolink with no issues, and video quality appears good. However, we can see that the content bitrate is now lower than before, appering in the range of 1,400 kb/s to 5,500 kb/s with an average of about 3,450 kb/s. This could be because of a bandwidth bottleneck in the pitap, or because now it is dark outside and H.264 can work more efficiently with less color in the image. In this regard I am not sure.
@ -187,7 +189,7 @@ Here is a look at `received_file-1.pcap` in wireshark. For this capture, I start
![wireshark-3](./photos/capture/wireshark-3.png)
*End of stream - This looks ok too, we see the teardown of the RTSP stream. It also seems that APR packets from the pitap wireless interface are making their way back to `br0` through the gateway, which makes some sense in our configuration*
*End of stream - This looks ok too, we see the teardown of the RTSP stream. It also seems that APR packets from the pitap wireless interface are making their way back to `br0` through the gateway, which makes sense in our configuration*
@ -205,6 +207,28 @@ The result is that for a brief period of the wireshark capture while I have the
![ttl](./photos/attack/ttl.png)
#### 6b.
[`final_1.pcap`](./captures/final_1.pcap) demonstrates the result of this attack.
#### 6b. HTTP - OK -> NO
At this point I realize my approach with a simple scapy script is flawed. Sniffing and sending packets with scapy does not actually intercept and modify the packets, and instead it just effectively sends a modified copy of the packet out after the operating system has already sent the original. This was noticed when working with TCP packets because of re-transmission error responses.
To proceed, I will install `nftables` from `apt`, and `netfilterqueue` from `pip`. `nftables` seems to provide stong capabilities at both layer 2 and 3 traffic, and may help with filtering and queueuing to user space. I first considered just using `ebtables` but queueing traffic to user space with that was not non-trivial
I want to modify HTTP responses, so I will use the following `nftables` rules (created with the `nft` utility) to place the packets we want to modify into a user space queue (`NFQUEUE`). Then, a python script can accesses, modify, and explicitly send them
```bash
sudo nft add table bridge filter
sudo nft add rule bridge filter forward ip protocol tcp tcp sport 80 queue num 0
```
![nftables](./photos/attack/nftables.png)
Then, I wrote the script found in [`http.py`](./scripts/http.py). This script combines `netfilterqueue` to get/send the packets from the nftable queue, and `scapy` to modify them. This was difficult to get working due to python version issues (some pi bookworm specific), pip issues with the no-longer-maintained `netfilterqueue` as well as just figuring out the script (like knowing to delete the checksums in the ip and tcp layer). For that reason, this 'attack' is pretty simple and all we do is change 'OK' to 'NO' in http resposes from the reolink web server with status code 200.
However, it did work
![http.png](./photos/attack/http.png)
See [final_2.pcap](./captures/final_2.pcap)
#### 6c.

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,3 +1,4 @@
#Created with some assitance from ChatGPT for event handling mechanism
import os
import signal
import sys
@ -40,7 +41,6 @@ def capture_traffic(interface, ip='192.168.0.56', port=5000):
print(f"Sent {filename} to {ip}:{port}")
while not stop_event.is_set():
start_time = time.time()
filename = create_filename(interface)
packets = sniff(iface=interface, timeout=30*1, stop_filter=lambda x: stop_event.is_set())
save_packets(packets, filename)

36
final/scripts/http.py Normal file
View File

@ -0,0 +1,36 @@
from netfilterqueue import NetfilterQueue, Packet as NFQPacket
from scapy.all import IP, TCP, Packet as SPacket, Raw, Ether, send
# from scapy.layers.ipsec import IP
from scapy.layers.http import HTTPResponse, HTTP
def process_packet(nfqpacket: NFQPacket):
scapy_packet: SPacket = IP(nfqpacket.get_payload())
if scapy_packet.haslayer(IP) and scapy_packet.haslayer(TCP) and scapy_packet.haslayer(HTTP) and scapy_packet.haslayer(HTTPResponse):
if scapy_packet[IP][TCP][HTTP][HTTPResponse].fields['Status_Code'] == b'200':
print(f"Original Packet: {scapy_packet.summary()}")
new_packet: SPacket = IP(scapy_packet.build().replace(b'OK',b'NO'))
del new_packet[IP].chksum
del new_packet[TCP].chksum
print(f"Modified Packet: {new_packet.summary()}")
print()
nfqpacket.set_payload(new_packet.build())
nfqpacket.accept()
def main():
nfqueue = NetfilterQueue()
nfqueue.bind(0, process_packet)
try:
print("Binding to NFQUEUE 0...")
nfqueue.run()
except KeyboardInterrupt:
print("Stopping...")
finally:
nfqueue.unbind()
if __name__ == "__main__":
main()

View File

@ -1,65 +0,0 @@
import sys
from scapy.all import sniff, send, IP, IPv6, Packet, Raw, Ether, sendp, base64_bytes, hex_bytes,bytes_base64
from scapy.layers.inet import UDP
from scapy.layers.rtp import RTP, bind_layers
from datetime import datetime
def invert_byte(byte):
return ~byte & 0xFF
def modify_packet(packet: Packet):
global modifications
global seen
udp: Packet = packet[UDP]
# udp.show()
if RTP in udp:
rtp: Packet = udp[RTP]
# load: bytes = rtp.load
if rtp.payload_type == 96:
print("96")
# payload: bytes = rtp.payload.load
# pretty = bytes_base64(payload)
# print(payload)
# hex: str = payload.hex()
# new_hex = hex[::-1]
# print(hex)
# print()
# print(new_hex)
# print(type(new_payload))
# rtp.payload = Raw(hex_bytes(new_hex))
original_payload = bytes(rtp.payload)
new_payload_hex = "deadbeef" # New payload in hex format
new_payload = bytes.fromhex(new_payload_hex)
rtp.payload = Raw(new_payload)
udp.len = len(udp)
udp.chksum = None # Recalculate checksum
# payload[6] = invert_byte(payload[6])
else:
print('npe')
seen += 1
sendp(packet, iface=sys.argv[1])
if __name__ == "__main__":
modifications = 0
seen = 0
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <interface>")
sys.exit(1)
interface = sys.argv[1]
start_time = datetime.now()
print(f"Script started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
bind_layers(UDP, RTP, sport=6970)
packets = sniff(filter="udp port 6970", iface=interface, timeout=60*1, prn=modify_packet)
end_time = datetime.now()
print(f"Script ended at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{modifications} ip(v6) packets modified")
print(f"{seen} total ip(v6) packets seen")

View File

@ -1,12 +1,15 @@
import sys
from scapy.all import sniff, send, IP, IPv6, Packet, Raw, Ether, sendp
from scapy.all import sniff, send, IP, IPv6, TCP, Packet, Raw, Ether, sendp, wrpcap, PacketList, HTTPResponse
from datetime import datetime
def save_packets(packets, filename):
wrpcap(filename, packets)
print(f"Saved {len(packets)} packets to {filename}")
def modify_packet(packet: Packet):
global modifications
global seen
if packet.haslayer(IP):
if packet.haslayer(IP) and packet.haslayer(TCP) and packet.haslayer(HTTPResponse):
if packet[IP].ttl != 65:
packet[IP].ttl = 65
modifications += 1
@ -16,7 +19,7 @@ def modify_packet(packet: Packet):
modifications += 1
seen += 1
#Can be extented with other protocols which have TTL
sendp(packet, iface=sys.argv[1])
sendp(packet, iface='eth0')
if __name__ == "__main__":
@ -31,8 +34,8 @@ if __name__ == "__main__":
start_time = datetime.now()
print(f"Script started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
packets = sniff(filter="ip or ip6", iface=interface, timeout=2.5*1, prn=modify_packet)
packets: PacketList = sniff(filter="ip or ip6", iface='eth1', timeout=5*1, prn=modify_packet)
# save_packets(packets, "final_1.pcap")
end_time = datetime.now()
print(f"Script ended at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{modifications} ip(v6) packets modified")