JS反混淆-控制流平展(二)

    技术2022-07-11  105

    0x00 问题定义

    接第一篇遗留的问题,在做控制流平展反混淆时,用动态执行的方式获取执行路径时,如果原控制流中存在循环的情况,那么获取到的路径就会有重复的现象,如下所示:

    [ [ 3 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 0 ] ]

    这里可以发现,[1],[2]的序列重复出现了11次,这种重复的路径信息可以推断其原始路径是一种循环的状态。如果想把它还原成循环的形式,那么就需要做两部分工作:

    识别路径结果中的重复路径。以循环的方式修复控制流。

    0x01 解决方案

    最小相邻重复子串问题

    假定有一个字符串A,找到它的最小相邻的重复子串,该子串符合如下特征:

    (1)必有一个有多个与其相邻且相同的子串

    (2)子串内部不存在重复子串(最小粒度)

    这样的子串,就是要找的典型的执行路径存在循环的情况,最小子串即循环内部的逻辑。

    如上所示执行路径,其存在一个最小相邻重复子串[1],[2],长度为2,起始位置是1,重复了11次。就可以用1,2,11这样的三元组来表示查找结果。

    还原时,遍历执行路径,如果某个路径位置上存在最小相邻重复子串即认为其是一个循环,就根据三元组创建一个for循环,并把重复的路径语句作为for循环的内容。

    循环嵌套问题

    最小相邻重复子串找到的是粒度最小的循环,实际情况中,该循环外层还可能有循环,即循环存在包含关系。比如如下动态执行路径:

    [ [ 3 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 3 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 5 ], [ 1 ], [ 2 ], [ 1 ], [ 3 ], [ 0 ] ]

    如上所示路径,路径[ 3 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ], [ 1 ], [ 2 ]循环了2次,在该循环内部,还有一个循环了4次的循环[ 1 ],[ 2 ]。再加上最后的6个单项。其大概的形式就是:

    for (i = 0; i < 2; i++) { s3; for (j = 0; j < 4; j++) { s1; s2; } } s5; s1; s2; s1; s3; s0;

    我们期望达到的效果是,在作控制流平展反混淆时,当路径中存在循环情况时,按照循环的方式对其进行还原。

    现在的问题就是,如何表示查找结果,我这里采用一个树形结构去表示它。

    loop1表示循环了2次的外层大循环,atom1表示序列[ 5 ], [ 1 ], [ 2 ], [ 1 ], [ 3 ], [ 0 ],atom2表示序列[ 3 ], loop2表示循环了4次的内层小循环。atom3表示序列[ 1 ],[ 2 ]。这样,就用树形结构代替了队列结构保存了执行路径。

    下面就是根据树形结构的分析结果来进行路径还原。

    使用一个递归算法逐层取查找,如果是loop节点就构造一个for循环,并进一步查找它的孩子节点,把孩子节点作为for的body语句,递归的退出条件是atom节点才停止查找。代码实现如下:

    function setPath(astPath, pathNode, cases) { if (pathNode.is_atom()) { addSeq(astPath, pathNode, cases) } if (pathNode.is_loop()) { var forState = createForStatement(pathNode.loopTimes, []) for (var pIndex = 0; pIndex < pathNode.child.length; pIndex++) { var node = pathNode.child[pIndex] setPath(forState, node, cases); } astPath.body.body.push(forState); } }

    这样就完成了循环方式的恢复。

    0x02 反思

    按照常规的情况,原始路径可能存在三种情况:

    无分支路径。存在判断分支路径。存在循环路径。

    而我们通过动态执行的方式去获取路径信息,因为动态时某个分支路径选择的地方必然会走其中某一分支路径,相当于把第2种情况弱化成第1种情况了。所以只需要考虑1和3两种情况。 不过,动态获取路径也有缺点,除非测试所有输入,否则不能保证路径完全覆盖。即使保证路径完全覆盖,那还要解决多种情况路径整合的问题。如果只是需要知道在某个确定输入情况下的路径,那就比较适合采用动态获取路径的方式。

    Processed: 0.012, SQL: 9