11 months ago

With the craziness of the holiday season and travel over, I figured I'd jump on blog post #2. One of the relatively new concepts to me in SEC660 was packet manipulation with Scapy. If you've taken any of the other SANS classes like 560, or deal with IDS/Firewalling/Network Forensics, youre probably familiar with how powerful it is - I'd heard of it in passing, but never had hands on it. So as a refresher, i spent a few wonderfully frustrating hours writing a simple data exfiltration tool in Python3 using Scapy. I wanted to do the following:

  1. Write a script that would create an ICMP packet using Scapy
  2. Add a payload I defined and could change
  3. Send the ICMP packet to a target host, again using Scapy
  4. On the target host, run a program that sniffs packets with Scapy
  5. When an ICMP packet was detected on the target host, print the payload.

Seems easy enough, right? I thought so, too. First thing's first, make sure Python3 and Scapy are installed (pip3 install scapy should do it). Scapy has an interactive interface you can run with the 'scapy' command at a terminal:

In the interactive console, we can run commands directly without using Python or another language. Since I wanted to send an ICMP packet, I looked up at how to craft a simple ICMP packet itself; at a minimum, we can use the following:

 IP(dst="target")/ICMP()

So to send a single, simple ICMP packet to Google's DNS server at 8.8.8.8, we'd use:

 IP(dst="8.8.8.8")/ICMP()

Easy enough. Now we need a mechanism to send packets. There are three we'll talk about here; send, sr, and sr1. Send does just what you think it'd do; it sends the packet. This is fine, our data gets out and to our target assuming the network is doing what it should. But thats not much help for things like ICMP, where we expect a reply. Thats where sr comes in handy - it sends and receives packets. Finally, sr1 sends and receives one single packet. We can wrap any of these around the constructed packet above:

 send(IP(dst="8.8.8.8")/ICMP())

Well, it does what it says on the box, but not much beyond that. Lets try sr:

 sr(IP(dst="8.8.8.8")/ICMP())

Thats much more useful. We can play around with it a bit more, and assign it to a variable, then do things like call the summary:

You can probably see this being pretty useful in both offensive and defensive security. Now that I was off to a good start, I fired up SublimeText and started putting together a basic script to send ICMP packets based on user input. Thats where I ran into a huge headache. Long story short, and let me save you a whole bunch of headache: on a system with multiple interfaces, Scapy doesn't always select the one you want to use. That means if its selected lo0 (your loopback interface), you're going nowhere fast. If it's selected tun0, and you arent on VPN, same deal. You can both print your current interface and change it using the following commands:

 conf.iface
 conf.iface = "your network interface (eth0/en0/etc)" 

Set this before you go much farther, and set it in your script; otherwise, your packets are going to go out of a disconnected or nonroutable interface. However, this gave me a great opportunity to build in an automatic interface detector which I'll discuss in a bit. First lets build out a basic python script:

        #!/usr/bin/env python3
        from scapy.all import *

        conf.iface = "en0"

        scapypkt = IP(dst="8.8.8.8")/ICMP()
        send_packet = sr(scapypkt)
        print("Packet Sent!")

If we run the program with Wireshark open, we can see the packet send:

So, the payload. Right now we're sending empty packets - by making a simple modification to the packet construction, we can add in a payload as well:

 sr(IP(dst="8.8.8.8")/ICMP()/"pwned!")

Now when we send the packet, heres what we see in Wireshark:

So this got me thinking - could we simply use the payload field to stuff data from one system and send it to another with Scapy? Absolutely.

I'll save you the trouble of writing a script and explaining Python code, but here's what I came up with: https://github.com/highmeh/gxpn_study/blob/master/scapy_icmp_payload.py

The script is basic, but it has the following:

  • Uses argparse to take user input. Users can enter a target IP, a payload, an interface, or have the script auto-select the interface.
  • If the user chooses to auto-select the interface, the script uses 'netifaces' to parse through all interfaces, and check for the presence of an IP address. If an IP address exists, it tries to ping 8.8.8.8. The first interface that meets these criteria is assigned as the active interface using conf.iface.
  • It builds and sends the package with the user's payload included.

So lets give it a whirl:

We can see in Wireshark the packet was received and the payload is included. You can also see the host reaching out to 8.8.8.8 in an earlier packet, during the interface auto-selection process.

So now that we're able to send payloads, I wanted to create a simple listener I could run on another machine to parse out the payload portion. Here's the script I came up with:

https://github.com/highmeh/gxpn_study/blob/master/scapy_sniff_icmp_payload.py

This is a pretty straightforward script. It uses Scapy's "sniff" function to look for an ICMP packet. When it receives one, it strips out and prints the payload to the screen, then exits. This could be the start of a very, very basic ICMP C2 server - with the other script being a data exfiltration tool. Lets see what happens when we try to send the hostname from our main host to our sniffer:

There we have it - the sniffer script captures the ICMP packet, strips out the payload, and prints it. Wireshark catches it as well.

This isn't anything groundbreaking, or even well coded - but I needed a small project to put together in a few hours that would refresh my memory on Scapy before diving into the SEC660 books and reviewing/indexing. Worked pretty well; I'm looking forward to using Scapy more both during cert prep and during engagements at work.

← GXPN Prep 1: Basic Stack-Based Buffer Overflow