|
最近做完一个项目,项目是Windows桌面客户游戏产品,基于NW架构开发,核心代码是基于js语言,cocos2d框架开发的h5游戏,分析js加密的过程当中使用到了js AST抽象语法树,对魂效果的js代码进行反混淆,增加代码可读性,更同意解密js相关的业务逻辑。
项目需求不复杂,就是一个打地鼠的游戏,每个房间两个人,游戏开始后,谁比对手多打10个地鼠就算赢。游戏本身很多通过按键xx等jiaob软件录制软件做的脚本在游戏**,客户觉得慢了,想以xieyi或者请求的方式定制软件。
游戏长这样
开始以为是unity 后来研究软件安装包 其实就是nw,和electron一样,使用的是js前端的技术栈
确定是js,通过fd抓包找到登录链接,就能看到项目源码,抓包后在Chrome中打开如下
发现代码中导出都是混淆过的代码,可读性非常差。通过Chrome network对游戏的协yi分析,了解到游戏是通过websocket加密,搜索websocket、onopen、onmessage、onclose、oneroor,等websocket比用的关键词,找到相关代码如下
可以看到这个源码文件导出都是_0x2b3c9d[_0x2568('0x3cb')]这样的代码,看上去像是从一个数组中,取对应索引的值,而且单引号内的0x开头的是16进制的数字,自己用微软的计算器,可以转换成10进制。
把代码拉到第一行,看到第一行的代码,其实就是一个大数组,里面基本都是明文,可以确定,这个代码文件里面的所有_0x2b3c9d[0xxxxx],就是从第一行的这个大树组里面取值。
这个大树组维护了几百个值,源文件几万行,不可能手工一个个拿计算器换算成10进制的数值后,再去找数组对应的索引值。那这个项目没法做。
这时候就需要使用到js AST抽象语法树相关的概念。
各位下次遇到js源码分析,代码里面有一个这样的值固定的很大的数组,其他代码中导出都是类似于数组[索引],这样的代码结构,就可以使用到抽象语法树的技术对代码进行反混淆。使用起来也相对简单。
给大家提供一个AST抽象语法树,代码结构分析的网站 https://astexplorer.net/,打开后将上面找到的websocket相关代码复制到左边,得到如下界面。
其实就是根据js语言的编译规则, 将所有的胆码拆分成相应的单元, 对代码结构进行分析。找到对应的代码结构,就可以通过编码的方式,将所有数组【索引】这样结构的代码全部找出来,然后在进行替换。
具体编码方式如下, 需要使用到nodejs的环境,先通过npm安装相应的包,然后把这个大树组复制进去,通过上面AST分析找到需要替换的节点,通过node fs模块,读取源代码,然后再对符合条件的节点进行替换。
没法截图了 上传附件了, 除了上面包引入,和大数组, 其他关键代码如下
// 数组调用切换为字符串
const traverse_arr = {
Identifier: {
exit(path) {
if (t.isIdentifier(path.node, {
name: '_0x2568'
})) {
// 参数
let args = path.parentPath.node.arguments
// 节点替换
if (args) {
args = args[0]
const len = arr.length
let index = parseInt(args.value, 16)
const val = arr[index]
path.parentPath.replaceWith(t.expressionStatement(t.stringLiteral(val)))
}
}
}
}
}
const traverse_member = {
MemberExpression(path) {
let property = path.get('property')
if (property.isStringLiteral()) {
const val = property.node.value
path.node.computed = false
property.replaceWith(t.Identifier(val))
}
}
}
const jscode = fs.readFileSync("./demo.js", {
encoding: "utf-8"
})
const ast = parser.parse(jscode)
traverse(ast, traverse_arr)
traverse(ast, remove_commma)
traverse(ast, remove_var_commma)
// traverse(ast, traverse_member)
const {
code
} = generator(ast)
fs.writeFile('./decode.js', code, (err) => {});
最后将反混淆之后的代码进行输出。得到基本能够看得懂的明文代码如下
没法上传附件了,随便扣一段反混淆之后的明文代码,结合上面截图中的混淆代码对比看一下
var _0x90c116 = _0x43e0bb("./MessageHelper"),
_0xbab33a = _0x43e0bb("./Logger"),
_0xe92f43 = _0x43e0bb("../Net/NetMsgHandler"),
_0x2072e8 = function () {
function _0x5064cc() {
this["connected"] = !0x1, this["socket"] = null;
}
return _0x5064cc["prototype"]["handleConnect"] = function (_0x342fe6) {
this["connected"] = _0x342fe6, null != this["onConnect"] && this['onConnect'](_0x342fe6);
}, _0x5064cc["prototype"]["handleOpen"] = function () {
this["handleConnect"](!0x0);
}, _0x5064cc['prototype']['handleMessage'] = function (_0x2bd9a6) {
if (null != this["onMessage"]) {
var _0x22c348 = _0x90c116["default"]["decode"](_0x2bd9a6["data"]);
this["onMessage"](_0x22c348["tag"], _0x22c348["tagGame"], _0x22c348["body"]);
} else _0xbab33a["logger"]["error"]("handleMessage:: this.onMessage is null! isConnected=%s", _0xe92f43["netMsgHandler"]["isConnected"]());
}, _0x5064cc["prototype"]["handleError"] = function () {
this['handleConnect'](!0x1);
}, _0x5064cc["prototype"]["handleClose"] = function () {
null != this["onClose"] && this["onClose"](), this["connected"] = !0x1;
}, _0x5064cc["prototype"]["connect"] = function (_0x18d706, _0x3fd53f) {
try {
'WebSocket' in window ? this["socket"] = new WebSocket("ws://" + _0x18d706 + ':' + _0x3fd53f) : "MozWebSocket" in window && (this['socket'] = new MozWebSocket("ws://" + _0x18d706 + ':' + _0x3fd53f)), this['socket']["binaryType"] = "arraybuffer", this["socket"]["session"] = this, this["socket"]["onopen"] = this["handleOpen"]["bind"](this), this['socket']["onmessage"] = this["handleMessage"]['bind'](this), this["socket"]["onclose"] = this["handleClose"]['bind'](this), this["socket"]["onerror"] = this["handleError"]['bind'](this);
} catch (_0x201db4) {
return this["handleConnect"](!0x1), void _0xbab33a["logger"]["error"](_0x201db4["message"] + '\x0a' + _0x201db4["stack"]);
}
}, _0x5064cc["prototype"]["setCallback"] = function (_0x59d05b, _0x26b217, _0x3d761e) {
this['onConnect'] = _0x59d05b, this["onMessage"] = _0x26b217, this["onClose"] = _0x3d761e;
}, _0x5064cc['prototype']['send'] = function (_0x2ef6f4, _0x30ae76, _0xf65e69) {
return this["socket"]['readyState'] === WebSocket["OPEN"] && (this["socket"]["send"](_0x90c116["default"]["encode"](_0x2ef6f4, _0x30ae76, _0xf65e69)), !0x0);
}, _0x5064cc["prototype"]["sendGameMsg"] = function (_0x4dbbf3, _0x37be87, _0x2298b8) {
return this["socket"]["readyState"] === WebSocket['OPEN'] && (this["socket"]["send"](_0x90c116["default"]["encodeGameMsg"](_0x4dbbf3, _0x37be87, _0x2298b8)), !0x0);
}, _0x5064cc["prototype"]["close"] = function () {
console["log"]('net.Close------>'), this['socket']['close']();
}, _0x5064cc["prototype"]["getWSState"] = function () {
return null == this['socket'] ? WebSocket["CLOSED"] : this["socket"]["readyState"];
}, _0x5064cc;
}();
反混淆之后,socket onmessage 等等,将上面大树组里面的值都替换到源代码里面去,就能很方便的对代码进行分析了。
有js,websocket,java,c#,安卓,等技术相关的协yi、逆向项目需求的老板可以找我详聊。
|
评分
-
查看全部评分
|