TCP客户端及UDP客户端、服务端
基础的TCP客户端及UDP客户端、服务端等简单的代码块就不再进行展示,写一些学习这部分内容时候的一些笔记吧。
- socket库已经集成在Python中,可能这句话不太准确,在IDE软件中编程时,import部分操作可以在编程过程中自动进行补全,不过在具体使用过程中,有时候会出现失效的情况,所以还是手动进行import操作最好。
- 一些函数:
socket(int socket_family,int socket_type,int protocol)
创建socket对象,后续对对象进行操作,之后有对socket()的部分详解。
socket.connect(address)
主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
socket.send(string)
送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
socket.recv(bufsize)
收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息。
接收数据这一部分,较多时间应该是不能清晰明确接收所数据大小,所以使用while循环进行接收,配合其他操作也可以进行对某一终端进行监听等动作,目前是只进行了监听操作的测试。
socket.sendto(address)
送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
socket.recvfrom(bufsize)
收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
用方法:data,addr = socket.recvfrom(bufsize)
socket.close()
闭套接字
socket.bind(address)
定地址(host,port)到套接字, 在AF_INET下,address以元组(host,port)的形式表示地址。
建服务端socket套接字
socket.listen(backlog)
始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1。
socket.accept()
动接受TCP客户端连接,(阻塞式)等待连接的到来
netcat.py
这个脚本算是我进行Python学习中第一个较大体量的工具脚本,这个学习过程怎么说呢,很痛苦,因为对Python很多东西都不了解就开始功能较多的工具编写,所以写的比较艰难,再加上顶着Python2 to Python3的语言版本差异,这部分的学习一直持续了两周多时间。
不过,也很感谢我这种比较莽的学习方式:先学,在学习过程遇到的不会的、不懂的再去补充学习。这样的方法学习起来会很有效率,问题的产生到解决问题这种过程的印象很深刻,所以在这个脚本之后我基本上就可以开始独立编写一部分的Poc脚本。
import sys
import socket
import getopt
import threading
import subprocess
# 全局变量
listen = False
command = False # 命令
upload = False
execute = "" # 执行
target = "" # 连接目标
upload_destination = "" # 上传目的路径
port = 0
def main():
global listen # global用来在函数中声明此变量是全局变量
global port
global execute
global command
global upload_destination
global target
if not len(sys.argv[1:]):
usage()
try:
opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",["help","listen","execute","target","port","command","upload"])
except getopt.GetoptError as err:
print(str(err))
usage()
for o,a in opts: # 判断输入的参数是否存在,并对变量进行赋值
if o in ("-h","--hlep"):
usage()
elif o in ("-l","--listen"):
listen = True
elif o in ("-e","--execute"):
execute = a
elif o in ("-c","--command"):
command = True
elif o in ("-u","--upload"):
upload_destination = a
elif o in ("-t","--target"):
target = a
elif o in ("-p","--port"):
port = int(a)
else:
assert False,"Unhandled Option" # assert 表达式 [, 参数],返回值为假时,则执行后面的异常
if not listen and len(target) and port > 0: # 判断时进行监听还是发送数据,listen的值与目标的地址和端口是否设置的条件
# 不进行监听,连接远程主机
# buffer = sys.stdin.read() # 相当于input()
buffer = "test"
client_sender(buffer)
if listen:
server_loop() # 监听端口
def usage():
print("Usage:nctest.py -t target_host -p port")
print("-l --listen - listen on [host]:[port] for incoming connections")
print("-e --execute=file_to_run - execute the given file upon receiving a connection")
print("-c --command - initialize a command shell")
print("-u --upload=destination - upon receiving connection upload a file and write to [destination]")
print("")
print("")
print("Examples:")
print("nctest.py -t 192.168.0.1 -p 5555 -l -c")
print("nctest.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe")
print("nctest.py -t 192,168.0.1 -p 5555 -l -e=\"cat /etc/passwd\"")
print("echo 'ABCDEFGHI' | ./nctest.py -t 192.168.11.12 -p 135")
sys.exit(0)
def client_sender(buffer):
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
client.connect((target,port))
if len(buffer):
client.send(buffer.encode())
while True:
recv_len = 1
response = ""
while recv_len: # 接收目标主机发送回来的数据,并赋值给response变量,
data = client.recv(4096).decode("gb2312")
recv_len = len(data) # 此处取data的长度仅为了下面IF的判断结束循环,recv_len为1,即永真
response += data
if recv_len < 4096:
break
print(response)
buffer = input("") # 此处为是否有更多的需要服务端响应的“请求”
buffer += "\n"
client.send(buffer.encode())
except:
print("[*] Exception Exiting.")
client.close()
def server_loop():
global target
if not len(target):
target = "0.0.0.0"
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((target,port))
server.listen(5)
while True:
client_socket,addr = server.accept()
client_tread = threading.Thread(target=client_handler,args=(client_socket,))
client_tread.start()
def run_command(command):
command = command.rstrip() # 删除字符串后的特定字符,默认为空格
try:
output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell=True)
except:
output = "Failed to execute command.\r\n"
return output
def client_handler(client_socket):
global upload
global execute
global command
if len(upload_destination):
file_buffer = ""
while True:
data = client_socket.recv(4096)
if not data:
break
else:
file_buffer += data
try:
file_descriptor = open(upload_destination,"wb")
file_descriptor.write(file_buffer.encode())
file_descriptor.close()
# 确认操作
client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
except:
client_socket.send("Failed to save file to %s\r\n" % upload_destination)
if len(execute):
output = run_command(execute)
client_socket.send(output)
if command:
while True:
windows = "<BHP:#>"
client_socket.send(windows.encode())
cmd_buffer = "" # 接收输入的命令
while "\n" not in cmd_buffer:
cmd_buffer = client_socket.recv(4096)
cmd_buffer = cmd_buffer.decode()
cmd_buffer += cmd_buffer
response = run_command(cmd_buffer)
response_type = str(type(response))
if "str" in response_type:
client_socket.send(response.encode())
elif "bytes" in response_type:
client_socket.send(response)
else:
print("[*] Command response type Unresolved")
main()
# 调试命令:
# server端:python netcat.py -t 127.0.0.1 -p 10086 -l -c
# client端:python netcat.py -t 127.0.0.1 -p 10086
netcat.py的一些笔记
sys.srgv[]
sys.argv[]是用来获取命令行输入内容的方法
sys.argv[]返回的是一个列表,sys.argv[0]是程序本身的名字,所以最终结果要从第二位开始取,即sys.argv[1]
opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",["help","listen","execute","target","port","command","upload"])
使用sys.argv[1:]过滤掉第一个参数(它是执行脚本的名字,不应算作参数的一部分)
使用短格式分析串"hle:"
当一个选项只是表示开关状态时,即后面不带附加参数时,在分析串中写入选项字符
当选项后面是带一个附加参数时,在分析串中写入选项字符同时后面加一个":"号。所以"hle:"就表示"hl"是开关选项;"e:"则表示后面应该带一个参数
在命令行中输入方式短格式分析串举例:用友beanshell命令执行漏洞.py -h -l -e file(h和l为开关选项,e为需要附加参数,附加参数为:file)
使用长格式分析串列表:["help","listen","execute","target","port","command","upload"]
长格式串也可以有开关状态,即后面不跟"="号
如果跟一个等号则表示后面还应有一个参数。这个长格式表示"help"等是一个开关选项;如果存在如"port="则表示后面应该带一个参数
在命令行中输入方式短格式分析串举例:netcat.py --help --port=80(help为开关选项,port为需要附加参数,附加参数为:80)
如果长短格式都存在的话,是否有附加参数仅定义短格式即可,在使用过程中长格式可以沿用短格式的参数输入方式
调用getopt函数。函数返回两个列表:opts和args。opts为分析出的格式信息。args为不属于格式信息的剩余的命令行参数, 即不是按照getopt()
里面定义的长或短选项字符和附加参数以外的信息。opts是一个两元素元组的列表。每个元素为:(选项串, 附加参数)。如果没有附加参数则为空串''
而args则为:['file1', 'file2'],这就是上面不属于格式信息的剩余的命令行参数
output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell=True) output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell=True) subprocess.run(args,*,stdin=None,input=None,stdout=None,stderr=None,capture_output=False,shell=False,cwd=None,timeout=None,check=False,encoding=None,errors=None,text=None,env=None,universal_newlines=None)
"command"此处参数名称为args,表示为需要执行的命令,必须是字符串或参数列表,可以不输入"args="
args:表示要执行的命令。必须是一个字符串,字符串参数列表
stdin、stdout和stderr:子进程的标准输入、输出和错误
其值可以是subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者None
subprocess.PIPE表示为子进程创建新的管道
subprocess.DEVNULL表示使用os.devnull
默认使用的是None,表示什么都不做。另外,stderr可以合并到stdout里一起输出
timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出TimeoutExpired异常
check:如果该参数设置为True,并且进程退出状态码不是0,则弹出CalledProcessError异常
encoding: 如果指定了该参数,则stdin、stdout和stderr可以接收字符串数据,并以该编码方式编码。否则只接收bytes类型的数据
shell:如果该参数为True,将通过操作系统的shell执行指定的命令
其他库的一些函数
sys.stdin.read()
相当于input()
Python2 to Python3
这一部分简单写一些Python2 to Python3的改变。
数据类型上:
- python2和python3的区别最大的是在库的所能处理的数据的数据类型上,socket库中send和recv的数据都是bytes类型的,所以在send前需要str to bytes,recv后需要bytes to str,具体的情况下需要具体的处理方式,数据类型转换的方法、语句也有所不同。
- python3中的字符型数据类型为str和bytes,而python2中的字符数据类型为str和unicode,所以在py2脚本中isinstance()这个函数使用时,会出现unicode类型的判断。
部分函数上:
函数这部分呢,仅代表我个人观点,对一些Python2中使用但在Python3中不使用或少使用的函数都定义为语言版本之间的不同
isinstance() 和 type()
isinstance(object, classinfo):判断一个对象是否是一个已知的类型
type():返回对象的类型
xrange() 和 range()
xrange(start,stop[,step]),用法、作用同range(),python3中没有xrange()这个函数
代码编写上:
- isinstance()这个函数在python3中个人认为基本上没有太大的作用,可以使用type()构造判断语句,虽然代码相对会多一些,但是稳定
- py3的占位符,占位符与C语言和py2会存在一定的区别,注意写法
socket库
socket.socket(int socket_family,int socket_type,int protocol)
int socket_family:
创建的socket的地址簇或者协议簇,取值以AF或PF开头,实际使用中两者并没有区别
最常用的取值:AF_INET、AF_PACKET、AF_UNIX等
AF_UNIX:主要用于主机内部进程间通信
AF_INET与AF_PACKET区别在于前者只能看到三层以上的内容,后者可以看到二层
AF_INET是与IP报文对应的,而AF_PACKET则是与Ethernet II报文对应的。AF_INET创建的套接字称为inet socket,而AF_PACKET创建的套接字称为packet socket
int socket_type:
socket_family会影响socket_type和protocol取值范围
socket_type表示套接字类型:
enum sock_type{
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
};
SOCK_STREAM 流格式套接字
基于TCP协议,是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果有损坏或丢失,可以重新发送
SOCK_STREAM的几个特征:
数据在传输过程中不会消失
数据是按照顺序传输的
数据的发送和接收不是同步的
流格式套接字的内部有一个缓冲区(也就是字符数组),通过 socket 传输的数据将保存到这个缓冲区。接收端在收到数据后并不一定立即读取,只要数据不超过缓冲区的容量,接收端有可能在缓冲区被填满以后一次性地读取,也可能分成好几次读取
流格式套接字有什么实际的应用场景吗?浏览器所使用的 http 协议就基于面向连接的套接字,因为必须要确保数据准确无误,否则加载的 HTML 将无法解析
SOCK_DGRAM** 数据报格式套接字
基于UDP协议,也叫"无连接的套接字",是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字,只管传输数据,不做数据校验,数据损坏和丢失的情况下无法进行补救,但传输效率比刘格式套接字要高
SOCK_DGRAM的几个特征:
强调快速传输而非传输顺序
传输的数据可能丢失也可能损毁
限制每次传输的数据大小
数据的发送和接收是同步的
SOCK_STREAM和SOCK_DGRAM统称为标准套接字
SOCK_RAW 原始套接字
SOCK_RAW可以处理普通套接字无法处理的ICMP、IGMP等网络报文,其次,SOCK_RAW可以处理特殊的IPv4报文,此外利用原始套接字,可以通过IP_HDRINCL套接字选项有用户构造IP头
SOCK_RAW可以处理普通的网络报文之外,还可以处理一些特殊协议报文以及操作IP层及其以上的数据
SOCK_RAW的几个特征
若设置IP_HDRINCL选项,SOCK_RAW可以操作IP头数据,即用户需要甜筒IP头及其以上的payload,否则SOCK_RAW无法操作IP头数据
端口对于SOCK_RAW是没有意义的
如果使用bind函数绑定本地IP,那么如果IP_HDRINCL未设置,则需要用此IP填充源IP地址,若不调用bind则源IP地址设置为外出接口的主IP地址
如果使用connect函数设置目标IP,则可以使用send或write函数发送报文,而不需要使用sendto函数
内核处理流程:
接收到的TCP、UDP分组不会传递给任何SOCK_RAW
ICMP、IGMP报文分组传递给SOCK_RAW
内核不识别的IP报文传递给SOCK_RAW
SOCK_RAW是否接收报文:
protocol指定类型需要匹配,否则不传递给该SOCK_RAW
如果使用bind函数绑定了源IP,则报文目的IP必须和绑定的IP匹配,否则不传递给该SOCK_RAW
如果使用了connect函数绑定了目的IP,则报文源IP必须和制定的IP匹配,否则不传递给该SOCK_RAW
SOCK_RAW的应用场景
原始套接字处理的只是IP层及其以上的数据,比如实现SYN FLOOD攻击、处理PING报文等。当需要操作更底层的数据的时候,就需要采用其他的方式
int protocol:
protocol表示套接字上报文的协议
默认值为0
对于AF_INET地址簇,protocol的取值范围是如IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP这样的报文协议类型,或者IPPROTO_IP=0这个特殊值
对于AF_PACKET地址簇,protocol的取值范围是ETH_P_IP、ETH_P_ARP这样的以太帧协议类型
socket的协议开关
每一个inet socket只能解法一种IP协议类型的报文,例如TCP套接字不能接收UDP报文,反之亦同
同时,protocol的值还收到socket_type的限制,不匹配的取值会导致套接字创建操作返回失败
socket.setsockopt(level,optname,value)
功能作用:
获取(get)或者设置(set)与某个套接字关联的选项
参数含义:
level定义了那个选项将被使用,通常情况下是SOL_SOCKET,意思是正在使用的socket选项,也可以通过设置一个特殊协议号码来设置协议选项
optname提供使用的特殊选项,
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
写在最后
这本书第2章后续是ssh和proxy工具的编写,并没有太多的新的东西,所以就不再对此部分写读书笔记了,主要还是熟悉这些三方库的代码语法和版本语言的差异,也逐渐养成自己的代码编写习惯。
总的来说,这块的学习最大的收获就是对程序dubug相关的操作和经验,插入代码桩、try-except结构的使用及优化对应报错信息的输出、IDE软件的DeBug模块的使用等等。
netcat.py因为是我第一个编写的脚本,写的还比较的粗糙,dubug的代码桩还没有清除干净,虽然是抄的书,但是还是可以看出一些个人习惯,也依据自己的理解对脚本做了一些小小的优化,比如说部分代码的结构、输入输出、回显优化等等
另外,脚本中,仅调试了command模块,不同命令的response回显的数据类型是不同的,需要进行对数据类型的判断,对此做了部分优化,除此之外发现有些命令仍然不能正常使用,没有具体检测是哪些,应该是与脚本执行的权限相关,不过脚本的基础功能是可以实现了,有道是脚本只要能跑,咱就不动了!
Comments | NOTHING