jsd

一个流量探测工具,假设哪天在甲方爸爸现场不允许使用一些流量分析工具时,就得自己造轮子啦。

requestment

  • sys
  • socket
  • threading
  • argparse
  • textwrap

该脚本分为四个模块,一个socket server监听模块server_loopproxy_handler用来控制转发流量方向,socket接受数据模块receive_fromhexdump解码模块显示数据模块。(还有两个自定义修改流量数据模块req_handlerres_handler)


server_loop模块

​ 接收五个参数,local_hostlocal_port建立socket server循环监听流量作为代理,设置五个最大客户连接数量。接收到连接后新开一个线程,把连接交给proxy_handler函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def server_loop(local_host, local_port,
remote_host, remote_port, receive_first):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
server.bind((local_host, local_port))
except Exception as err:
print("[==>]Can't bind on {0}:{1} {2}".format(local_host, local_port, err))
sys.exit(0)

server.listen(5)

while True:
client_socket, addr = server.accept()

line = "[==>]Received from {}:{}".format(addr[0], addr[1])
print(line)

threading.Thread(target=proxy_handler,
args=(client_socket, remote_host,
remote_port, receive_first)).start()

proxy_handler模块

监听的流量进入proxy_handler模块,首先连接目标主机,通过remote_buffer函数接收对方发过来的数据(如FTP会先发送一个欢迎连接的消息,收到后才能发消息给对方),接下来进入循环接收数据,请求数据传入req_handler ,响应数据传入res_handler并利用hexdump函数解码并且输出到屏幕上,直到没有数据传输后跳出循环,关闭socket连接。

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
def proxy_handler(client_socket, remote_host, remote_port, receive_first):
# 先连接到远程主机
address = (remote_host, remote_port)
remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_socket.connect(address)

# 判断对方有无发送消息,例如FTP会先发送一个欢迎连接的消息,收到后才能发消息给对方
if receive_first:
remote_buffer = receive_from(remote_socket)
hexdump(remote_buffer)

remote_buffer = req_handler(remote_buffer)

# 开启循环监听数据
while True:
local_buffer = receive_from(client_socket)

if len(local_buffer):
line = "[==>]Received {} bytes from localhost.".format(len(local_buffer))
print(line)
hexdump(local_buffer)

remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
line = "[==>]Received {} bytes from remote_host.".format(len(remote_buffer))
print(line)
hexdump(remote_buffer)

remote_buffer = res_handler(remote_buffer)
client_socket.send(remote_buffer)
print("[==>]Send to localhost.")

if not len(remote_buffer) or not len(local_buffer):
client_socket.close()
remote_socket.close()
print("[==>]No more data, Closing connect!!!.")
break

hexdump模块

​ 此模块将bytes与string类型输入转换成16进制数据和ASCII,以及脚本里定义的变量HEX_FILTER

HEX_FILTER变量

​ 原作者通过布尔短路求值的方式打印出0-255间的ASCII

1
HEX_FILTER = ''.join((len(repr(chr(i))) == 3) and chr(i) or '.' for i in range(256))

​ 无法打印的ACSII为二位十六进制,通过repr函数打印出其字符串形式进行比较,可以发现不可打印的ASCII长度为6位,可打印的ASCII为3位。

1
2
3
4
5
6
7
8
>>> chr(0)
'\x00'
>>> len(repr(chr(0)))
6
>>> chr(65)
'A'
>>> len(repr(chr(65)))
3

​ 控制台输出,可以看到打印了一些基本字符和数字,.号则为不可打印

1
2
>>> ''.join((len(repr(chr(i))) == 3) and chr(i) or '.' for i in range(256))
'................................ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~.................................................................................................................................'

​ 接下来进到解码hexdump模块,通过判断先把bytes类型解码,通过循环把16个字符通过translate函数并以HEX_FILTER作为对照表翻译出可打印字符,并且格式化输出为16个二位十六进制数存入result列表。最后循环输出即可。

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
def hexdump(src, length=16, show=True):
# 解码bytes型
if isinstance(src, bytes):
src.decode()

result = list()
for i in range(0, len(src), length):
word = str(src[i:i + length])

# 用translate把整段字符转换为可打印形式
# 映射方式HEX_FILTER[ascii]
# 遍历word中字符的ascii,并且利用HEX_FILTER[ascii]替换
printable = word.translate(HEX_FILTER)

# 把整段数据转换为十六进制格式
hexa = ' '.join([f'{ord(c):02X}' for c in word])

# 把word变量起始点的偏移、其十六进制和可打印字符串放进result列表里里。
hexwidth = length * 3
result.append(f'{i:04x} {hexa:<{hexwidth}} {printable}')

if show:
for line in result:
print(line)
else:
return result

附完整代码:

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import sys
import socket
import threading
import argparse
import textwrap

HEX_FILTER = ''.join((len(repr(chr(i))) == 3) and chr(i) or '.' for i in range(256))


def hexdump(src, length=16, show=True):
# 解码bytes型
if isinstance(src, bytes): # isinstance判断类型
src.decode()

result = list()
for i in range(0, len(src), length):
word = str(src[i:i + length])

# 用translate把整段字符转换为可打印形式
# 映射方式HEX_FILTER[ascii]
# 遍历word中字符的ascii,并且利用HEX_FILTER[ascii]替换
printable = word.translate(HEX_FILTER)

# 把整段数据转换为十六进制格式
hexa = ' '.join([f'{ord(c):02X}' for c in word]) # ascii值转为16进制

# 把word变量起始点的偏移、其十六进制和可打印字符串放进result里。
hexwidth = length * 3
result.append(f'{i:04x} {hexa:<{hexwidth}} {printable}')

if show:
for line in result:
print(line)
else:
return result


def receive_from(connection):
buffer = b""
connection.settimeout(40)
try:
while True:
res = connection.recv(4096)
if not res:
break
else:
buffer += res
except Exception as err:
print("recv err: ", err)
return buffer


def req_handler(buffer):
# TODO mod req here
return buffer


def res_handler(buffer):
# TODO mod res here
return buffer


def proxy_handler(client_socket, remote_host, remote_port, receive_first):
address = (remote_host, remote_port)
remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_socket.connect(address)


if receive_first:
remote_buffer = receive_from(remote_socket)
hexdump(remote_buffer)

remote_buffer = req_handler(remote_buffer)


while True:
local_buffer = receive_from(client_socket)

if len(local_buffer):
line = "[==>]Received {} bytes from localhost.".format(len(local_buffer))
print(line)
hexdump(local_buffer)


remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
line = "[==>]Received {} bytes from remote_host.".format(len(remote_buffer))
print(line)
hexdump(remote_buffer)


remote_buffer = res_handler(remote_buffer)
client_socket.send(remote_buffer)
print("[==>]Send to localhost.")

if not len(remote_buffer) or not len(local_buffer):
client_socket.close()
remote_socket.close()
print("[==>]No more data, Closing connect!!!.")
break


def server_loop(local_host, local_port,
remote_host, remote_port, receive_first):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
server.bind((local_host, local_port))
except Exception as err:
print("[==>]Can't bind on {0}:{1} {2}".format(local_host, local_port, err))
sys.exit(0)

server.listen(5)

while True:
client_socket, addr = server.accept()

line = "[==>]Received from {}:{}".format(addr[0], addr[1])
print(line)

threading.Thread(target=proxy_handler,
args=(client_socket, remote_host,
remote_port, receive_first)).start()


def main():
parse = argparse.ArgumentParser(description="TCP_PROXY!!!",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent('''Example:
tcp_proxy.py -lh local_host -rh remote_host -lp 21 -rp 21 -s #需要先接收消息
'''))

parse.add_argument('-s', action='store_true', help='first receive')
parse.add_argument('-lh', help='local host')
parse.add_argument('-rh', help='remote host')

parse.add_argument('-lp', type=int, help='local port')
parse.add_argument('-rp', type=int, help='remote port')
args = parse.parse_args()
server_loop(args.lh, args.lp, args.rh, args.rp, args.s)


if __name__ == "__main__":
main()