I’ve been dabbling for a while now with scapy. A quick cursory examination and you can tell that this tool has a lot of flexibility and power. It takes a bit of time to learn the method of packet building and the arguments to the various methods. In one of my first experiments with the tool I thought I’d attempt to detect systems running the nifty, if not completely insecure, tool known as ICMP Shell. This tool gives you access to a remote shell and communication to and from this system is over ICMP. The default behavior is to send and receive ICMP echo-reply messages (icmp type 0) with the id set to 60165. Using the provided ish client all this is handled for you once you give it the IP address of the listening system. Since we aren’t using the client the first order of business is to craft a packet that looks similar to the one generated by the client.
Here’s a walkthrough of the packet construction. But, before I begin, I used the ‘pre’ tags and because of that the lines do not wrap properly. In fact, it looks quite bad.
Welcome to Scapy (1.0.4.24beta)
>>> ip = IP(dst="172.20.62.0/24", len=59)
All I need for a valid IP packet is the destination IP address and, because I’m using padding, a correct total packet length. There may be a way to automate this but I haven’t figured it out yet. You may be wondering why I have not specified any other options and this is because scapy picks sane defaults. To see which defaults it has chosen for this packet use ls(ip).
>>> icmp = ICMP(type="echo-reply",id=60165)
It is the same story here as well. I only need to provide the appropriate icmp type/code and also, because ICMP shell is expecting it, an ICMP identifier.
>>> id = str('x69x64x0a')
>>> payload = str('x00' * 20) + str('x02') + str('x00' * 7) + id
>>> padding=Padding(payload)
The ICMP shell client pads the ICMP message with some data and if I do not duplicate the appropriate positions of this data it will be rejected by the ICMP shell server. Using a bit of python to save typing I’ve created the necessary padding.
>>> packet=ip/icmp/padding
Since we’ve already created placeholders for the various parts we only need to glue them together to create a valid IP packet. Typing the name of the variable outputs the constructed object. Of course, we could have saved some typing and done something like this:
>>>packet=IP(dst="172.20.62.0/24", len=59)/ICMP(type="echo-reply", id=60165)/padding
And that would have done the same thing assuming we defined padding appropriately. After this we're ready to inject these packets on the wire. When we're ready we type:
>>>send(packet)
There are many other methods we can use to send packets, control timeout, retries and other behaviors. This particular method sends packets at layer three without keeping track of responses. We are, at first, going to use TCPdump to track our responses. Below is what a standard ICMP Shell negotiation looks like.
$ sudo tcpdump -nexXs 1500 -i eth0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 1500 bytes
16:49:18.665206 00:11:25:47:72:b6 > 00:0b:cd:b8:7f:07, ethertype IPv4 (0x0800), length 73: 172.20.62.20 > 172.20.62.40: ICMP echo reply, id 60165, seq 0, length 39
0x0000: 4500 003b 0000 4000 4001 665d ac14 3e14 E..;..@.@.f]..>.
0x0010: ac14 3e28 0000 9f95 eb05 0000 0000 0000 ..>(............
0x0020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0030: 0200 0000 0000 0000 6964 0a ........id.
16:49:18.669660 00:0b:cd:b8:7f:07 > 00:11:25:47:72:b6, ethertype IPv4 (0x0800), length 109: 172.20.62.40 > 172.20.62.20: ICMP echo reply, id 60165, seq 11264, length 75
0x0000: 4500 005f 0000 4000 4001 6639 ac14 3e28 E.._..@.@.f9..>(
0x0010: ac14 3e14 0000 32bd eb05 2c00 0000 0000 ..>...2...,.....
0x0020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0030: 0000 0000 0000 0000 7569 643d 3028 726f ........uid=0(ro
0x0040: 6f74 2920 6769 643d 3028 726f 6f74 2920 ot).gid=0(root).
0x0050: 6772 6f75 7073 3d30 2872 6f6f 7429 0a groups=0(root).
And here is what the scapy injected packed looks like.
16:50:35.649054 00:11:25:47:72:b6 > 00:0b:cd:b8:7f:07, ethertype IPv4 (0×0800), length 73: 172.20.62.20 > 172.20.62.40: ICMP echo reply, id 60165, seq 0, length 39
0×0000: 4500 003b 0001 0000 4001 a65c ac14 3e14 E..;….@….>.
0×0010: ac14 3e28 0000 14fa eb05 0000 0000 0000 ..>(…………
0×0020: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0×0030: 0200 0000 0000 0000 6964 0a ……..id.The result, again in TCPdump is
16:50:35.656251 00:0b:cd:b8:7f:07 > 00:11:25:47:72:b6, ethertype IPv4 (0×0800), length 109: 172.20.62.40 > 172.20.62.20: ICMP echo reply, id 60165, seq 11520, length 75
0×0000: 4500 005f 0000 4000 4001 6639 ac14 3e28 E.._..@.@.f9..>(
0×0010: ac14 3e14 0000 31bd eb05 2d00 0000 0000 ..>…1…-…..
0×0020: 0000 0000 0000 0000 0000 0000 0000 0000 …………….
0×0030: 0000 0000 0000 0000 7569 643d 3028 726f ……..uid=0(ro
0×0040: 6f74 2920 6769 643d 3028 726f 6f74 2920 ot).gid=0(root).
0×0050: 6772 6f75 7073 3d30 2872 6f6f 7429 0a groups=0(root).
Looks like we found a valid system that has ICMP Shell running on it. Hats off to scapy.
But, isn’t TCPdump a bit slow. Can’t we use scapy to filter through packets and show us only those that meet our criteria? Of course! Scapy has a sniff method that takes quite a few arguments. I have the minimum to get the job done.
>>> sniff(filter="icmp", prn=lambda x: x.sprintf("Found IShell: %IP.dst% > %IP.src% -- %Raw.load%"),lfilter=lambda x: str(x[Raw]).find("uid") != -1)
We want only icmp packets, we want to print some output and finally with the lfilter we’re telling the system to only deliver packets to the prn method that meet the criteria. In this case we’re looking for ‘uid’ in the payload of the ICMP packet. If uid is found we print out the source, destination and the payload for verification. I found out the long way the the lambda expressions only take simple statements. At first I tried to pack everything into the prn argument, but that failed miserably. Eventually I stumbled upon the lfilter and the realization of the statement requirement. Once you get past this I think it is smooth sailing.
There it is. Some basic features of scapy that, hopefully, demonstrate scapy’s flexibility and utility.