反混淆

插件源码的主要部分在 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 为 thirdfactooorinput 标签。

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"];
        }
    }
}

在这里学习了一下 MutationRecord 的函数,MutationRecord JavaScript APItype 会随着不同的操作改变

  • 改变 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>

直接放在本地跑,成功

poc

Capture the flag

远程服务提供了一个 /submit 接口,可以提交我们的 html 文件,然后会用类似给这个 html “拍照”的方式,以图片格式返回。期间, html 文件里的 js 代码都会执行,但是 alert() 会引起报错,基本排除了 XSS 的可能。直接上传,DONE!

successfully_done

proof_of_solved

总结

第一次解出国际赛的题目,当能 getflag 的时候手都在抖。总体过程还是反混淆最折磨人,代码的逻辑并不复杂,getflag 也不需要绕太多的弯,只要能把 js 恢复出来就成功了大半。