October 17, 2017, 09:47:20 AM
Welcome, Guest. Please login or register.

There are two rules for success: #1 Never Tell Everything You Know.

Author Topic: amonSniffer.py  (Read 2542 times)

Offline Amonsec

  • Top Hat Member
  • Prospect
  • ********
  • Posts: 49
  • Internets: +36/-0
  • 1336 working to become 1337
amonSniffer.py
« on: November 02, 2016, 05:50:14 PM »

Hello everybody!
Today I'm proud to share my new network sniffer, so uninstall your Wireshark and git clone my script. Lulz.  :P
I hope he work well, for the moment I try it on my own network and it's OK.

First of all:
- Don't forget to send me a message if you find something wrong, or if you have some ideas in how I can improve my script;
- It's a basic one, so not a lot of options (for the moment);
- I'm actually learning Python so it's not a fully 'professional' script;
- In the script core section, I only pick pieces of my script and simplify the all.
- I'm apologize for my bad English, nonnative English speaker/writer

Link: https://github.com/amonsec/toolbox/tree/master/amon-sniffer
Second of all, I think drop a script here without any details or informations on how he work, is a bad thing for non-familiar in Python (or network) users.



With what we play
Network packets:
A network packet is a formatted unit of data carried by a packet-switched network. When data is formatted into packets, packet switching is possible and the bandwidth of the communication medium can be better shared among users than with circuit switching.
A packet consists of control information and user data, which is also known as the payload. Control information provides data for delivering the payload, for example: source and destination network addresses, error detection codes, and sequencing information. Typically, control information is found in packet headers and trailers. [1]

Python 2.7:
Python is a widely used high-level, general-purpose, interpreted, dynamic programming language. Its design philosophy emphasizes code readability, and its syntax allows programmers to express concepts in fewer lines of code than possible in languages such as C++ or Java. The language provides constructs intended to enable writing clear programs on both a small and large scale.
Python supports multiple programming paradigms, including object-oriented, imperative and functional programming or procedural styles. It features a dynamic type system and automatic memory management and has a large and comprehensive standard library. [2]



Script core
Code: [Select]
import socket
s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW,  socket.htons(0x0003))
sniffed = s.recv(2048)

If we want to sniff the network we need to use the socket library. [3]
And we need to use different arguments that we can see now:

socket.PF_PACKET            -> is used to receive or send packet as the most basic level/td]
socket.SOCK_RAW            -> provide custom headers for the specific protocol
socket.htons(0x0003 )        -> convert multi-byte integer types from host byte order to network byte order [4]
0x0003                                  ->   is the gateway-gateway protocol (ggp), it's an Internet protocol (cat /etc/protocols) [5]
recv(2048)                            ->   receive data from the network, 2048 is the buffer size.

Code: [Select]
def ethernet_layer_analyze(sniffed):
        try:
            data = struct.unpack('!6s6sH', sniffed[:14])
            mac_destination = binascii.hexlify(data[0])
            mac_source = binascii.hexlify(data[1])
            mac_protocol = data[2]
            if hex(mac_protocol) == "0x800": ip = "1"

Now we gonna parse all network's layers related to the OSI model. We begin with the second layer: Data link. [6]
We have a network packet, a lot of bytes, completely not understandable, so to work with it we need to unpack the packet, with the struct Python's library. [7]   

struct.unpack('!6s6sH', sniffed[:14])
I call to the struct library to unpack my packet. For that I need to use two argument, first the format string, and second my packet (string),for the moment it's easy.
struc.upack() alway return an list object.

sniffed[:14])
'sniffed' is my network packet, and why I only use the first 14bytes? Because we need to get the MAC source address, the MAC destination address and the protocol used. If we take a look about the structure of the Data link we can see we only need 14bytes for this 3 informations. [8]

!6s6sH
A little bit more tricky this time, the first character of the format string can be used to indicate the byte order, size and alignment of the packed data. Here ! for, in order: network (= big-endian); standard; none. [9]

6s              ->6bytes  string 
6s              ->6 more bytes string
H          ->2bytes unsigned short



binascii.hexlify(data[])
Everything is encoded in hexadecimal, so we can easily decode this with the binascii Python library, with binascii.hexlify() to return the hexadecimal representation of the binary data. [10]

if hex(mac_protocol) == "0x800": ip = "1"
If the hexadecimal value of mac_protocol is equal to '0x800' we have IPv4 packet, so we can continue our analyze, else we display the result.

Code: [Select]
def network_layer_analyze(sniffed):
        try:
            network_header = struct.unpack('!6H4s4s', sniffed[:20])
           
            network_version = network_header[0] >> 12
            network_ihl = (network_header[0] >> 8) & 0x0f
            network_typeOfService = network_header[0] & 0x00ff
            network_totalLength = network_header[1]

            network_identification = network_header[2]
            network_flags = network_header[3] >> 13
            network_offset = network_header[3] & 0x1fff

            network_timeToLive = network_header[4] >> 8
            network_protocol = network_header[4] & 0x00ff
            network_headerChecksum = network_header[5]

            network_sourceAddress = socket.inet_ntoa(network_header[6])
            network_destinationAddress = socket.inet_ntoa(network_header[7])

In this section we gonna use really useful operators, called Bitwise operators. [11]
Here I don't gonna explain everything because it takes to much time.

!6H4s4s

6H            ->12bytes unsigned short
4s              ->4bytes for IPv4 source address
4H          ->4bytes for IPv4 destination address

[12]




network_version = network_header[0] >> 12
First we want to get the version, so for that we use the bitwise operators  >>. What that mean? We take everything except the last 12bits.
4bits + 8 bits = 12bits

network_ihl = (network_header[0] >> 8 ) & 0x0f
Same here, we get everything except the last 8bits, but if we do only that we get IHL but the version with it. So we need to use the logical and operator & and set "where to start", here at 0x0f, more exactly which bits we want.

     Version(4bits)           IHL(4bits)     
      0000      1111      <- Binary:  0000 1111
          0          f        <- Hexadecimal: 0x0f

network_typeOfService = network_header[0] & 0x00ff
Practically the same here.
     Version(4bits)           IHL(4bits)          Type of Service(8bits)     
      0000      0000      1111  1111      <- Binary:  0000 0000 1111 1111
          0          0           f f      <- Hexadecimal: 0x00ff




network_totalLength = network_header[1]
Easy to understand, 2bytes.




network_identification = network_header[2]
Easy to understand, 2bytes.

 


network_flags = network_header[3] >> 13
This time we need everything except the last 13bits.

network_offset = network_header[3] & 0x1fff

      Flags(3bits)           Fragment offset(13bits)     
      0000      1 1111 1111 1111      <- Binary:  0001 1111 1111 1111
          1          fff       <- Hexadecimal: 0x1fff




network_timeToLive = network_header[4] >> 8
Everything except the last 8bits.

network_protocol = network_header[4] & 0x00ff

     Time to live(8bits)           Protocol(8bits)     
      0000 0000      1111 1111      <- Binary:  0000 0000 1111 1111
          00          f f       <- Hexadecimal: 0x00ff




network_headerChecksum = network_header[5]
Easy to understand, 2bytes.


Code: [Select]
if network_protocol == 6:
              whoisnext = "TCP"
        elif network_protocol == 17:
              whoisnext = "UDP"

A good part of the work is done now, but we can continue for more information, informations based on protocols. For the moment we have two different choice, TCP (Transmission Control protocol) or UDP (User Datagram protocol). So the question is how we can find if it's TCP or UDP?
The network protocol is an integer, and all integers are  different protocols. For example 6  for TCP and 17 for UDP. [13]



TCP Analyze
Code: [Select]
def tcp_analyze(sniffed):
        try:
            tcp_header = struct.unpack('!2H2I4H', sniffed[:20])

            tcp_offset = tcp_header[4] >> 12
            tcp_reserved = (tcp_header[4] >> 6) & 0x03ff
            tcp_flags = tcp_header[4] & 0x003f

            urg = tcp_flags & 0x0020
            ack = tcp_flags & 0x0010
            psh = tcp_flags & 0x0008
            rst = tcp_flags & 0x0004
            syn = tcp_flags & 0x0002
            fin = tcp_flags & 0x0001

!2H2I4H

2H            ->4bytes unsigned short
2I              ->8byts unsigned int
4H          ->8bytes unsigned short

[14]



tcp_offset = tcp_header[4] >> 12
Everything except the last 12bits.

tcp_reserved = (tcp_header[4] >> 6) & 0x03ff

     Data Offset(4bits)          Reserved(6bits)       
      0000       0011 1111 1111      <- Binary:  0000 0011 1111 1111
          03          f f       <- Hexadecimal: 0x03ff

tcp_flags = tcp_header[4] & 0x003f

     Data Offset(4bits)          Reserved(6bits)             All flags(6bits)           
      0000      0000      0011 1111      <- Binary:  0000 0000 0011 1111
          0          0           3f      <- Hexadecimal: 0x003f

So now, it's a little bit more tricky. We get all different flags from the tcp_flags section we find earlier.

urg = tcp_flags & 0x0020

  URG    ACK    PSH    RST    SYN    FIN 
   1      0      0      0      0      0    Binary: 10 0000
   2     0      0      0      0      0    Hexadecimal: 0x0020

ack = tcp_flags & 0x0010

  URG    ACK    PSH    RST    SYN    FIN 
   0      1      0      0      0      0    Binary: 01 0000
   0     1      0      0      0      0    Hexadecimal: 0x0010


psh = tcp_flags & 0x0008

  URG    ACK    PSH    RST    SYN    FIN 
   0      0      1      0      0      0    Binary: 00 1000
   0     0      8      0      0      0    Hexadecimal: 0x008

rst = tcp_flags & 0x0004

  URG    ACK    PSH    RST    SYN    FIN 
   0      0      0      1      0      0    Binary: 00 0100
   0     0      0      4      0      0    Hexadecimal: 0x0004

syn = tcp_flags & 0x0002

  URG    ACK    PSH    RST    SYN    FIN 
   0      0      0      0      1      0    Binary: 00 0010
   0     0      0      0      2      0    Hexadecimal: 0x0002

fin = tcp_flags & 0x0001

  URG    ACK    PSH    RST    SYN    FIN 
   0      0      0      0      0      1    Binary: 00 0001
   0     0      0      0      0      1    Hexadecimal: 0x0001



UDP Analyze
Code: [Select]
def udp_analyze(sniffed):
        try:
            udp_header = struct.unpack('!4H', sniffed[:8])
            udp_source_port = udp_header[0]
            udp_destination_port = udp_header[1]

            udp_length = udp_header[2]
            udp_checksum = udp_header[3]

!4H
4H -> 8bytes unsigned short

[15]

udp_source_port = udp_header[0]
2bytes.

udp_destination_port = udp_header[1]
2bytes.

udp_length = udp_header[2]
2bytes.

udp_checksum = udp_header[3]
2bytes.


Finally




Ending


I hope I don't suck a lot , and I can help few people with this topic. This is a 'beta' project so I probably need  to fix a lot of thing and to improve it.
Thanks everybody and specially to Top-Hat-Sec forum who give me tons of motivation.

_amonsec.


References (in order)
[1]    https://en.wikipedia.org/wiki/Network_packet
[2]    https://www.python.org/
[3]    https://docs.python.org/2/library/socket.html
[4]    https://beej.us/guide/bgnet/output/html/multipage/htonsman.html
[5]    https://en.wikipedia.org/wiki/Gateway-to-Gateway_Protocol
[6]    https://en.wikipedia.org/wiki/OSI_model   
[7]    https://docs.python.org/2/library/struct.html
[8]    https://en.wikipedia.org/wiki/Ethernet_frame#Structure
[9]    https://docs.python.org/2/library/struct.html#byte-order-size-and-alignment
[10]  https://docs.python.org/2/library/binascii.html
[11]  https://wiki.python.org/moin/BitwiseOperators
[12]  https://tools.ietf.org/html/rfc791#section-3.1
[13]  https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
[14]  https://tools.ietf.org/html/rfc793#section-3.1
[15]  https://tools.ietf.org/html/rfc768

« Last Edit: May 01, 2017, 03:52:53 AM by _amonsec »
"A computer is only as good as it's user" - R4V3N
OSCP (2017)

Offline GalaxyNinja

  • Global Moderator
  • Elite
  • *****
  • Posts: 1728
  • Internets: +96/-0
  • My password is **********
Re: My basic network sniffer
« Reply #1 on: November 04, 2016, 04:01:36 AM »
Awesome _amonsec! I bet you have a much better understanding of how wireshark actually works now having created your own version of it!
What is your tool called? You need a catchy name to get people interested; otherwise there is no name they can give their friends.
Example 1: "hey have you tried that new tool on THS?" "what tool?" "well, I don't know it's name, but it's like wireshark, but it isn't" "um... okay man".
Example 2: "hey have you tried the sniff-i-nator? This dude amonsec made it. It's a wireshark replacement tool" "No I haven't; I'll try it out right away!"

P.S. the sniff-i-nator is probably not a good name.   ;)
A computer is only as strong as its user! -R4v3n

Offline Amonsec

  • Top Hat Member
  • Prospect
  • ********
  • Posts: 49
  • Internets: +36/-0
  • 1336 working to become 1337
Re: amonSniffer.py
« Reply #2 on: November 04, 2016, 01:53:31 PM »
Thanks GalaxyNinja.
Quote
P.S. the sniff-i-nator is probably not a good name.   ;)
Haha nice one!

For the moment I don't really give a name to my script, and yes I learn a lot in network structure.  :)
"A computer is only as good as it's user" - R4V3N
OSCP (2017)