查看原文
其他

向Typora学习electron安全攻防

钞能力大叔 看雪学苑 2022-07-13


本文为看雪论坛优秀文章
看雪论坛作者ID:钞能力大叔


目标应用: aHR0cHM6Ly90eXBvcmEuaW8v
 
越来越多的应用开始使用 electron 来构建跨平台桌面应用。从实现方式上来说,其本质还是基于chrome内核的html、js、css构成的应用,基于浏览器,代码必定会暴露在用户侧,任何加密手段只是增加破解门槛跟时间成本而已。

认识electron项目文件目录特征


 
electron打包的项目,最常见的就是 asar 格式的私有编码文件,里面包含文件名、大小、内容偏移量等数据,按文件头部的 json内容 解析即可提取出所有文件。
 


electron asar解包


目前来说,官方的版本并没有提供保护源码的方法。在github开源的找到个大神提供的解决方案(https://github.com/toyobayashi/electron-asar-encrypt-demo) ,该方案可以把启动文件编译为node二进制文件,作为启动入口,来保护薄弱的js代码。在项目启动时,将加密后的代码进行解密,交回electron流程进行执行,从而避免上述步骤直接解包拿到源代码的可能。
 
经过分析对比,typora用的恰好是这个demo提供的思路。

 
找到上述步骤解压成功的 package.json 文件,main属性就是 electron 项目启动的主入口。把 main.node 拖到ida中, 分析执行流程。
 
 
结合 electron-asar-encrypt-demo 跟 IDA伪代码, 可以定位到两个关键函数 napi_create_string_utf8 跟 napi_call_function。

万能js提取方案


已知经过编译后的node文件, 在执行前,都会调用 napi_create_string_utf8 创建js字符串,所以在调用该函数的时候,基于electron没有自带加密的特性,想要运行js流程,必定会传递明文,所以在加载流程上截断,就可以导出解密后的数据。
 
直接上frida大法:
let napi_create_string_utf8 = Module.getExportByName(null, 'napi_create_string_utf8');var index = 0;if (napi_create_string_utf8) { console.log('绑定成功'); Interceptor.attach(napi_create_string_utf8, { onEnter: function (args) { console.log('napi_create_string_utf8', '调用', args[0], args[1].readCString().substring(0, 100), args[2], args[3]); if (args[2].toInt32() > 100) { // 过滤出大文件 index += 1; var f = new File('export_' + String(index) + '.js', 'wb'); f.write(args[1].readByteArray(args[2].toInt32())); f.flush(); f.close(); } } });} else { console.log('绑定失败');}
 
解包项目中的所有 asar文件,就能拿到所有的源代码,并且把解压的文件夹,改成对应的 asar 文件名即可正常运行项目,无需重打包即可调试。
 


无限试用思路


删除注册表键,让程序认为是第一次安装。
 
 
不想进入注册表这个繁琐的话,可以保存下面的代码, 把名字改成 xxx.reg, 双击运行即可无限畅玩。
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Typora]"IDate"="1/24/9999""SLicense"=""


去除授权功能实现单机版本思路


将所有的网络验证给他删掉/注释掉, 也可以直接把 “是否授权” 的变量,改成true即可。
 


注册机思路


由于该示例使用了rsa解密,所以要基于官方版本编写注册机就不太行了。一定要出注册机的话,需要先替换截图部分的rsa公钥即可。

 
这个地方使用了node自带的公钥解密,一些语言好像不能直接生成(可能我的打开方式不对),我就直接给出node版本的创建实例。
const crypto = require('crypto');const fs = require('fs');const path = require('path'); const keyPair = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: '' }}); fs.writeFileSync("public_key.pem", keyPair.publicKey);fs.writeFileSync("private_key.pem", keyPair.privateKey);

生成后的公钥, 覆盖即可,然后编写自己的注册机。

自建授权网关实现注册机思路


自建网关的话,可以把应用自带的域名,替换成自己的,然后按接口需要的返回值,给他返回响应的数据格式即可。这个实例仅需要修改两处即可。
 
 
 
接口就不提供了,放张效果图:


重打包发版思路


把解压的文件夹打包回 asar格式的文件即可, 这个网上一大把资料。


对于electron加密方式的思考


相对于原生开发来说,js安全做客户端毕竟薄弱,UI交互是没问题的,关键代码可以放到dll、so层去做,但是也没办法避免从js层面去剥离dll层函数的调用。所以目前来说并没有很好的解决方案,本文只是起到抛砖引玉的作用。期待electron有更好、更安全的解决方案。




看雪ID:钞能力大叔

https://bbs.pediy.com/user-home-860779.htm

*本文由看雪论坛 钞能力大叔 原创,转载请注明来自看雪社区



# 往期推荐

1.游戏安全之借坡下驴

2.EXP编写学习之绕过SafeSEH

3.CVE-2016-0165提权漏洞学习笔记

4.CVE-2015-0057提权漏洞学习笔记

5.Android Hook技术学习——常见的Hook技术方案总结

6.2022DASCTF MAY 出题人挑战赛






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存