1.app.asar分析
通过大佬们的文章知道Typora是通过加载main.node文件执行的,直接分析app.asar文件就行,asar是electron用的归档文件通过asar解压出来即可
asar extract ./app.asar ./app.unpacked
解压出来后发现atom.js是加密后的文件,这个应该就是typora的js逻辑代码了。直接分析main.node文件看看有没有引用atom.js
2. main.node分析
通过查找main.node中的字符串找到0xE57D这里有对字符串./atom.js的引用
这里实际是调用napi_create_string_utf8创建了一个给js环境使用的字符串./atom.js,
通过静态分析知道v66是创建好的atom.js的字符串地址, 实际是被传入sub_4467中调用napi_call_function来调用js函数
通过动态分析可以知道这里执行的js函数是
(function makeRequireFunction(e) {
const n = e.constructor;
function t(e, n) {
if ("string" != typeof e) throw new TypeError('The "' + n + '" argument must be of type string. Received type ' + typeof e)
}
const r = function(n) {
return e.require(n)
};
function o(r, o) {
return t(r, "request"),
n._resolveFilename(r, e, !1, o)
}
return r.resolve = o,
o.paths = function(r) {
return t(r, "request"),
n._resolveLookupPaths(r, e)
},
r.main = process.mainModule,
r.extensions = n._extensions,
r.cache = n._cache,
r
})
参数是字符串atom.js,可以看到实际是require("./atom.js"),了解js的都知道直接require一个加密的js会直接报错,这里猜测应该是重写了require的底层
这里重新创建了一个_compile函数并调用了napi_define_properties来覆盖当前Module的_compile函数,js调用require函数时会调用到_compile函数解析js,应该是在_compile函数中完成对atom.js的解密,这里直接分析_compile函数。
通过动态分析最终会调用到sub_10BF0执行解密并执行atom.js中的代码
这里sub_4647会将atom.js进行解密并返回js的字符串指针。
3.解密流程分析
直接来到sub_B480,拿到atom.js的密文后首先通过base64解密,base64解密完成后去掉了解密后的最后一个字节并创建了新的字节数组。
下面要确定使用的是什么加密,sub_17070会使用到新创建出的密文去解密,每次解密后都会将密文的最后16个字节和解密后的数据异或操作基本可以确定key的最后16个字节是iv,加密方式是AES的CBC模式
直接在main.node偏移B784处断点拿到key和iv,rcx就是第一个参数前32字节是key最后16字节是iv
接下来去CyberChef验证下,解密时注意最后一个字节是用来计算iv的不参与到aes运算的需要删除
拿到js源码后找到验证相关的代码直接patch掉,再打包成app.asar替换原来的
4.破解加密
为了替换原来的atom.js需要通过aes加密后替换回去,这里的难点在于iv的计算是通过原字符串长度和最后一个字节共同计算的,可以看sub_1992的实现,这里为了省事直接调用dll的函数计算iv,把文件长度和最后一个字节改了就能直接成一个iv了
#include <windows.h>
#include <iostream>
typedef int64_t(*FunctionPtr)(int64_t* array, int64_t p2, int64_t p3);
int main() {
HMODULE hModule = LoadLibrary(L"./main.node");
DWORD_PTR baseAddress = (DWORD_PTR)hModule;
DWORD_PTR functionOffset = 0x1992;
DWORD_PTR functionAddress = baseAddress + functionOffset;
FunctionPtr func = (FunctionPtr)functionAddress;
if (!func) {
std::cerr << "无法获取函数指针" << std::endl;
FreeLibrary(hModule);
return 1;
}
const int64_t arrayLength = 3;
int64_t inputArray[arrayLength] = {0,0 ,0};
int64_t len = 0x22531;
int64_t param3 = 0x10;
byte last_byte = 0x6a;
int64_t result = func(inputArray, (len%256)^last_byte, 0x10);
byte* res = (byte*)(inputArray[0]);
for (size_t i = 0; i < 16; i++)
{
std::cout <<"0x" << std::hex << static_cast<int>(res[i]);
}
FreeLibrary(hModule);
return 0;
}
5.打包
万事俱备只欠东风,aes加密有了key和iv,这里最后一个计算iv的字节我用的'0xb7',按下边的步骤来就好
1.先将修改后的js文件进行aes加密
openssl enc -aes-256-cbc -in dump.js -out atom.byte -iv 9B342BB23D876525D200266E2A2E46F7 -K aes_key
2.将计算iv用的字节写入到文件最后一个字节
printf '\xb7' >> atom.byte
3.base64加密
base64 --wrap=0 atom.byte > atom.js
4.asar打包覆盖原来的app.asar
asar.cmd pack .\appasar\ app.asar --unpack-dir "{main.node}"
最后启动激活完成