Altering packets on the fly with scapy as a MITM - python

Assuming I managed to be in the middle of the communication between a client and a server (let's say that I open up a hotspot and cause the client to connect to the server only through my machine).
How can I alter packets that my client sends and receives without interrupting my own communication with other services? There must be a way to route all of the packets the client both sends and is about to receive (before forwarding them to him) through my script.
I think that the correct direction of going about accomplishing this is with iptables but not sure exactly what arguments would fit to make this work. I already have the following simple script:
hotspotd start #a script that runs dnsmasq as both a DNS and DHCP server, configures and starts a hotspot
iptables -P FORWARD ACCEPT
iptables --append FORWARD --in-interface wlan0 -j ACCEPT
iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE
#wlan0 is the interface on which the hotspot is.
#eth0 is the interface that is connected to the internet
Now, this perfectly works for a passive MITM - I can see everything that the client sends and receives. But now I want to step it up and redirect every message he sends and receives through me.
My eventual purpose is to get to a level where I could execute the following script:
from scapy.all import *
from scapy_http.http import *
def callback(pkt):
#Process the packet here, see source and destination addresses, ports, data
send(pkt)
sniff(filter='port 666', prn=callback) #Assuming all relevant packets are redirected to port 666
How do I accomplish redirecting every packet the client sends and is-about-to-receive?

You can use NFQUEUE which has python bindings.
NFQUEUE is a userspace queue that is a valid iptables target. You can redirect some traffic to the NFQUQUE:
iptables -I INPUT -d 192.168.0.0/24 -j NFQUEUE --queue-num 1
Then access the packets from your code:
from netfilterqueue import NetfilterQueue
def print_and_accept(pkt):
print(pkt)
pkt.accept()
nfqueue = NetfilterQueue()
nfqueue.bind(1, print_and_accept)
try:
nfqueue.run()
except KeyboardInterrupt:
print('')
nfqueue.unbind()
Note the pkt.accept() call. This returns a verdict to the nfqueue, telling it that it should accept the packet - i.e. allow it to continue along its normal route in the kernel. To modify a packet, instead of accepting it, you'd need to copy it, return a drop verdict, and finally resend it with the included modifications.

Related

Sending packet with python and scapy to local address doesn't work

I am trying to send a udp packet to a local ip address. This is my example code:
from scapy.all import *
if __name__ == "__main__":
send(IP(dst="127.0.0.1")/UDP(sport=19600,dport=39600)/"abc")
I've started netcat to catch what I am going to send:
nc -ul 39600
Then I am executing the code:
python3 example_scapy_send.py
Nothing is received by the listening netcat.
At the same time I have started wireshark and I can see the packet is sent.
If I send a packet using netcat it is ariving on the listening netcat.
usr#dev:/home/usr# nc -u 127.0.0.1 39600
test
Wireshark:
The only difference I can see is that at layer 2 - destination address is multicast/broadcast when sent with scapy and unicast when sent with netcat. But this is not something I can control.
If I sent the same packet with scapy to another ip on the network (another host) the packet is received (by netcat). So the issue applies only if I am sending to a local address. Tested with any local ip. Not only 127.0.0.1. I've also tested with sendp and sr scapy functions but the result is the same.
Something more: if I've started another scapy script to listen to UDP/39600 (instead of netcat) I can see/I am receiving the packet I've sent.
Any ideas what is wrong?
tests done under ubuntu/scapy 2.5/python 3.8
I couldn't find a way to make it work with send/sendp scapy functions, but instead I tried using standart python socket and it did the job:
someSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
someSocket.sendto(bytes('abc', "utf-8"), (127.0.0.1, 39600))
Acording to Scapy troubleshooting:
The loopback interface is a very special interface. Packets going through it are not really assembled and disassembled. The kernel routes the packet to its destination while it is still stored an internal structure. What you see with tcpdump -i lo is only a fake to make you think everything is normal. The kernel is not aware of what Scapy is doing behind his back, so what you see on the loopback interface is also a fake. Except this one did not come from a local structure. Thus the kernel will never receive it.
On Linux, in order to speak to local IPv4 applications, you need to build your packets one layer upper, using a PF_INET/SOCK_RAW socket instead of a PF_PACKET/SOCK_RAW (or its equivalent on other systems than Linux)
So you may need to add line before sending packet:
conf.L3socket = L3RawSocket
In your script. That way everything should supposed to work. At least in my environment worked out fine.

scapy takes infinite time sending TCP SYN packet with no reply.

I want to send a simple SYN request to my router to get an ACK response in a bid to learn network (TCP/IP) using python and scapy.
But scapy is taking a long time of getting any answer.
script-
#!/usr/bin/env python
from scapy.all import *
pack=TCP(sport=22,dport=80,flags='S')/IP(src="192.168.0.13",dst="192.168.0.1")
# tried with retry and timeout options using both sr() and sr1()
# but it comes with no answer from the router.
# ran this with sudo and iptables policy is default [ACCEPT]
ans = sr1(pack)
What is the solution ?
So to summarize, you need to reverse IP and TCP order.
What people call TCP/IP is indeed a TCP layer stacked on top of an IP layer.
Here is for instance the stack of basic layers:
Each stack adds new informations: Ethernet gives the MAC addresses so that the packet goes to the router. Then IP tells to which computer the data should be sent. TCP finally announces some data, extensions.....
It needs to be in this order: you need to know where the packet needs to go before specifying the data

Modify data in outgoing UDP packets on linux

The RADIUS part of it is not important to the query, but thought I would explain the context:
I have a linux based radius server that always includes a certain attribute in the "Access-Accept" packet, that one of my network devices does not like. The server does not have an option to disable said attribute. I was looking into the possibility of using a script to modify the outgoing access accept to remove the attribute and recalculate the checksum and Radius authenticator fields.
I will be able to code a python script which creates a UDP socket to listen on and then modify the radius packet as required. The part I am stuck with is:
(A) How to redirect outgoing traffic to my script's socket?
(B) How to ensure source and destination IP/port remain the same?
Doing some research for (A), it looks like we can use iptables to redirect incoming data to a script:
The suggested command in the above link:
iptables -t nat -D PREROUTING -s yourhost -d desthost -p tcp --dport 80 -j REDIRECT --to 10101
However, I need to modify the outgoing data.
I am guessing I need to modify PREROUTING to POSTROUTING
There also seems to be a NAT keyword. I am unsure how that will affect the IP addresses.
I am also concerned, if my script does send out the modified packet with the same IPs/Ports, will it not hit the iptable rule once again, and get stuck in an infinite loop? How do I avoid this?
For query (B), I suppose using Scapy is an option to ensure IP/port remains the same in the modified packet. If there is an easier option, I would like to know that.
Any help with the above would be truly appreciated.
You have to use the NFQUEUE iptables functionality: if a packet matches your rule, it will be redirected in userspace to a specific queue.
For instance, to intercept all radius packet to your "non-standard" network device:
iptables -A OUTPUT -d <device> -p udp --sport <radius_port> -j NFQUEUE --queue-num 1
Then you have to run a script that binds to the Nfqueue and modifies the packets accordingly.
A small python sample with nfqueue and scapy (you'll need to install the dependancies):
from scapy.all import *
from netfilterqueue import NetfilterQueue
queueId = 1
def doStuff(packet):
[do your packet mangling here]
packet.set_payload(str(modified_packet_payload))
packet.accept()
# bind the callback function to the queue
nfqueue = NetfilterQueue()
nfqueue.bind(queueId, doStuff)
try:
nfqueue.run()
except KeyboardInterrupt:
pass

How to both read and write from a TAP device connected to a MACVLAN via a bridge?

Let me describe my setup.
Using Debian. I've got eth0 connected to my home router. A MACVLAN (named m0) is created from eth0. A TAP device (named tap0) is created using tunctl. Finally, a bridge (named br0) is created using bridge-utils and both m0 and tap0 are connected to the bridge.
Using ping, I was able to capture an example ICMP request from tcpdump. First, I used dhclient to get br0 an IP address from the home router. Next, I ping the router from br0. I then capture this packet in tcpdump.
Next, I write to the tap0 interface in Python. Once I get the file object, I copy the example ICMP request from before (it has IP address from br0 to 192.168.1.1, which is home router), convert it to binary, and then write it to the file object.
Using tcpdump, I can see the packet I wrote to the file object in Python appear just as intended -- an ICMP echo request. I can see this packet on every interface in the chain (tap0, br0, m0, eth0).
Here's the problem I'm trying to tackle. I can see the ICMP echo reply on every interface in the chain, except tap0 (br0, m0, eth0) using tcpdump. Ideally, I want to read from the file object in Python to see the ICMP echo reply there. It makes sense that what I have doesn't work, since the bridge is the one with the IP address in the reply packet. How do I modify this setup such that I can both read and write from tap0?
dhclient doesn't work with tap0, however. The only way I could imagine this working is somehow finding a way to give tap0 an IP address that is known by the router. I hope I don't have to implement a dhclient in Python that writes through the tap0 file object.
This setup was inspired by the networking needed in VMs. I'm trying to experiment with the system infrastructure necessary for a user space program to write data to the wire and being able to read the data back from the wire.
Thanks for the responses and sorry for the somewhat lengthy post.
Commands from Terminal to:
Create macvlan m0: sudo ip link add link eth0 m0 type macvlan
Create TAP tap0: sudo tunctl -u root
Initialization of network devices: sudo ip addr add 0.0.0.0 dev m0/tap0 sudo ip link set dev m0/tap0 promisc on
Create and link bridge to m0 and tap0: sudo brctl addbr br0 sudo brctl addif br0 tap0/m0 sudo ip link set dev br0 up sudo dhclient br0
In Python, I'm doing stuff similar to the initialization steps of https://gist.github.com/glacjay/585369
From there I just have a file object referring to the tap0 interface that I open with os.fdopen(TAP0_FD, 'r+b') and that I can write to and read from, as in the explanation above.

Unwanted RST TCP packet with Scapy

In order to understand how TCP works, I tried to forge my own TCP SYN/SYN-ACK/ACK (based on the tutorial: http://www.thice.nl/creating-ack-get-packets-with-scapy/ ).
The problem is that whenever my computer recieve the SYN-ACK from the server, it generates a RST packet that stops the connection process.
I tried on a OS X Lion and on a Ubuntu 10.10 Maverick Meerkat, both reset the connection. I found this: http://lkml.indiana.edu/hypermail/linux/net/0404.2/0021.html, I don't know if it is the reason.
Does anyone could tell me what could be the reason? And how to avoid this problem?
Thank you.
The article you cited makes this pretty clear...
Since you are not completing the full TCP handshake your operating system might try to take control and can start sending RST (reset) packets, to avoid this we can use iptables:
iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.1.20 -j DROP
Essentially, the problem is that scapy runs in user space, and the linux kernel will receive the SYN-ACK first. The kernel will send a RST because it won't have a socket open on the port number in question, before you have a chance to do anything with scapy.
The solution (as the blog mentions) is to firewall your kernel from sending a RST packet.
I don't have a non-iptables answer, but one can fix the reset issue. Instead of trying to filter the outgoing reset in the filter table, filter all of the incoming packets from the target in the raw table instead. This prevents the return packets from the target from even being processed by the kernel, though scapy still sees them. I used the following syntax:
iptables -t raw -A PREROUTING -p tcp --dport <source port I use for scapy traffic> -j DROP
This solution does force me to use the same source port for my traffic; feel free to use your own iptables-fu to identify your target's return packets.
The blog article cited in other answers is not entirely correct. It's not only that you aren't completing the three way handshake, it's that the kernel's IP stack has no idea that there's a connection happening. When it receives the SYN-ACK, it sends a RST-ACK because it's unexpected. Receiving first or last really doesn't enter into it. The stack receiving the SYN-ACK is the issue.
Using IPTables to drop outbound RST packets is a common and valid approach, but sometimes you need to send a RST from Scapy. A more involved but very workable approach is to go lower, generating and responding to ARP with a MAC that is different from the host's. This allows you to have the ability to send and receive anything without any interference from the host.
Clearly this is more effort. Personally, I only take this approach (as opposed to the RST dropping approach) when I actually need to send a RST myself.
I found a solution without IPTables in https://widu.tumblr.com/post/43624355124/suppressing-tcp-rst-on-raw-sockets .
To bypass this problem, simply create a standard TCP socket as a server socket and bind to the requested port. Don’t do accept().
Just socket(), bind() on the port and listen(). This relaxes the kernel and let you do the 3-way handshake.

Categories