-
Notifications
You must be signed in to change notification settings - Fork 60
Case Study. Part 1: How does Polymorph work?
The way Polymorph works is quite simple. The first step is to capture a network packet equivalent to the type of network packets you wish to modify on the fly afterwards. To do this, we execute the following command in Polymorph's main interface:
PH > capture -i lo -f icmp
[+] Waiting for packets...
(Press Ctr-C to exit)
The capture
command performs network traffic sniffing in the same way that other utilities such as wireshark or tcpdump do. With the -f
option you can set a filter for the network packets you want to capture. For more information on this command, you can use the -h
option.
After running the capture
command, the next thing we need to do is generate network traffic containing network packets equivalent to the ones we want to modify on the fly. In this case, we generate ICMP traffic.
root@kali:/home/kali# ping localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.020 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.044 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.075 ms
^C
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2039ms
rtt min/avg/max/mdev = 0.020/0.046/0.075/0.022 ms
Once the ICMP traffic is generated, we can stop Polymorph's sniffing process with the combination ctrl + C
, Polymorph will parse the captured network packets and generate a data structure called template.
PH > capture -i lo -f icmp
[+] Waiting for packets...
(Press Ctr-C to exit)
[+] Parsing packet: 12
[+] Parsing complete!
PH:cap >
The template has a dissection of all the layers and fields of the different network protocols that implement the captured network packets. Among the attributes it includes for each of the fields are: the field type, the fixed position it occupies within the packet bytes, its representation as a function of type, its value in bytes...
To view all the templates that have been generated from the capture made, we can use the command show
:
PH:cap > show
1 Template: ETH / IP / ICMP
2 Template: ETH / IP / ICMP
3 Template: ETH / IP / ICMP
4 Template: ETH / IP / ICMP
5 Template: ETH / IP / ICMP
6 Template: ETH / IP / ICMP
7 Template: ETH / IP / ICMP
8 Template: ETH / IP / ICMP
9 Template: ETH / IP / ICMP
10 Template: ETH / IP / ICMP
11 Template: ETH / IP / ICMP
12 Template: ETH / IP / ICMP
PH:cap >
At this point, what we must do is select the template that corresponds to the type of network packet that we would like to modify on the fly. To do this we use the command template
followed by the template number.
If we want to see more detail about which network packet each of the generated templates corresponds to, we can use the wireshark
command that will open this application with the captured network packets.
PH:cap > show
1 Template: ETH / IP / ICMP
2 Template: ETH / IP / ICMP
3 Template: ETH / IP / ICMP
4 Template: ETH / IP / ICMP
5 Template: ETH / IP / ICMP
6 Template: ETH / IP / ICMP
7 Template: ETH / IP / ICMP
8 Template: ETH / IP / ICMP
9 Template: ETH / IP / ICMP
10 Template: ETH / IP / ICMP
11 Template: ETH / IP / ICMP
12 Template: ETH / IP / ICMP
PH:cap > template 1
PH:cap/t1 >
After doing the above, we are in the interface that will allow us to perform different actions on the selected template and modify network packets on the fly using this template as a reference. Let's start by visualizing the template's content with the show
command:
PH:cap/t1 > show
---[ ETH ]---
FT_ETHER dst = 00:00:00:00:00:00
FT_ETHER src = 00:00:00:00:00:00
FT_HEX type = 0x800
---[ IP ]---
FT_BIN_BE version = 4
FT_BIN_BE hdr_len = 5
FT_HEX dsfield = 0x0
FT_INT_BE len = 84
FT_HEX id = 0xa3fd
FT_HEX flags = 0x4000
FT_INT_BE ttl = 64
FT_INT_BE proto = 1
FT_HEX checksum = 0x98a9
FT_IPv4 src = 127.0.0.1
FT_IPv4 addr = 127.0.0.1
---[ ICMP ]---
FT_INT_BE type = 8
FT_INT_BE code = 0
FT_HEX checksum = 0x41a8
FT_INT_BE ident = 13880
FT_INT_BE seq = 1
FT_ABSOLUTE_TIME data_time= Sep 30, 2020 13:35:20.000000
FT_BYTES data = b'j*\n\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567'
As can be seen, Polymorph has dissected the ICMP network packet by identifying a set of layers (Ethernet, IP, ICMP) and a set of fields for each of the layers.
Polymorph now "knows" what an ICMP network packet looks like, what position each of the fields occupies within the network packet bytes, and stores as a reference the values that the captured network packet had for each of the fields.
The great advantage of this, is that now Polymorph will allow us to modify network packets in real time by accessing each of the fields of the captured network packet using the names shown in the template for each of the fields.
To understand how this works, we are going to add one of the fundamental components of the framework, a function
. As the name implies, functions
are code snippets in Python 3 that will be executed on network packets in transit. We can add as many functions
as we want, each of which will be executed sequentially on the intercepted network packets.
To add a function
we execute the command shown below. The -a
option indicates that we are adding a new function
, the -e
option indicates the text editor with which we want to edit this function.
PH:cap/t1 > functions -a filter_icmp_packets -e emacs
When this command is executed, the selected text editor will be opened with a code skeleton in Python 3 similar to the following one:
def filter_icmp_packets(packet):
# your code here
# If the condition is meet
return packet
The three most important things we should know about functions
are
- The
packet
parameter represents the packet that is being intercepted in real time at that moment - We can access the contents of the packet that is being intercepted through the name of the layers and fields that have been generated in the template
- If we want to continue executing other
functions
that we have added, the function must returnpacket
, otherwise, it must returnNone
.
With these main points in mind, we are going to create our first function
that will take care of filtering ICMP network packets request and reply.
def filter_icmp_packets(packet):
try:
if packet['IP']['proto'] == 1:
if packet['ICMP']['type'] == 8:
print("ICMP Request. Executing next function...")
return packet
elif packet['ICMP']['type'] == 0:
print("ICMP Reply. Executing next function...")
return packet
except:
return None
Save, close the text editor, and the function will be automatically added to Polymorph.
PH:cap/t1 > functions -a filter_icmp_packets -e emacs
[+] Function filter_icmp_packets added
We can visualize the functions
that we have active and the order with the command functions -s
:
PH:cap/t1 > functions -s
+-------+-------------------------------------------------------------------+
| Order | Functions |
+=======+===================================================================+
| 0 | def filter_icmp_packets(packet): |
| | try: |
| | if packet['IP']['proto'] == 1: |
| | if packet['ICMP']['type'] == 8: |
| | print("ICMP Request. Executing next function...") |
| | return packet |
| | elif packet['ICMP']['type'] == 0: |
| | print("ICMP Reply. Executing next function...") |
| | return packet |
| | except: |
| | return None |
| | |
+-------+-------------------------------------------------------------------+
Once the function
is added, it is time to test it by intercepting network traffic in real time. To do this, we execute the intercept -localhost
command, which will put Polymorph in intercept mode in the loopback interface.
PH:cap/t1 > intercept -localhost
[*] Waiting for packets...
(Press Ctrl-C to exit)
Now all we have to do is generate network traffic on the system and check the execution of the function on the network packets. If all goes well, we should see a result similar to the one shown below when we generate ICMP traffic by executing the ping localhost
command on a terminal.
PH:cap/t1 > intercept -localhost
[*] Waiting for packets...
(Press Ctrl-C to exit)
ICMP Request. Executing next function...
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
ICMP Reply. Executing next function...
The possibilities with functions
are as many as we can think of, for example, we are going to create a new function
to modify the data
field of the ICMP Request network packets.
def modify_icmp(packet):
if packet['ICMP']['type'] == 8:
packet['ICMP']['data'] = packet['ICMP']['data'][:-8] + b'newvalue'
print("New value inserted")
return packet
The most important thing to keep in mind, is that both to compare a value, as to add a new value to a field, we must respect the type shown in the template. In this case the type is FT_BYTES
that indicates us that this field must receive a value in bytes.
We execute again the command intercept -localhost
and generate ICMP traffic.
PH:cap/t1 > intercept -localhost
[*] Waiting for packets...
(Press Ctrl-C to exit)
ICMP Request. Executing next function...
New value inserted
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
New value inserted
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
New value inserted
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
New value inserted
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
New value inserted
ICMP Reply. Executing next function...
ICMP Request. Executing next function...
New value inserted
ICMP Reply. Executing next function...
We can observe how after executing the command ping localhost
the machine does not receive a response, this is because we are modifying the content of the ping request packets on the fly, which causes that when the ping reply arrives the value of the data
field does not coincide with the one originally sent. We can check this if we use a utility such as wireshark
at the same time as modifying the network packets with Polymorph.
0000 00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00 ..............E.
0010 00 54 d3 6a 40 00 40 01 69 3c 7f 00 00 01 7f 00 .T.j@.@.i<......
0020 00 01 08 00 70 fe 37 3b 00 01 5e ce 74 5f 00 00 ....p.7;..^.t_..
0030 00 00 ca e7 03 00 00 00 00 00 10 11 12 13 14 15 ................
0040 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 .......... !"#$%
0050 26 27 28 29 2a 2b 2c 2d 2e 2f 6e 65 77 76 61 6c &'()*+,-./newval
0060 75 65 ue
With the command Ctr + C
we leave the intercept
mode and with the command save -p path
we save the template on disk to be able to use it later without the need to add the functions
again.
PH:cap/t1 > save -p icmp_template
[+] Template saved to disk
Finally, we can exit Polymorph with the exit
command.
PH:cap/t1 > exit
Are you sure you want to exit? (y/n) y
Bye Bye! See you soon!