反混淆
插件源码的主要部分在 content_script.js
, 但因为经过混淆,根本不是给人看的,于是得先反混淆。考虑到 js 本身就是解释型语言,我可以直接在 chrome devtools 中下断点,然后在 console 里打印我想要的变量值。最外层的函数不是特别多,所以依次下断点配合 devtools 的控制台获取变量。
这段过程都是体力活,就不具体描述了,最后反混淆出了大致能看的 js 代码 readable_code.js。
最后看下来,反混淆的部分是最重要的,因为后面的分析都不是很难。
本地分析
反混淆以后,可以看到在源码的最后有一段代码会定时向后台获取 flag 并输出。
observer["observe"](document, config),
console["log"]("The observer is observing."),
setTimeout(function() {
const _0xd26915 = {};
_0xd26915["getflag"] = _0x10b2d5["xOsuT"], //true
chrome["runtime"]["sendMessage"](_0xd26915, function(_0x336e82) {
// _0x336382 is response from backend included with flag
FLAG = _0x336e82["flag"],
console['log'](("flag: " + _0x336e82["flag"]));
// four && and display flag on the #thirdfactooor
nodesadded == 5 && (nodesdeleted == 3) && \
attrcharsadded == 23 && (domvalue == 2188) && \
(document["getElementById"]("thirdfactooor")['value'] = _0x336e82["flag"]);
// append new div
const _0x369bcb = document["createElement"]("div");
_0x369bcb["setAttribute"]("id", "processed"),
document["body"]["appendChild"](_0x369bcb);
});
}, 500);
其中这个 document["getElementById"]("thirdfactooor")
是在 check_dom()
会进行的一项检查,我们需要让 html 中有一个 id 为 thirdfactooor
的 input
标签。
if (document["querySelector"]("thirdfactooor")["tagName"] == "INPUT") {}
只需要满足
nodesadded == 5 && (nodesdeleted == 3) && attrcharsadded == 23 && (domvalue == 2188)
依次找到赋值的地方
/*
> _0x8a010b instanceof MutationRecord
<· true
*/
if (_0x8a010b["type"] === "childList") {
if (false) {
// never execute
} else {
nodesadded += _0x8a010b["addedNodes"]["length"],
nodesdeleted += _0x8a010b["removedNodes"]["length"];
}
} else {
if ((_0x8a010b["type"] === "attributes")) {
if (true) {
attrcharsadded += _0x8a010b["attributeName"]["length"];
}
}
}
在这里学习了一下 MutationRecode
的函数,MutationRecord JavaScript API。 type
会随着不同的操作改变
- 改变
Node.childNodes
时会变成childList
- 改变
Element.attribute
时会变成attributes
按要求我们需要5次节点增加,3次节点移除,改变的属性名的长度总和为23。
需要注意的是,在这段赋值操作前,有一处判断会导致程序提前退出。
var _0x5b12b9 = document["getElementById"]("3fa");
...
if ((_0x8a010b["target"] === _0x5b12b9) || \
_0x8a010b["target"]["parentNode"] === _0x5b12b9 || \
_0x8a010b["target"]["parentNode"]["parentNode"] === _0x5b12b9) {}
else return;
所以 id 为 #3fa
的节点必须是根节点(可以直接给 <html>
)。
接下来在调试的时候观察到 domvalue
与 .html
文件中的字数有关,凑足一定的字数即可。
<input id="thirdfactooor" size="1000px">
<script>
let fa = document.getElementsByTagName("html")[0];
fa.setAttribute("id", "3fa")
for(let i = 0; i < 4; i++) {
fa.lang = i; // 2 + 4 chars * 4 + 5
}
let a = document.createElement("div")
let b = document.createElement("div")
let c = document.createElement("div")
let d = document.createElement("div")
let e = document.createElement("div")
fa.appendChild(a)
fa.appendChild(b)
fa.appendChild(c)
fa.appendChild(d)
fa.appendChild(e)
fa.removeChild(a)
fa.removeChild(b)
fa.removeChild(c);
console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
</script>
直接放在本地跑,成功
Capture the flag
远程服务提供了一个 /submit
接口,可以提交我们的 html 文件,然后会用类似给这个 html “拍照”的方式,以图片格式返回。期间, html 文件里的 js 代码都会执行,但是 alert()
会引起报错,基本排除了 XSS
的可能。直接上传,DONE!
总结
第一次解出国际赛的题目,当能 getflag
的时候手都在抖。总体过程还是反混淆最折磨人,代码的逻辑并不复杂,getflag
也不需要绕太多的弯,只要能把 js 恢复出来就成功了大半。