Typora破解之逆向分析(上)

Typora破解之逆向分析(上)

开发环境识别

用IDA打开Typroa.exe,可以看到有electron、Node字样

打开程序目录,可以看到asar文件、node字样,联想到nodeJs,猜测可能是由JS写的

在查询后得知electron是使用Nodejs的前端开发框架的桌面应用程序,electron使用了谷歌的v8引擎以及渲染引擎,分为主进程喝渲染进程,通过IPC进行交换信息。asar格式文件是和tar风格的归档格式,electron无需解压即可从中读取任意文件。

可以看到在app.asar.unpacked中发现了一个main.node,node文件是nodejs解析器将js代码转换成二进制代码的文件格式,所以js代码大都在这里面

接下来打开Typora进程,使用x64dbg附加找到typora相关信息,发现有好几个进程,可以看到命令行参数可以看到渲染gpu等关键字,并且目录指向了app.asar

由于只有一个主进程,其他附加进程都是主进程创建的,我们来看下附加进程。可以看到其中有个main.node模块,为什么node可以当dll加载呢?

使用PE工具查一下,VS2017编译的64位dll

使用010editor查看该app.asar,得到文件信息和加密信息

逻辑分析

已知信息

  • 框架为electron,框架会直接加载main.node模块
  • JS文件被加密,解析JS脚本的是V8引擎

根据已知信息,对程序的整体逻辑进行简单的分析

  • V8不能解析加密的JS代码,需要将加密的JS代码进行解密后才能送到js引擎执行
  • 由于electron框架需要加载main.node模块,可能就是需要对加密的js代码进行解密操作

整体逻辑

框架加载main.node模块去解密app.asar的JS代码并送到JS引擎中进行渲染执行(一种是解密所有JS代码;一种是解密某个js代码,其他代码由解密出来的那个代码进行解密)

node分析

根据上面的分析得到main.node是解密模块,我们来对其进行具体分析

根据逆向的惯例,我们先寻找字符串,可以看到有Buffer、base64、app.asar、electron等字样

使用FindCrypt3插件找到AES的算法常量

我们来看看AES的加解密操作

  • 选择合适的密钥长度:AES算法支持128位、192位和256位三种密钥长度。根据安全需求选择合适的密钥长度。
  • 选择加密模式和填充方案:根据应用场景和需求选择合适的加密模式和填充方案。常见的加密模式有ECB、CBC、CTR等,常见的填充方案有PKCS7、PKCS5、NoPadding等。
  • 密钥生成和扩展:根据选择的密钥长度生成密钥,并通过密钥扩展算法生成每一轮操作所需的子密钥。
  • 分组处理:将待加密的明文按照分组长度(128位)进行划分,得到多个分组。
  • 加密过程:对每个分组进行多轮的迭代操作,包括字节替代、行移位、列混淆和轮密钥加等步骤,最终得到密文。
  • 解密过程:对密文进行逆向操作,包括轮密钥逆序加、逆向列混淆、逆向行移位和逆向字节替代等步骤,最终得到明文。

我们在上面的字符串中找到了app.asar这个关键词,我们进一步观察用到其被交叉引用的伪代码

可以推测这个函数加载了app.asar的内容,然后调用sub_180003E40对其进行解密

跟进sub_180003E40函数,发现base64、buffer和from等字符串的使用,查阅相关资料,推测使用了Buffer.from()对缓冲区中的数据进行Base64解码

在观察代码的时候可以看到很多Node API函数

1
2
3
4
napi_get_named_property
napi_get_global
napi_get_named_property
......

可以看到这部分的函数调用和C语言实现AES算法结构相似,猜测为AES解密算法

已知信息:

  • main.node模块使用node api进行js函数调用
  • main.node模块使用AES解密算法

根据已知信息进行如下分析:

  • 分析算法找到解密密钥以及判断模式,使用解密算法对其进行解密获取JS代码
  • 分析程序执行流程,找到解密后的JS代码缓冲区获取解密后的JS代码

寻找JS代码

这里选用第二方法去寻找解密后的缓冲区代码

我们先来观察解密之前的JS函数napi_call_function()调用参数有两个

我们来看下这个node api接口函数

1
2
3
4
5
6
NAPI_EXTERN napi_status napi_call_function(napi_env env,            //环境
napi_value recv, //名为global的值
napi_value func, //要调用的javascript函数
size_t argc, //JavaScript函数的参数个数 类似argc
const napi_value* argv, //JavaScript函数的参数数组 类似argv
napi_value* result); //返回的JavaScript对象

接下来我们进行一波动态调试,使用x64dbg对main.node进行下断点动态调试

点击调试后,断点跳转到如下点

通过上面分析定位函数位置,同样为模块偏移为674A的位置

下图为x64dbg的函数位置,并为其打上断点进行调试

运行调试后可以看到该函数的参数以及对应的内存位置,这里可以看到函数参数为 rcx rdx r8 r9 rsp+0x20,超过四个则入栈

如下图可以看到其调用的参数

接下来我们再来观察在调用JS代码后地址会有什么改变

第五个参数为rsp+20,也就是rax的值,进入内存查看可以得到和前面同样的地址,也就是第一个参数

下面继续分析AES解密代码部分,会将密文的缓冲区拿过来进行解密

首先进行一串16进制的赋值,v46的数组刚好32字节,也就是256bit,然后v32申请内存,调用sub_18000B060函数对v46和v32进行操作

继续分析sub_180006AC0,反汇编得到伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
__int64 __fastcall sub_180006AC0(v45,block,block_size)
{

if ( block_size )
{
v3 = block;
v5 = v45 + 0xF0 - (_QWORD)block; //v45+0xF0的地址 减去 block的地址得到v5
v6 = ((block_size - 1) >> 4) + 1; //做为外圈循环的次数
do
{
v7 = *v3;
//v7为 xmmword 16字节浮点寄存器 ,把block的内容取16字节给v7 16字节符合AES块大小
//由此推测block是真正的密文,将在这个函数中进行解密操作

sub_180007320(v3, v45); //用到了AES解密常量 应该是解密相关 并且对推测的key 也就是前32字节有一些操作
v8 = 16i64; //内圈循环16次
do
{
result = *((char*)(v3 + v5)); //block地址 + v5偏移 取一个字节内容
*(char*)v3 ^= result; //取block的1字节数据,与block地址 + v5偏移 进行异或
v3 = (__int128 *)((char *)v3 + 1); //block += 1
--v8; //总共16次 也就是16个字节异或
}
while ( v8 );
v5 -= 16i64; //外圈循环 v5 每次-16 也就是每次异或 异或的值都会变化 范围为-16字节
v45 + 0xF0 = v7; //block的16字节内容 给到v45+0xF0
--v6; //外圈循环次数
}
while ( v6 );
}
return result;
}

可以推测该函数为主要的解密算法函数,key存放在v45中,前32位也就是256位,iv存放在block+v5中

获取JS代码

根据前面的代码分析,只需要在彻底解密后在送到JS引擎执行的时候拿到该解密后的代码即可

根据上层函数调用,解密后返回一个值作为调用JS函数的参数

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信