网站地址:aHR0cHM6Ly9iei56enptaC5jbi9pbmRleA==

今天分析了极简壁纸的js逆向,学习记录一下。

一、抓包

打开F12并点击翻页,直接开始抓包,筛选XHR的请求

getData就是翻页的请求,查看一下返回的数据

数据被加密了,接下来进行分析解密

二、断点分析

下一个getData接口的XHR断点,并点击翻页,定位到断点位置

一路单步调试,可以看到醒目的'JSON["parse"]'、'["data"]["result"]'

这里其实就是开始解密的位置,可以把鼠标放上去看一下变量值,就是getData接口返回的密文值

而JSON.parse是解析json字符串的函数,那么解密函数就是这个_0x1addfb['a']['decipher']了,我们可以进去下断点,并继续运行跳到断点处

可以看到是一个A(B(C(D)))的函数调用,而D就是需要解密的密文,三个函数都可以通过在Console里打印跳过去下断点

其实他们就在嵌套函数的上面,可以分别进去单步调试,但可以发现这里并不是一些常规的MD5、AES等加密,而是纯算法运算,那就把这三个函数抠出来分析,为了方便称他们为A、B、C函数

代码还原

根据调用顺序分析,先看C函数

C函数很简单,参数就是接口返回的密文。先用atob对密文进行了base64的解码,然后用charCodeAt将每一个字符转换为Unicode,并依次放进一个int8Array数组中

转换成Python代码

import base64
import numpy as np
# 编码转换
plaintext = base64.b64decode(ciphertext.encode("utf8")).decode("latin1")
# 构造int8Array数组
big_list = np.zeros(len(plaintext), dtype=np.int8)
for i in range(len(plaintext)):
    big_list[i] = ord(plaintext[i])

这里JS的atob用python的base64库代替,JS的int8Array用numpy库的np.zeros(len(plaintext), dtype=np.int8)代替,需要注意的是base64解码后是字节类型,因为有很多特殊字符,用utf-8来decode会报错,要用"latin1"来解码。

运行结果对比

然后看B函数

B函数里有很多十六进制的值,先用AST将其还原,将代码放入AST Explorer,通过选中值可以看到十六进制的值都是有一个extra属性的,而十进制的值是在value属性里,那么只需要删除所有NumericLiteral节点的extra属性,即可让十六进制的值转为十进制,方便分析

对应AST函数

// 替换十六进制为十进制
function replace_0x0(path){
    var node = path.node
    if (node.extra === undefined){
        return;
    }
    delete node.extra
}

还原后的代码

将B函数中的代码略做调整,将每个逗号表达式都换成分号,转成一行新的代码

按照格式和运算符,直接转成Python代码即可,语法上只有js的push函数要换成python的append方法,big_list变量是C函数的返回值

测试一下输出,结果一模一样

最后看A函数,做了大量的三元运算符的计算,并且计算的值都是十六进制的

三元运算符、十六进制都可以用AST来进行还原,但还原之前先把代码做一下调整,这个函数里有一个for循环,这个for循环没有花括号,其实只有一行代码,把花括号加上,并把这一行代码里的前两个逗号换成分号,让这两个赋值语句和三元表达式不在同一条代码,方便后面用AST进行处理

AST十六进制的处理和B函数处理时一致,而三元表达式需要转换为if else的形式,方便进行分析,即将所有ConditionalExpression节点替换为ifStatement节点

对应AST处理

// 替换三元表达式为if else
function replace_conditional(path){
    const {test, consequent, alternate} = path.node;
    let ifstate = t.BlockStatement([t.ExpressionStatement(consequent)], [])
    let elsestate = t.BlockStatement([t.ExpressionStatement(alternate)], [])
    path.replaceWithMultiple(t.ifStatement(test, ifstate, elsestate));
}

替换完成后,A函数就变成了这个样子

继续分析A函数代码,在循环中,是遍历B函数返回值的每一个元素赋值给_0x481fd7,然后进行 _0x481fd7 >>> 7 === 0的运算,无符号右移7位,并判断是否等于0,根据之前的运算,传入A函数的数组元素值的范围都在0~127之间,在这之间的所有值>>>7后结果都是0,也就是说这个嵌套if else结构只需要将第一个if中的语句提取出来就可以了

转换成Python代码就是很简单的将数组元素全部用chr从数字转换为字符,并拼接到一起,data变量为传入A函数的参数

分别运行对比一下

结果一致,加密数据被解密成了json字符串,那么这个混淆后的A(B(C))就已经完成反混淆并转换为Python代码了。

最后

分析这个站仅是为了达到学习的目的,由于极简壁纸是个人维护的免费网站,为了避免给站长造成不必要的困扰,就不贴完整代码了。


oh yeah