61 Commits

Author SHA1 Message Date
MasOnShi
c1beef1a98 #57: add support for linux-x64 2022-04-04 20:04:52 +08:00
MasOnShi
d16428a8b2 change readme content 2022-04-04 17:08:18 +08:00
MasOnShi
d13b7df457 change readme content 2022-04-04 16:31:58 +08:00
MasOnShi
f09cf7a8b5 add win x86 supports. 2022-04-04 16:11:04 +08:00
MasOnShi
43b858c3ce add win x86 supports. 2022-04-04 15:09:07 +08:00
MasOnShi
937e3aa178 add win x86 supports. 2022-04-04 14:55:19 +08:00
MasOnShi
f340eef89c dev commits. 2022-04-04 13:51:22 +08:00
MasOnShi
c3fa3cb361 dev commits. 2022-04-04 13:34:38 +08:00
MasOnShi
7176bb2f82 dev commits. 2022-04-04 13:26:25 +08:00
MasOnShi
8f467159c7 dev commits. 2022-04-03 21:50:23 +08:00
MasOnShi
5489c3b664 dev commits. 2022-04-03 21:47:37 +08:00
MasOnShi
1710c83f99 dev commits. 2022-04-03 20:51:11 +08:00
MasOnShi
219e0316f5 dev commits. 2022-04-03 20:47:47 +08:00
MasOnShi
60a7f39b16 dev commits. 2022-04-03 20:47:37 +08:00
MasOnShi
3c9d47b625 dev commits. 2022-04-03 20:41:22 +08:00
MasOnShi
16c225bb1f dev commits. 2022-04-03 20:38:48 +08:00
MasOnShi
643c0f3e3c dev commits. 2022-04-03 20:33:02 +08:00
MasOnShi
d84a6b9041 dev commits. 2022-04-03 20:26:41 +08:00
MasOnShi
e21922b1f1 dev commits. 2022-04-03 20:22:25 +08:00
MasOnShi
18e2562146 dev commits. 2022-04-03 20:16:16 +08:00
MasOnShi
d870e234c3 dev commits. 2022-04-03 20:14:34 +08:00
MasOnShi
76c9a5b398 dev commits. 2022-04-03 20:12:42 +08:00
MasOnShi
25cb04e00f dev commits. 2022-04-03 20:09:41 +08:00
MasOnShi
1c879400fd dev commits. 2022-04-03 20:05:35 +08:00
MasOnShi
d5f9fa4026 dev commits. 2022-04-03 19:58:21 +08:00
MasOnShi
11589aff49 dev commits. 2022-04-03 19:57:35 +08:00
MasOnShi
2a7fd5622a dev commits. 2022-04-03 19:50:15 +08:00
MasOnShi
df83d9cae4 updaate aesIv for 1.2.2-dev 2022-04-03 19:28:39 +08:00
mason
768f9e870a updaate aesIv for 1.2.2-dev 2022-03-15 12:24:26 +08:00
mason
4d15e2a665 updaate aesKey for 1.2.2-dev 2022-03-14 21:07:22 +08:00
Mas0nShi
6a052ea508 fix #32. 2022-02-22 16:20:34 +08:00
Mas0nShi
214f7b5c79 fix the temp folder existence errors. 2022-02-22 10:58:36 +08:00
Mason Shi
7c83710ede Update README_CN.md 2022-02-21 17:26:24 +08:00
Mason Shi
dac502ef7f Update README.md 2022-02-20 18:08:39 +08:00
Mason Shi
6874fc54bf Update README_CN.md 2022-01-04 23:26:44 +08:00
Mason Shi
f74141b4fa Update README_CN.md 2022-01-04 23:25:59 +08:00
Mason Shi
71c41fcaec Create README.md 2021-12-23 14:53:06 +08:00
Mason
1d28997146 solve #18 2021-12-12 14:54:44 +08:00
Mason
ed3fb49ad0 solve issue #13 and optimize some details 2021-12-04 22:27:20 +08:00
Mason
f3310f448a rewrite details 2021-12-03 05:29:04 +08:00
Mason Shi
1927e06b26 Update README.md 2021-12-01 16:12:28 +08:00
Mason
d067e4e296 Merge remote-tracking branch 'origin/master' 2021-12-01 16:10:55 +08:00
Mason
0147f7c384 add Note 2021-12-01 16:10:08 +08:00
Mason
1c7a0daf60 add Note 2021-12-01 16:09:39 +08:00
Mason Shi
a45d1b01a8 Update codeql-analysis.yml 2021-12-01 15:28:35 +08:00
Mason
957e412291 Initial commit 2021-12-01 15:21:18 +08:00
Mason
d5c1bc55c2 Initial commit 2021-12-01 15:21:18 +08:00
Mason Shi
6152b16fca Update codeql-analysis.yml 2021-12-01 15:15:43 +08:00
Mason Shi
f8ef3769c7 Merge pull request #6 from Mas0nShi/add-license-1
Delete keygen.js
2021-12-01 15:11:31 +08:00
Mason Shi
b43a44045f Delete keygen.js
abide by the rules
2021-12-01 15:11:03 +08:00
Mason Shi
9787c2dd50 Delete keygen.js
abide by the rules
2021-12-01 15:10:47 +08:00
Mason Shi
9589526703 Delete patch directory 2021-12-01 15:10:35 +08:00
Mason
263c976b1a abide by the rules 2021-12-01 15:10:09 +08:00
Mason
9fd4bad0ab Initial commit 2021-11-30 23:20:33 +08:00
Mason Shi
009b2ebff3 Merge pull request #3 from laowenruo/patch-1
Update README.md
2021-11-30 19:55:28 +08:00
Mason Shi
583ae2efe7 Merge pull request #4 from laowenruo/patch-2
Update README_CN.md
2021-11-30 19:54:47 +08:00
coder_ryan
f586be4b1e Update README_CN.md
typroa -> typora
2021-11-30 19:14:51 +08:00
coder_ryan
c60fa54d54 Update README.md
typroa -> typora
2021-11-30 19:14:05 +08:00
Mason
4532c94d2b Initial commit 2021-11-30 15:32:21 +08:00
Mason Shi
41ae7e552b Update README.md 2021-11-30 15:24:07 +08:00
Mason Shi
00f916b16f none 2021-11-30 15:16:00 +08:00
22 changed files with 342 additions and 363 deletions

View File

@@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
language: ["javascript" , 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support

View File

@@ -1,36 +1,54 @@
# typora Cracker
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FMas0nShi%2FtyporaCracker.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FMas0nShi%2FtyporaCracker?ref=badge_shield)
![GitHub](https://img.shields.io/github/license/Mas0nShi/typoraCracker)
**typoraCracker STOPS MAINTENANCE NOW. [why](https://github.com/Mas0nShi/typoraCracker/issues/39#issuecomment-1083117056)?**
A patch and keygen tools for typora.
# typora Cracker
![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FMas0nShi%2FtyporaCracker.svg?type=shield)
![GitHub](https://img.shields.io/github/license/Mas0nShi/typoraCracker)
[![Automatic analysis](https://github.com/Mas0nShi/typoraCracker/actions/workflows/manual.yml/badge.svg)](https://github.com/Mas0nShi/typoraCracker/actions/workflows/manual.yml)
A extract & decryption and pack & encryption tools for typora.
中文说明请戳[这里](README_CN.md)
## WARNING
**NOTE: typoraCracker doesn't provide support for crack.**
```
FOR STUDY AND DISCUSSION ONLY, PLEASE DO NOT ENGAGE IN ANY ILLEGAL ACTS.
ANY PROBLEMS ARISING FROM THIS WILL BE BORNE BY THE USER (YOU).
```
## Features
- Supports Version 1.0.0 - 1.2.0 [(old archive)](https://github.com/Mas0nShi/typoraCracker/tree/backup-raw)
- Supports Version 1.2.+ [(Experimental archive)](https://github.com/Mas0nShi/typoraCracker/tree/master)
## Support List
2022.4.4: Experimental support for automatic binary analysis to generate scripts. (v1.2.+)
| OS / ARCH | x86 | x64 | arm64 |
|:---------:|:---:|:---:|:-----:|
| win | ✅ | ✅ | ❌ |
| linux | ❌ | ❌ | ❌ |
| macOS | ❌ | ❌ | ❌ |
- Supports ALL OS supported by typora
## Usage
1. `pip install -r requirements.txt`
2. `python typroa.py --help`
3. read and use.
4. patch License.js.
5. replace app.asar.
6. run keygen.
7. enjoy it.
1. download in [Release Pages](https://github.com/Mas0nShi/typoraCracker/releases)
2. unzip
3. `pip install -r requirements.txt`
4. `python typora.py --help`
5. read and use.
6. do something.
7. pack and replace app.asar.
8. enjoy it.
## Example
```shell
> python typroa.py --help
> python typora.py --help
usage: typora.py [-h] [-u] [-f] asarPath dirPath
[extract and decryption / pack and encryption] app.asar file from [Typora].
@@ -48,12 +66,12 @@ If you have any questions, please contact [ MasonShi@88.com ]
> python typora.py {installRoot}/Typora/resources/app.asar workstation/outfile/
> python typora.py -u workstation/outfile/ workstation/outappasar
# (patch code by yourself in workstation/outfile/dec_app)
> python typora.py -u workstation/outfile/dec_app workstation/outappasar
> cp {installRoot}/Typora/resources/app.asar {installRoot}/Typora/resources/app.asar.bak
> mv workstation/outappasar/app.asar {installRoot}/Typora/resources/app.asar
# (patch code)
> node keygen.js
> node example/keygen.js
XXXXXX-XXXXXX-XXXXXX-XXXXXX
> typora
# (input info)

View File

@@ -1,32 +1,50 @@
**typoraCracker 停止维护. [为什么](https://github.com/Mas0nShi/typoraCracker/issues/39#issuecomment-1083117056)**
# typora Cracker
一个typora的Patch和KeyGen工具
一个typora的解包&解密,打包&加密工具
## 敬告
**请注意:** typoraCracker不会提供破解相关支持包括但不限于思路、流程、成品。
```
仅供学习和讨论,请不要从事任何非法行为。
由此产生的任何问题都将由用户(您)承担。
```
## Features
- 版本 1.0.0 - 1.2.0 [(使用旧的存档)](https://github.com/Mas0nShi/typoraCracker/tree/backup-raw)
- 版本 1.2.+ [(使用测试存档)](https://github.com/Mas0nShi/typoraCracker/tree/master)
## Support List
2022.4.4: 支持自动化分析二进制文件并生成脚本(测试性功能 v1.2.+
| OS / ARCH | x86 | x64 | arm64 |
|:---------:|:---:|:---:|:-----:|
| win | ✅ | ✅ | ❌ |
| linux | ❌ | ❌ | ❌ |
| macOS | ❌ | ❌ | ❌ |
- 理论上支持Typora支持的所有操作系统
## 食用方式
1. `pip install -r requirements.txt`
2. `python typroa.py --help`
3. 阅读帮助文档及使用。
4. 修改导出的 License.js。
5. 替换原目录下的 app.asar
6. 运行KeyGen程序
7. 正常激活。
1. 前往[Release Pages](https://github.com/Mas0nShi/typoraCracker/releases) 下载对应版本
2. 解压
3. 安装依赖:`pip install -r requirements.txt`
4. 使用说明:`python typora.py --help`
5. 使用
6. 做你想做的事
7. 打包并替换原目录下的 app.asar。
8. 享受成果。
## 示例
```shell
> python typroa.py --help
> python typora.py --help
usage: typora.py [-h] [-u] [-f] asarPath dirPath
[extract and decryption / pack and encryption] app.asar file from [Typora].
@@ -44,12 +62,12 @@ If you have any questions, please contact [ MasonShi@88.com ]
> python typora.py {installRoot}/Typora/resources/app.asar workstation/outfile/
> python typora.py -u workstation/outfile/ workstation/outappasar
# (patch code by yourself in workstation/outfile/dec_app)
> python typora.py -u workstation/outfile/dec_app workstation/outappasar
> cp {installRoot}/Typora/resources/app.asar {installRoot}/Typora/resources/app.asar.bak
> mv workstation/outappasar/app.asar {installRoot}/Typora/resources/app.asar
# (patch code)
> node keygen.js
> node example/keygen.js
XXXXXX-XXXXXX-XXXXXX-XXXXXX
> typora
# (input info)
@@ -58,4 +76,4 @@ serial: XXXXXX-XXXXXX-XXXXXX-XXXXXX
```
## LICENSE
MIT LICENSE
MIT LICENSE

View File

@@ -0,0 +1,23 @@
from utils import get_version, download_file, extract_file, log
from config import DOWNLOAD_LINK
import os
BASE_DIR = os.path.dirname(__file__)
def run_version(download_os, download_arch):
from_url = DOWNLOAD_LINK[download_os][download_arch]
to_dir = os.path.join(BASE_DIR, f"{download_os}/{download_arch}")
download_path = os.path.join(to_dir, os.path.basename(from_url))
download_file(from_url, download_path)
extract_file(download_path, to_dir)
version = get_version(to_dir)
open(os.path.join(to_dir, "LATEST_VERSION"), "w").write(version)
log.success(f"{download_os}-{download_arch} the latest version is {version}")
if __name__ == '__main__':
run_version("win", "x64")
# run_version("win", "x86")
# run_version("linux", "x64")

20
auto-analysis/config.py Normal file
View File

@@ -0,0 +1,20 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: config.py
@Time: 2022/4/4 19:50
@Desc: It's all about getting better.
"""
DOWNLOAD_LINK = {
"win": {
"x86": "https://typora.io/windows/typora-setup-ia32.exe",
"x64": "https://typora.io/windows/typora-setup-x64.exe",
"arm64": "https://typora.io/windows/typora-setup-arm64.exe",
},
"linux": {
"x64": "https://download.typora.io/linux/Typora-linux-x64.tar.gz",
"arm64": "https://download.typora.io/linux/Typora-linux-arm64.tar.gz",
},
}

View File

View File

View File

@@ -0,0 +1,32 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@Name: typora_linux_x64_analysis
@Time: 2022/4/4 19:48
@Desc: It's all about getting better.
"""
import json
import r2pipe
def get_aes_key_and_iv(file_path):
r = r2pipe.open(file_path)
r.cmd("aaa")
regex = r.cmdj("axtj @@ str.base64")
assert len(regex) == 1
func = regex[0]["fcn_name"]
r.cmd(f"s {func}")
asm = r.cmdj("pdfj")['ops']
assert len(asm) != 0
if 'str.dip3' in json.dumps(asm):
r.cmd('s str.dip3 - 32')
data = r.cmdj('xj 48')
key = bytearray(data[0:32])
iv = bytearray(data[32:48])
else:
raise "need rewrite scripts for linux x64"
return key, iv

14
auto-analysis/patch.py Normal file
View File

@@ -0,0 +1,14 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: patch.py
@Time: 2022/4/3 18:36
@Desc: It's all about getting better.
"""
import utils
if __name__ == '__main__':
utils.win_x86_run()
utils.win_x64_run()
utils.linux_x64_run()

89
auto-analysis/utils.py Normal file
View File

@@ -0,0 +1,89 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: utils.py
@Time: 2022/4/3 18:36
@Desc: It's all about getting better.
"""
from loguru import logger as log
from config import DOWNLOAD_LINK
import subprocess
import json
import os
BASE_DIR = os.path.dirname(__file__)
def get_version(to_path):
package_file_path = os.path.join(to_path, "app/resources/package.json")
package_info = open(package_file_path, "r").read()
package_obj = json.loads(package_info)
return package_obj["version"]
def download_file(from_link, to_path):
subprocess.check_call(["wget", from_link, "-O", to_path])
def extract_file(from_path, to_path):
subprocess.check_call(["innoextract", from_path, "-d", to_path])
def patch_file(_key, _iv, to_dir):
exports_file_path = os.path.join(BASE_DIR, "../exports.tar.gz")
save_dir = os.path.join(to_dir, "build")
if not os.path.exists(save_dir):
os.makedirs(save_dir)
subprocess.check_call(["tar", "-zxvf", exports_file_path, "-C", save_dir])
patch_file_path = os.path.join(save_dir, "typora.py")
content = open(patch_file_path, "r").read()
content = content.replace("{AES_KEY}", f"b''.fromhex('{_key}')")
content = content.replace("{AES_IV}", f"b''.fromhex('{_iv}')")
open(patch_file_path, "w").write(content)
def scheduler(func, basedir, link):
download_path = os.path.join(basedir, os.path.basename(link))
log.info(f"downloading from {link}")
download_file(link, download_path)
log.info("ready extract package")
extract_file(download_path, basedir)
log.info("preparation stage completed")
main_node_path = os.path.join(basedir, "app/resources/app.asar.unpacked/main.node")
log.info("auto analysis start")
key, iv = func.get_aes_key_and_iv(main_node_path)
log.success("analysis done")
patch_file(key.hex(), iv.hex(), basedir)
log.success("patch done")
def win_x64_run():
from win.x64 import analysis
dirs = os.path.join(BASE_DIR, "win/x64")
url = DOWNLOAD_LINK["win"]["x64"]
scheduler(func=analysis, basedir=dirs, link=url)
def win_x86_run():
from win.x86 import analysis
dirs = os.path.join(BASE_DIR, "win/x86")
url = DOWNLOAD_LINK["win"]["x86"]
scheduler(func=analysis, basedir=dirs, link=url)
def linux_x64_run():
from linux.x64 import analysis
dirs = os.path.join(BASE_DIR, "linux/x64")
url = DOWNLOAD_LINK["linux"]["x64"]
scheduler(func=analysis, basedir=dirs, link=url)
if __name__ == '__main__':
win_x86_run()
win_x64_run()
linux_x64_run()

View File

View File

View File

@@ -0,0 +1,38 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@Name: typora_win_x64_analysis
@Time: 2022/4/3 18:26
@Desc: It's all about getting better.
"""
import struct
import r2pipe
def regex_key_iv(asm_obj):
asm_regex = []
for body in asm_obj:
if "=[4]" in body["esil"] and body['type'] == 'mov':
opcode, value = body["disasm"].split(", ")
if "0x" in value:
asm_regex.append({"opcode": opcode, "value": value})
return asm_regex
def get_aes_key_and_iv(file_path):
r = r2pipe.open(file_path)
r.cmd("aaa")
regex = r.cmdj("axtj @@ str.base64")
assert len(regex) == 1
func = regex[0]["fcn_name"]
r.cmd(f"s {func}")
asm = r.cmdj("pdfj")['ops']
assert len(asm) != 0
asm_regex = regex_key_iv(asm)
assert len(asm_regex) == 12
iv = struct.pack("<4L", *[int(asm_regex[i]['value'], 16) for i in range(4)])
key = struct.pack("<8L", *[int(asm_regex[i]['value'], 16) for i in range(4, 12)])
return key, iv

View File

View File

@@ -0,0 +1,45 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@Name: typora_win_x86_analysis
@Time: 2022/4/3 18:36
@Desc: It's all about getting better.
"""
import struct
import r2pipe
def regex_key_iv(asm_obj):
asm_regex = []
for body in asm_obj:
if "=[4]" in body["esil"] and body['type'] == 'mov':
opcode, value = body["disasm"].split(", ")
if "0x" in value:
asm_regex.append({"opcode": opcode, "value": value})
return asm_regex
def get_aes_key_and_iv(file_path):
r = r2pipe.open(file_path)
r.cmd("aaa")
regex = r.cmdj("axtj @@ str.base64")
assert len(regex) == 1
func = regex[0]["fcn_name"]
r.cmd(f"s {func}")
asm = r.cmdj("pdfj")['ops']
assert len(asm) != 0
asm_regex = regex_key_iv(asm)
iv = struct.pack("<4L", *[int(asm_regex[i]['value'], 16) for i in range(4)])
# find the set key func
call_regex = [i for i in asm if i['size'] == 5 and i['type'] == 'call']
r.cmd(f"s {call_regex[1]['jump']}")
asm = r.cmdj("pdfj")["ops"]
asm_regex = regex_key_iv(asm)
assert len(asm_regex) == 8
key = struct.pack("<8L", *[int(asm_regex[i]['value'], 16) for i in range(8)])
return key, iv

12
example/README.md Normal file
View File

@@ -0,0 +1,12 @@
## example
tested in version 1.0.2 only, it's means that you may need to make changes yourself if release the latest version.
v1.0.2 download:
- windows(64bit): https://download.typora.io/windows/typora-setup-x64-1.0.2.exe
- windows(32bit): https://download.typora.io/windows/typora-setup-ia32-1.0.2.exe
- windows(arm): https://download.typora.io/windows/typora-setup-arm64-1.0.2.exe
- linux(64bit|DEB): https://download.typora.io/linux/typora_1.0.2_amd64.deb
- linux(64bit|TAR): https://download.typora.io/linux/Typora-linux-x64-1.0.2.tar.gz
- linux(arm|DEB): https://download.typora.io/linux/typora_1.0.2_arm64.deb
- linux(arm|TAR): https://download.typora.io/linux/Typora-linux-arm64-1.0.2.tar.gz

1
example/patch/License.js Normal file

File diff suppressed because one or more lines are too long

BIN
exports.tar.gz Normal file

Binary file not shown.

176
masar.py
View File

@@ -1,176 +0,0 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: masar.py
@Time: 2021-11-29 22:34
@Desc: It's all about getting better.
"""
import os
import errno
import io
import struct
import shutil
import fileinput
import json
def round_up(i, m):
return (i + m - 1) & ~(m - 1)
class Asar:
def __init__(self, path, fp, header, base_offset):
self.path = path
self.fp = fp
self.header = header
self.base_offset = base_offset
@classmethod
def open(cls, path):
fp = open(path, 'rb')
data_size, header_size, header_object_size, header_string_size = struct.unpack('<4I', fp.read(16))
header_json = fp.read(header_string_size).decode('utf-8')
return cls(
path=path,
fp=fp,
header=json.loads(header_json),
base_offset=round_up(16 + header_string_size, 4)
)
@classmethod
def compress(cls, path):
offset = 0
paths = []
def _path_to_dict(path):
nonlocal offset, paths
result = {'files': {}}
for f in os.scandir(path):
if os.path.isdir(f.path):
result['files'][f.name] = _path_to_dict(f.path)
elif f.is_symlink():
result['files'][f.name] = {
'link': os.path.realpath(f.name)
}
# modify
elif f.name == "main.node":
size = f.stat().st_size
result['files'][f.name] = {
'size': size,
"unpacked": True
}
else:
paths.append(f.path)
size = f.stat().st_size
result['files'][f.name] = {
'size': size,
'offset': str(offset)
}
offset += size
return result
def _paths_to_bytes(paths):
_bytes = io.BytesIO()
with fileinput.FileInput(files=paths, mode="rb") as f:
for i in f:
_bytes.write(i)
return _bytes.getvalue()
header = _path_to_dict(path)
header_json = json.dumps(header, sort_keys=True, separators=(',', ':')).encode('utf-8')
header_string_size = len(header_json)
data_size = 4
aligned_size = round_up(header_string_size, data_size)
header_size = aligned_size + 8
header_object_size = aligned_size + data_size
diff = aligned_size - header_string_size
header_json = header_json + b'\0' * diff if diff else header_json
fp = io.BytesIO()
fp.write(struct.pack('<4I', data_size, header_size, header_object_size, header_string_size))
fp.write(header_json)
fp.write(_paths_to_bytes(paths))
return cls(
path=path,
fp=fp,
header=header,
base_offset=round_up(16 + header_string_size, 4))
def _copy_unpacked_file(self, source, destination):
unpacked_dir = self.path + '.unpacked'
if not os.path.isdir(unpacked_dir):
print("Couldn't copy file {}, no extracted directory".format(source))
return
src = os.path.join(unpacked_dir, source)
if not os.path.exists(src):
print("Couldn't copy file {}, doesn't exist".format(src))
return
dest = os.path.join(destination, source)
shutil.copyfile(src, dest)
def _extract_file(self, source, info, destination):
if 'offset' not in info:
self._copy_unpacked_file(source, destination)
return
self.fp.seek(self.base_offset + int(info['offset']))
r = self.fp.read(int(info['size']))
dest = os.path.join(destination, source)
with open(dest, 'wb') as f:
f.write(r)
def _extract_link(self, source, link, destination):
dest_filename = os.path.normpath(os.path.join(destination, source))
link_src_path = os.path.dirname(os.path.join(destination, link))
link_to = os.path.join(link_src_path, os.path.basename(link))
try:
os.symlink(link_to, dest_filename)
except OSError as e:
if e.errno == errno.EXIST:
os.unlink(dest_filename)
os.symlink(link_to, dest_filename)
else:
raise e
def _extract_directory(self, source, files, destination):
dest = os.path.normpath(os.path.join(destination, source))
if not os.path.exists(dest):
os.makedirs(dest)
for name, info in files.items():
item_path = os.path.join(source, name)
if 'files' in info:
self._extract_directory(item_path, info['files'], destination)
elif 'link' in info:
self._extract_link(item_path, info['link'], destination)
else:
self._extract_file(item_path, info, destination)
def extract(self, path):
if not os.path.isdir(path):
raise NotADirectoryError()
self._extract_directory('.', self.header['files'], path)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.fp.close()
def pack_asar(source, dest):
with Asar.compress(source) as a:
with open(dest, 'wb') as fp:
a.fp.seek(0)
fp.write(a.fp.read())
def extract_asar(source, dest):
with Asar.open(source) as a:
a.extract(dest)

View File

@@ -1,4 +0,0 @@
jsbeautifier==1.14.0
jsmin==3.0.0
loguru==0.5.3
pycryptodome==3.11.0

151
typora.py
View File

@@ -1,151 +0,0 @@
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: typora.py
@Time: 2021-11-29 21:24
@Desc: It's all about getting better.
"""
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.Padding import unpad
from base64 import b64decode, b64encode
from jsbeautifier import beautify
from jsmin import jsmin
from os import listdir, urandom, makedirs
from os.path import isfile, isdir, join as pjoin, split as psplit
from loguru import logger as log
from masar import extract_asar, pack_asar
import argparse
key = [0x4B029A9482B3E14E, 0xF157FEB4B4522F80, 0xE25692105308F4BE, 0x6DD58DDDA3EC0DC2]
aesKey = b""
for akey in key:
aesKey += int.to_bytes(akey, byteorder="little", length=8)
def _mkdir(_path):
try:
makedirs(_path)
except FileExistsError:
log.warning(f"May FolderExists: {_path}")
def decScript(b64: bytes, prettify: bool):
lCode = b64decode(b64)
# iv: the first 16 bytes of the file
aesIv = lCode[0:16]
# cipher text
cipherText = lCode[16:]
# AES 256 CBC
ins = AES.new(key=aesKey, iv=aesIv, mode=AES.MODE_CBC)
code = unpad(ins.decrypt(cipherText), 16, 'pkcs7')
if prettify:
code = beautify(code.decode()).encode()
return code
def extractWdec(asarPath, path, prettify):
"""
:param asarPath: asar out dir
:param path: out dir
:return: None
"""
# try to create empty dir to save extract files
path = pjoin(path, "tmp_app")
_mkdir(path)
log.info(f"extract asar file: {asarPath}")
# extract app.asar to {path}/*
extract_asar(asarPath, path)
log.success(f"extract ended.")
log.info(f"read Directory: {path}")
# construct the save directory {pathRoot}/dec_app
outPath = pjoin(psplit(path)[0], "dec_app")
# try to create empty dir to save decryption files
_mkdir(outPath)
log.info(f"set Directory: {outPath}")
# enumerate extract files
fileArr = listdir(path)
for name in fileArr:
# read files content
fpath = pjoin(path, name)
scode = open(fpath, "rb").read()
log.info(f"open file: {name}")
# if file suffix is *.js then decryption file
if isfile(fpath) and name.endswith(".js"):
scode = decScript(scode, prettify)
else:
log.debug(f"skip file: {name}")
# save content {outPath}/{name}
open(pjoin(outPath, name), "wb").write(scode)
log.success(f"decrypt and save file: {name}")
def encScript(_code: bytes, compress):
if compress:
_code = jsmin(_code.decode(), quote_chars="'\"`").encode()
aesIv = urandom(16)
cipherText = aesIv + _code
ins = AES.new(key=aesKey, iv=aesIv, mode=AES.MODE_CBC)
enc = ins.encrypt(pad(cipherText, 16, 'pkcs7'))
lCode = b64encode(enc)
return lCode
def packWenc(path, outPath, compress):
"""
:param path: out dir
:param outPath: pack path app.asar
:param compress: Bool
:return: None
"""
if not isdir(outPath):
log.error("plz input Directory for app.asar")
raise NotADirectoryError
encFilePath = pjoin(psplit(outPath)[0], "enc_app")
_mkdir(encFilePath)
outFilePath = pjoin(outPath, "app.asar")
log.info(f"set outFilePath: {outFilePath}")
fileArr = listdir(path)
for name in fileArr:
fpath = pjoin(path, name)
if isdir(fpath):
log.error("TODO: found folder")
raise IsADirectoryError
scode = open(fpath, "rb").read()
log.info(f"open file: {name}")
if isfile(fpath) and name.endswith(".js"):
scode = encScript(scode, compress)
open(pjoin(encFilePath, name), "wb").write(scode)
log.success(f"encrypt and save file: {name}")
log.info("ready to pack")
pack_asar(encFilePath, outFilePath)
log.success("pack done")
def main():
argParser = argparse.ArgumentParser(
description="[extract and decryption / pack and encryption] app.asar file from [Typora].",
epilog="If you have any questions, please contact [ MasonShi@88.com ]")
argParser.add_argument("asarPath", type=str, help="app.asar file path/dir [input/ouput]")
argParser.add_argument("dirPath", type=str, help="as tmp and out directory.")
argParser.add_argument('-u', dest='mode', action='store_const',
const=packWenc, default=extractWdec,
help='pack & encryption (default: extract & decryption)')
argParser.add_argument('-f', dest='format', action='store_const',
const=True, default=False,
help='enabled prettify/compress (default: disabled)')
args = argParser.parse_args()
args.mode(args.asarPath, args.dirPath, args.format)
log.success("Done!")
if __name__ == '__main__':
main()