一直在思考远控应该怎么设计,远控的源码究竟是什么样的。这次我会对Empire这个优秀的开源后渗透框架的源码进行分析,去挖掘这个框架背后的设计方法和原理。我想,分析完这个框架之后,我们就能够借鉴其思想,自己来实现一个远控程序来。

由于一些原因,这些分析只能在下班时间来写。对于这个框架,我打算分五篇文章写完,目录结构如下:

  1. Empire整体结构 & 程序入口
  2. Stager & Listener & Agent
  3. 数据流通 & 数据加密
  4. 插件 & 扩展性
  5. 第三方库们

今天先开始第一篇,谈一谈Empire的目录结构和入口文件。从Github获取源码,只显示了二级目录并去除了一些安装部署相关的文件后,比较重要的几个目录结构如下:

Empire
├── data                                        data目录用于存放静态文件、模板、数据库等
│   ├── agent
│   ├── empire-chain.pem
│   ├── empire-priv.key
│   ├── empire.db                               Empire使用了sqlite数据库存储
│   ├── misc
│   ├── module_source
│   ├── obfuscated_module_source
│   └── profiles
├── empire                                      主程序入口,python文件
├── lib
│   ├── common
│   ├── listeners                               放置了不同的listener
│   ├── modules                                 放置了各种payload,后渗透功能相关
│   ├── powershell                              放置Invoke-Obfuscation项目文件,用于混淆powershell
│   └── stagers                                 放置了各种平台下的stager
└── plugins                                     放置了插件示例文件

可以看见项目文件还是比较清晰的,由于当前远控多数情况还是被控端主动连接到控制端的,为了区分方便,在下文中我会将被控端称之为Client,将控制端称为Server。
我们先来分析Server端,看一下这个项目是怎样运行的。首先关注一下Empire/empire这个文件,这是整个程序的入口。在逐渐阅读了Empire项目源码之后,我比较惊讶的是这个后渗透框架的本质其实是一个Flask Web App。
程序在最开始定义了一些数据库连接和查询相关的函数,这里的数据库使用的是sqlite。为了给RESTFUL API鉴权,这里还给出了生成token的函数。

def refresh_api_token(conn):
"""
Generates a randomized RESTful API token and updates the value
in the config stored in the backend database.
"""
# generate a randomized API token
apiToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40))
execute_db_query(conn, "UPDATE config SET api_current_token=?", [apiToken])
return apiToken

岔个话题,这种写法还是蛮pythonic的,直接用了random.choice函数来随机选择,而且还使用了类似列表推导的语法
之后开始的start_restful_api()函数则是使用Flask框架来注册了一堆路由,用于RESTFUL API。有人可能会好奇这玩意是做什么用的,其实在EmpireProject的github账号中已经创建了Empire GUI客户端的项目,应该是用于客户端与服务端的交互过程,或者是留了接口能够以后被第三方工具调用

####################################################################
#
# The Empire RESTful API.
#
# Adapted from http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask
# example code at https://gist.github.com/miguelgrinberg/5614326
#
# Verb URI Action
# ---- --- ------
# GET http://localhost:1337/api/version return the current Empire version
#
# GET http://localhost:1337/api/config return the current default config
#
# GET http://localhost:1337/api/stagers return all current stagers
# GET http://localhost:1337/api/stagers/X return the stager with name X
# POST http://localhost:1337/api/stagers generate a stager given supplied options (need to implement)
# ...

官方已经给了很详细的注释,可以直接去读101行开始的源码,这一部分不再详细说明
从1362行来到了’__main__’,进入真正的逻辑,这里取了一些参数,我们只看最普通的执行情况,只有很简单的几句:

else:
    # normal execution
    main = empire.MainMenu(args=args)
    main.cmdloop()

进入了empire模块中MainMenu类的cmdloop()函数,这个empire模块才是框架比较核心的部分,之前的只是入口
进入Empire/lib/common/empire.py文件继续阅读

# custom exceptions used for nested menu navigation
class NavMain(Exception):
"""
Custom exception class used to navigate to the 'main' menu.
"""
    pass

一上来定义了几个类用于异常处理,其实这个是在菜单跳转中使用的。真正重要的是之后定义的几个大的类,MainMenu, SubMenu, AgentsMenu, AgentMenu, PowerShellAgentMenu, PythonAgentMenu, ListenersMenu, ListenerMenu, ModuleMenu, StagerMenu,下面一个一个讲
MainMenu是最核心的控制部分,程序启动后会首先进入主菜单。Empire菜单的控制逻辑其实不全是自己写的,而是继承了cmd模块中的Cmd类,这个类的详情可以看这里https://wiki.python.org/moin/CmdModule,简要来说的话就是提供了写命令行应用的一些很方便的特性,比如能够自定义命令和语法、整体读取命令和返回结果的循环、TAB键的语法补全,我们在MainMenu类中看到形如do_xxx的语法均为定义指令,help_xxx的语法均为定义帮助命令,complete_xxx的语法均为处理TAB补全相关
在写远控框架的时候另一个比较重要的问题是,当我们发送的命令得到Client回复时,Server如何获知这个消息。Empire在这个问题上给出的答案是使用dispatcher模块。这个模块是生产者-消费者模式的一种实现,详情可以见http://pydispatcher.sourceforge.net

# set up the event handling system
dispatcher.connect(self.handle_event, sender=dispatcher.Any)

77行这里指定了MainMenu的事件处理函数,这里对任何sender发出的信号都会接收并处理,我们去handle_event函数看一下

def handle_event(self, signal, sender):
        """
        Whenver an event is received from the dispatcher, log it to the DB,
        decide whether it should be printed, and if so, print it.
        If self.args.debug, also log all events to a file.
        """
        # load up the signal so we can inspect it
        try:
            signal_data = json.loads(signal)
        except ValueError:
            print(helpers.color("[!] Error: bad signal recieved {} from sender {}".format(signal, sender)))
            return
        # this should probably be set in the event itself but we can check
        # here (and for most the time difference won't matter so it's fine)
        if 'timestamp' not in signal_data:
            signal_data['timestamp'] = helpers.get_datetime()
        # if this is related to a task, set task_id; this is its own column in
        # the DB (else the column will be set to None/null)
        task_id = None
        if 'task_id' in signal_data:
            task_id = signal_data['task_id']
        if 'event_type' in signal_data:
            event_type = signal_data['event_type']
        else:
            event_type = 'dispatched_event'
        event_data = json.dumps({'signal': signal_data, 'sender': sender})
...

注释写的挺清楚,收到信号时会记录到Database,并根据signal中的选项决定是否打印数据,在不同的菜单中显示不同的数据类型的相关逻辑就是这样控制的,除此之外,dispatcher也是listener与主控程序的通信方式。之前提到过,empire本质上是一个web应用,这个web应用是被主控程序启动的,主控程序与web应用这种无状态应用之间其实是存在一种隔离的,empire使用dispatcher机制来突破了这种限制(我原以为会是通过数据库读写,其实并没有),既然提到这里,我们也看一眼listener的相关实现

# lib/listeners/http_com.py
@app.before_request
def check_ip():
    """
    Before every request, check if the IP address is allowed.
    """
    if not self.mainMenu.agents.is_ip_allowed(request.remote_addr):
        listenerName = self.options['Name']['Value']
        message = "[!] {} on the blacklist/not on the whitelist requested resource".format(request.remote_addr)
        signal = json.dumps({
            'print': True,
            'message': message
        })
    dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName))
    return make_response(self.default_response(), 404)

其实这里价值在于观察程序的启动方式,观察菜单跳转和命令都是怎么实现的,看完这里我们完全也可以自己做一个相似的CC出来
菜单跳转这里是通过raise Exception实现的,在主菜单中永续循环读取命令,判断需要显示的菜单,如果需要跳转到agents/listeners等菜单会抛出一个异常,修改需要现实菜单的变量,主菜单捕获相应异常,根据变量值实例化一个新的菜单对象,然后再开始新的菜单对象的cmdloop()函数.

第一章到此结束。先简单写一下逻辑结构,至此我们已经明确了empire的菜单实现,明确了菜单之间的跳转是怎么做到的,也明确了empire交互性良好的REPL模式命令行构建的秘密,从下一章开始我会将重点放在CC真正重要的部分。

标签: CC, Empire

添加新评论