昨天来源:CodeSec
本文原创作者:VillanCh,本文属CodeSec原创奖励计划,未经许可禁止转载
在最近忙活的一些事情中,体会到:如果你写的不是一个脚本,那么作为一个有命令行交互的工具,整个工具的结构和可拓展性就应该称为一个非常重要的部分。本文就带大家来探讨一下这个问题。当然前提是读者能写得出这个工具,本文理论比较多,实践部分读者可以期待以后的文章。
0×00 背景介绍其实就我自己来说,我是非常喜欢 SQLmap 的架构。当然不得不承认,SQLmap 源码也是非常优雅。当然网上也有了关于 SQLmap 源码分析的文章,两篇比较好的文章都算是虎头蛇尾。我的这篇文章不会只以 SQLmap 为例子。同样还有我自己上个月完成的一个很有趣的匿名代理收集工具。
在一开始我在想怎么样把构造一个工具的每一个步骤都给大家呈现出来这个问题上,伤了不少脑筋,因为需要涉及的方面还是非常的多的。仅仅是整体大致来讲,非常的抽象而且,肯定难免划水吧。
于是我们还是以解决具体问题的视角来讨论这个工具应该怎么样完成吧!
0×01 交互控制关于交互控制,我们大致能想到的就是三种方式:
1. 直接命令行交互
2. shell 交互 3. 提供 API这三种交互方式如果都能提供最好,如果工具简单,功能单一,结构也不打算设计多么复杂。一般来说,只需要提供一个命令行交互就可以了,但是我个人推荐至少还是需要提供 API, 单独把 API 抽离出来,这样让你的功能脚本(核心层)有更大的自由度去完成一些事情。不需要为了命令行的问题去妥协,不断修改功能脚本。只需要在 API 构造的时候完成对核心功能脚本的适应。
与此同时,你在构造 shell 或构造命令行解析器的时候,可以无后顾之忧的调用 API,不必牵一发而动全身。 那么我们如何完成用户交互的构造呢?既然我们选择 Python 作为工具开发语言,Python 强大的模块肯定帮的上我们的忙。cmd
optparsercmd是一个构造 shell 的一个简易框架,通过继承 cmd 中的 Cmd 类,添加相应方法可以构造出一个自己的 shell 用于实时与用户交互。
如果大家有过 Python 使用经验的话,应该对 optparser 比较熟悉吧,这是一个非常优秀的构造命令行交互的框架,方便解析命令行。 那么在 SQLmap 中,三种交互方式都是存在的,可能大家用的更多的是直接命令行交互,而很少用 SQLmap 的 shell 交互。 但是值得一提的是 SQLmap 的 API 并不是我提到的 API 概念一样。 使用过 SQLmap API 的朋友们肯定都知道 SQLmap 的 API 是要设定服务器的,通过本地 RESTful 风格的 API 调用来使用 SQLmap 完成功能。而我这里所说的 API 是介于核心功能层与交互层之间的层,提供交互与核心功能脚本的隔离。0×02 配置问题我私以为这是非常有必要重视的一个地方,在我刚开始接触这玩意的时候,经常性的改动了一个地方的数据,还要手动翻到别的脚本中去改相应的配置。道理大家都懂,但实际动手的时候,自己仍然是犯蠢了。那么这里其实很好解决。你可以设置配置的全局的变量都放在一个 yourdata.py 中,然后当需要的时候
from yourdata import XXXXXX或者 from data.yourdata import XXXXXX虽然可能在大家看来这是很小的事情,也并不用提到台面上,但是作为吃过亏的人来讲,我还是乐意带大家看看 SQLmap 是怎么解决这个问题的。
下图是 SQLmap 的部分目录结构:Image may be NSFW.
Clik here to view.

这个 data.py 中:#!/usr/bin/env python from lib.core.datatype import AttribDict from lib.core.log import LOGGER # sqlmap paths paths = AttribDict # object to store original command line options cmdLineOptions = AttribDict # object to store merged options (command line, configuration file and default options) mergedOptions = AttribDict # object to share within function and classes command # line options and settings conf = AttribDict # object to share within function and classes results kb = AttribDict # object with each database management system specific queries queries = {} # logger logger = LOGGER
注意: AttribDict 是 SQLmap 自己继承了一个 Dict 完成了对原来 Dict 的一些改进,可以简单就当成一个 Dict 来看就可以了。
然后 SQLmap 在测试 SQL 注入的过程中,使用的配置文件事先全部被加载到了 conf 和 kb 中了,因此,在程序执行的过程中,凡是需要用到配置文件的地方,只需要 import 进 conf 与 kb 就可以很方便完成任务了。 同时默认配置在同目录下的 defaults.py 中,各种数据(比如重要的枚举类型,重要的 dicts)都在 core 这个包中,当 SQLmap 需要的时候只需要 import 进实现设定好的数据就可以了。
可能有些读者还是觉得,这并没有什么了不起,或者并没有体会到这样做的便捷性(我直接写在功能脚本里快得很,而且从来不管你所谓的架构,我的程序照样跑啊。),如果这样想的话,大概这样可能程序的扩展性也健壮性都会受到很大的影响。
下面是我认为比较优化的一个结构
Image may be NSFW.
Clik here to view.

同样对比我们看一下比较糟糕的结构
Image may be NSFW.
Clik here to view.

问题还是相当明显的:配置1,配置2,配置3很有可能有交集,当你需要改动交集中的某项配置的时候,你需要改动三个功能脚本,这是非常难受的一件事情。
0×03 I/O 控制所谓的 I/O 控制并不是说像操作系统 I/O 那样的概念,我们要讨论的是程序的 I/O 问题:
1. 用户输入与呈现给用户(交互与输出)
2. 网络 I/O (访问网络) 3. 文件与数据 I/O (数据库与 csv, xml, json 等类型文件读取与导出) 4. 日志 问题:网络 I/O ?读者肯定要问,网络 I/O 也需要我们专门注意么?
肯定需要!在大家使用一个渗透工具的时候,肯定多多少少注意隐匿自己的身份,当然事先布置好 Tor 或者 VPN 或者 使用 HTTP 代理,当然,在国内的我们可能会花了好大的力气配置 Tor 的上游服务器,会花钱购买 VPN(当然会被告知禁止使用 VPN 做非法的勾当),或者说上公共代理网站上寻找一些公共的代理拿来用。(当然,我也经常干这些事情。所以写了一个公共代理收集过滤的工具)。所谓的网络 I/O 控制就是说控制你连接网络的某些方式,比如 SQLmap 提供了使用 Tor 的接口,也提供了 HTTP 代理的接口( proxy)这些功能都是在网络 I/O 控制实现的。 可能有朋友就会说,Python 中有 requests 还不够么?自己重新写一个是什么鬼啊? 那么我们就具体来看一下 SQLmap 中的网络控制,大家一切都明了了。Image may be NSFW.
Clik here to view.
对代理的细节处理显然不是其他的自带的模块所有的,这里的对 Tor 和 http 代理的处理就数据网络 I/O 的控制。
当然这里可能看不到对 Tor 处理的细节,但是打开 connReadProxy那么对于网络 I/O 我们就不谈更多了。
在用户输入与展现给用户的输出上,还是有话要说的。因为,如果很随意的直接 print 出去的话非常的尴尬,我们可以单独控制一个输出函数,程序中所有的输出都调用某个函数或者以同一种方式输出,而不是到处都存在的调试专用 print 。 其实这样做非常的合理,当输出可控时,你可以控制输出的等级,比如 Debug 级别,在 Debug 开启的时候,所有的信息都会输出。 Error 级别, 还有 Info, Warning, Critical 在输入的时候,你决定这条信息展示在用户面前的级别,一般来说,如果 Info 级别的信息优先级要低于 Critical, 你(或者)可以选择 Info 级别的输出要不要用户看到,只展示 Error 或者只展示 Critical 以上的信息。
同样的对于日志来说也一样,如果大家有使用过 logging 模块应该会熟悉这个非常好用的日志模块,替我们剩下了很大的力气,不用自己去写日志的输出格式什么了。
哎,数据库读写,核心功能脚本执行的结果要支持不同数据类型的导出,这些也应该是要具备的,因为导出的易用的数据文件,可以很容易的被其他工具使用,可以以更好的方式被加载,使用,而不是手动的再次输入结果,这样很蠢的。在 SQLmap 中就使用了很多专门处理各种特定文件类型的脚本来完成这样的基础性的工作。
0×04 差错控制编写渗透测试工具的人员一般不是专业的程序员,那么差错控制肯定也是一个值得注意的问题。并不推崇一个 try 一个 except 捕捉所有的错误,这样真的出事的时候会让人一头雾水的。那么正确的做法应该是什么呢?编写你自己的异常,灵活使用 Python 提供的例如 ValueError IndexError 和键盘中断等,然后让出问题的部分了属于胸。
可能我在这里说这个,懂得编程的读者会觉得可笑,说实话我也觉得这非常的班门弄斧,但是我相信点开这篇文章的大多数还是一个渗透测试的爱好者或者从业者,在进行渗透测试的时候经常会需要写脚本,完成自己的目的。到一定程度以后,希望把自己的脚本整合起来,做一个更好的工具。自己使用或者是留给别人用。毕竟写脚本和写程序做工具还是完全不同的两个概念,作为一个写得出脚本的渗透测试者,还是需要多少有一些编程的经验。
0×05 我心中的理想架构上面讲了这么多,可能有些人会有所收获,有些人会一头雾水,至少我觉得,结合我自己这段时间的体会,和几个具体的例子,一个 Python 工具该有的架构,该有的内容,我还算是比较了解的。
那么我就专门做了一个 Python 工具的架构图给大家看,当然这是我心目中比较不错的结构了。如果有兴趣的话,大家可以实现一下,开发一个更高效更好用的工具。
Image may be NSFW.
Clik here to view.
上图的工具比较适合脚本集类型的工具开发,功能可以比较分散,在具体的功能上可以不要求太大的连续性,但是交互性更加强,建议使用 shell 做交互以便不同功能随意切换而且不需要重新加载程序。
Image may be NSFW.
Clik here to view.
这第二种架构就是 SQLmap 所使用的架构的‘改变’版本(SQLmap 没有提供功能开放的 API, 但是我还是希望这样来让所有的 API 更加好找易于二次开发)。特别适合流程类的工具开发。比如 SQL 注入检测就是一个可以抽象出流程的测试过程。
0×06 结语一款牛B的工具的诞生,绝对不是简单的功能拼凑,而是不断重构,思考,注意细节。
显然我们足够幸运,可以使用 Python, 很多细节的工作,前人已经替我们完成了,站在巨人的肩膀上,风景显然更加的壮观。 那么,如果大家还是不太名如何完成一个工具的话,如果感兴趣的人多的话,我就继续讲非常具体的细节吧,很乐意和大家分享我微薄的经验。