python流量嗅探

有时候,我们需要对客户端流量进行分析,了解未知的协议,但在一些严格企业级环境中,可能会遇到wireshark无法使用的情况,这时候就需要自己去编写一个本地工具以获取指定网卡流量信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#coding=utf-8
"""
指定本地网卡IP和远程主机地址,监控TCP流量的接受发送
"""
import socket
import os
import struct
from ctypes import *


def hexdump(src, length=16):
result = []
digits = 4 if isinstance(src, unicode) else 2
for i in xrange(0, len(src), length):
s = src[i:i + length]
hexa = b' '.join(["%0*X" % (digits, ord(x)) for x in s])
text = b''.join([x if 0x20 <= ord(x) < 0x7F else "<%s>"%x.encode("string-escape") for x in s]) #非ascii可显示字符用.代替,将x.encode("string-escape")替换成b'.'
result.append(b"%04X %-*s %s" % (i, length * (digits + 1), hexa, text))
print(b'\n'.join(result))


class IP(Structure):
_fields_=[
("ihl", c_ubyte,4), #第三个值代表4bit,0.5byte , 不足一个字节是前后相反
("version",c_ubyte,4), #4bit,0.5byte
("tos",c_ubyte), #8bit,1byte
("len",c_ushort), #16bit,2byte
("id",c_ushort), #16bit,2byte
("offset",c_ushort), #16bit,2byte
("ttl",c_ubyte), #8bit,1byte
("protocol_num",c_ubyte), #8bit,1byte
("sum",c_ushort), #16bit,2byte
("src",c_ulong), #32bit,4byte
("dst",c_ulong) #32bit,4byte
]

def __new__(self,socket_buffer=None):
return self.from_buffer_copy(socket_buffer)

def __init__(self,socket_buffer=None):
self.protocol_map = {1:"ICMP",6:"TCP",17:"UDP"}

self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))

try:
self.protocol = self.protocol_map[self.protocol_num]
except:
self.protocol = str(self.protocol_num)

def main(localhost,remotehost,remoteport):
# 创建原始套接字,绑定在公开接口上
if os.name == "nt":
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP

sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)

sniffer.bind((localhost, 0))
# 设置在捕获的数据包中包含IP头
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# 在WIN平台上,需要设置IOCTL以启用混杂模式
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

try:
while True:
raw_buffer = sniffer.recvfrom(65565)[0]
ip_header = IP(raw_buffer[0:20])

if ip_header.protocol=="TCP" and ip_header.dst_address == remotehost:
offset = ip_header.ihl * 4
ipContent = raw_buffer[offset:]
tcp_header = ipContent[0:20]
sourcePort = int(tcp_header[0:2].encode("hex"),16)
destPort = int(tcp_header[2:4].encode("hex"),16)
print "Protocol:%s %s:%s -> %s:%s" % (ip_header.protocol, ip_header.src_address,sourcePort, ip_header.dst_address,destPort)
if destPort == remoteport:
sequenceNumber = int(tcp_header[4:8].encode("hex"),16)
acknowledgeNumber = int(tcp_header[8:12].encode("hex"),16) # acknowledge number 表示的是,接收方期待接收的下一个包起始字节的标号
tcpContent = ipContent[20:]
print "send %d bytes to remote.SYN=%s ACK=%s"%(len(tcpContent),sequenceNumber,acknowledgeNumber)
hexdump(tcpContent)

if ip_header.protocol=="TCP" and ip_header.src_address == remotehost:
offset = ip_header.ihl * 4
ipContent = raw_buffer[offset:]
tcp_header = ipContent[0:20]
sourcePort = int(tcp_header[0:2].encode("hex"),16)
destPort = int(tcp_header[2:4].encode("hex"),16)
print "Protocol:%s %s:%s -> %s:%s" % (ip_header.protocol, ip_header.src_address,sourcePort, ip_header.dst_address,destPort)
if sourcePort == remoteport:
sequenceNumber = int(tcp_header[4:8].encode("hex"),16)
acknowledgeNumber = int(tcp_header[8:12].encode("hex"),16) # acknowledge number 表示的是,接收方期待接收的下一个包起始字节的标号
tcpContent = ipContent[20:]
print "recieved %d bytes from remote.SYN=%s ACK=%s"%(len(tcpContent),sequenceNumber,acknowledgeNumber)
hexdump(tcpContent)

except KeyboardInterrupt:
# 关闭混杂模式
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)


if __name__ == '__main__':
main("10.126.1.209","172.30.1.71",80)

TCP3次握手:

Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80
send 12 bytes to remote.SYN=3097868532 ACK=0
0000  02 04 05 B4 01 03 03 08 01 01 04 02               <\x02><\x04><\x05><\xb4><\x01><\x03><\x03><\x08><\x01><\x01><\x04><\x02>

Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80
send 0 bytes to remote.SYN=3097868533 ACK=1855514852

Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:53808
recieved 12 bytes from remote.SYN=1855514851 ACK=3097868533
0000  02 04 05 B4 01 03 03 05 04 02 00 00               <\x02><\x04><\x05><\xb4><\x01><\x03><\x03><\x05><\x04><\x02><\x00><\x00>

TCP4次挥手:

Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80
send 0 bytes to remote.SYN=3097869053 ACK=1855517028

Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80
send 0 bytes to remote.SYN=3097869054 ACK=1855517029

Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:53808
recieved 0 bytes from remote.SYN=1855517028 ACK=3097869054

Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:53808
recieved 0 bytes from remote.SYN=1855517028 ACK=3097869054

HTTP请求:

Protocol:TCP 10.126.1.209:49981 -> 172.30.1.71:80
send 617 bytes to remote.SYN=3492030201 ACK=755814128
0000  47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A   GET / HTTP/1.1<\r><\n>
0010  48 6F 73 74 3A 20 31 37 32 2E 33 30 2E 31 2E 37   Host: 172.30.1.7
0020  31 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 6B   1<\r><\n>Connection: k
0030  65 65 70 2D 61 6C 69 76 65 0D 0A 55 70 67 72 61   eep-alive<\r><\n>Upgra
0040  64 65 2D 49 6E 73 65 63 75 72 65 2D 52 65 71 75   de-Insecure-Requ
0050  65 73 74 73 3A 20 31 0D 0A 55 73 65 72 2D 41 67   ests: 1<\r><\n>User-Ag
0060  65 6E 74 3A 20 4D 6F 7A 69 6C 6C 61 2F 35 2E 30   ent: Mozilla/5.0
0070  20 28 57 69 6E 64 6F 77 73 20 4E 54 20 36 2E 33    (Windows NT 6.3
0080  3B 20 57 69 6E 36 34 3B 20 78 36 34 29 20 41 70   ; Win64; x64) Ap
0090  70 6C 65 57 65 62 4B 69 74 2F 35 33 37 2E 33 36   pleWebKit/537.36
00A0  20 28 4B 48 54 4D 4C 2C 20 6C 69 6B 65 20 47 65    (KHTML, like Ge
00B0  63 6B 6F 29 20 43 68 72 6F 6D 65 2F 36 38 2E 30   cko) Chrome/68.0
00C0  2E 33 34 34 30 2E 31 30 36 20 53 61 66 61 72 69   .3440.106 Safari
00D0  2F 35 33 37 2E 33 36 0D 0A 41 63 63 65 70 74 3A   /537.36<\r><\n>Accept:
00E0  20 74 65 78 74 2F 68 74 6D 6C 2C 61 70 70 6C 69    text/html,appli
00F0  63 61 74 69 6F 6E 2F 78 68 74 6D 6C 2B 78 6D 6C   cation/xhtml+xml
0100  2C 61 70 70 6C 69 63 61 74 69 6F 6E 2F 78 6D 6C   ,application/xml
0110  3B 71 3D 30 2E 39 2C 69 6D 61 67 65 2F 77 65 62   ;q=0.9,image/web
0120  70 2C 69 6D 61 67 65 2F 61 70 6E 67 2C 2A 2F 2A   p,image/apng,*/*
0130  3B 71 3D 30 2E 38 0D 0A 41 63 63 65 70 74 2D 45   ;q=0.8<\r><\n>Accept-E
0140  6E 63 6F 64 69 6E 67 3A 20 67 7A 69 70 2C 20 64   ncoding: gzip, d
0150  65 66 6C 61 74 65 0D 0A 41 63 63 65 70 74 2D 4C   eflate<\r><\n>Accept-L
0160  61 6E 67 75 61 67 65 3A 20 7A 68 2D 43 4E 2C 7A   anguage: zh-CN,z
0170  68 3B 71 3D 30 2E 39 0D 0A 43 6F 6F 6B 69 65 3A   h;q=0.9<\r><\n>Cookie:
0180  20 63 73 72 66 74 6F 6B 65 6E 3D 63 50 33 30 30    csrftoken=cP300
0190  64 30 75 47 42 4E 49 38 75 77 4C 32 45 31 31 4A   d0uGBNI8uwL2E11J
01A0  4C 66 37 42 57 6C 56 5A 32 72 57 44 4A 31 70 55   Lf7BWlVZ2rWDJ1pU
01B0  31 52 6C 6C 7A 42 35 4F 6A 6C 77 33 37 44 68 53   1RllzB5Ojlw37DhS
01C0  72 48 78 6A 36 31 46 45 6D 4C 44 3B 20 62 5F 74   rHxj61FEmLD; b_t
01D0  5F 73 3D 22 32 7C 31 3A 30 7C 31 30 3A 31 35 33   _s="2|1:0|10:153
01E0  38 32 30 33 39 34 34 7C 35 3A 62 5F 74 5F 73 7C   8203944|5:b_t_s|
01F0  34 38 3A 4F 44 59 33 4F 57 4A 69 4E 54 55 74 5A   48:ODY3OWJiNTUtZ
0200  47 55 7A 5A 69 30 30 4D 54 6B 79 4C 54 68 6D 4D   GUzZi00MTkyLThmM
0210  6D 55 74 5A 6A 52 6A 4E 54 41 7A 5A 44 5A 6D 59   mUtZjRjNTAzZDZmY
0220  32 59 35 7C 62 66 30 30 65 30 36 61 32 34 33 65   2Y5|bf00e06a243e
0230  61 61 38 39 38 38 30 30 32 34 30 32 35 66 39 30   aa89880024025f90
0240  37 33 34 38 30 37 32 32 33 62 30 35 62 62 31 31   734807223b05bb11
0250  39 38 32 39 38 31 35 36 66 39 32 31 63 35 64 63   98298156f921c5dc
0260  34 65 61 31 22 0D 0A 0D 0A                        4ea1"<\r><\n><\r><\n>

HTTP响应:

Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:49981
recieved 238 bytes from remote.SYN=755814128 ACK=3492030818
0000  48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D   HTTP/1.1 200 OK<\r>
0010  0A 53 65 72 76 65 72 3A 20 6E 67 69 6E 78 2F 31   <\n>Server: nginx/1
0020  2E 31 33 2E 39 0D 0A 44 61 74 65 3A 20 53 75 6E   .13.9<\r><\n>Date: Sun
0030  2C 20 33 30 20 53 65 70 20 32 30 31 38 20 30 30   , 30 Sep 2018 00
0040  3A 35 39 3A 33 37 20 47 4D 54 0D 0A 43 6F 6E 74   :59:37 GMT<\r><\n>Cont
0050  65 6E 74 2D 54 79 70 65 3A 20 74 65 78 74 2F 68   ent-Type: text/h
0060  74 6D 6C 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 65 6E   tml<\r><\n>Content-Len
0070  67 74 68 3A 20 36 31 30 0D 0A 4C 61 73 74 2D 4D   gth: 610<\r><\n>Last-M
0080  6F 64 69 66 69 65 64 3A 20 54 68 75 2C 20 32 37   odified: Thu, 27
0090  20 53 65 70 20 32 30 31 38 20 30 34 3A 33 39 3A    Sep 2018 04:39:
00A0  31 38 20 47 4D 54 0D 0A 43 6F 6E 6E 65 63 74 69   18 GMT<\r><\n>Connecti
00B0  6F 6E 3A 20 6B 65 65 70 2D 61 6C 69 76 65 0D 0A   on: keep-alive<\r><\n>
00C0  45 54 61 67 3A 20 22 35 62 61 63 35 65 66 36 2D   ETag: "5bac5ef6-
00D0  32 36 32 22 0D 0A 41 63 63 65 70 74 2D 52 61 6E   262"<\r><\n>Accept-Ran
00E0  67 65 73 3A 20 62 79 74 65 73 0D 0A 0D 0A         ges: bytes<\r><\n><\r><\n>

TCP为什么才用3次握手或者4次挥手呢?

我的理解是,3次握手保证了通讯双方以最少的次数确保信息传递的通路上,发送和接受都能保持通畅。

通俗来讲就是,假如A、B通讯,对于A、B都能确保——1)能收到对方发送给自己的信息;2)自己发送的信息能被对方收到。

假如只握手两次的话,A发送消息给B,B返回确认消息。 对于A来讲,能够满足上面2个条件。但对于B来讲,只能满足第一个条件,而无法满足第二个条件。

那为什么要4次挥手呢?
因为断开连接时,需要确保双方信息已经发送完毕。1)A发送结束信号给B;2)B告诉A:好的,我知道了;3)B继续发送数据完成,通知A:可以断开连接了;4)A收到B的通知后,断开连接。

补充:
ctypes 结构体位域
(位域名,c_ubyte,位域长度)
1.一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

struct bs
{
    unsigned a:4
    unsigned b:5 /*从下一单元开始存放*/
    unsigned c:4
}

2.由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度。
3.位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

struct k
{
    int a:1
    int :2 /*无位域名,该2位不能使用*/
    int b:3
    int c:2
}; 

4.不够一个字节的位域,从后往前取bit

# -*-coding:utf-8 -*-
from ctypes import *

x = bytes("\x45\xA3\x35\x34\x56\x58\xA2\x31\x12\x89")

print bin(int("45",16))     #0b01000101
print bin(int("A3",16))     #0b10100011
print bin(int("35",16))     #0b00110101

class A(Structure):
    _fields_ = [
        ("a",c_ubyte,3),            #0b01000101,取最后3位 101 = 5
        ("b",c_ubyte,3),            #0b01000101,再往前取3位 000 = 0
        ("c",c_ubyte,2),            #0b01000101,取剩下的前2位 01 = 1
        ("d",c_ubyte,4),            #0b10100011,取4位,0011 = 3
        ("e",c_ubyte,5)             #0b00110101 前面剩余的位数不够,所以新使用一个字节,取后5位, 10101 = 21
    ]

    def __new__(self,bytesBuffer):
        return self.from_buffer_copy(bytesBuffer)

    def __init__(self,bytesBuffer):
        pass

if __name__ == '__main__':
    print A(x).a   #5
    print A(x).b   #0
    print A(x).c   #1
    print A(x).d   #3
    print A(x).e   #21