mirror of
https://github.com/Mas0nShi/typoraCracker.git
synced 2023-07-10 13:41:20 +08:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c984fd06c0 | ||
|
|
f340eef89c | ||
|
|
b8f870e55f | ||
|
|
4d635ec11f | ||
|
|
2d106c7e48 | ||
|
|
8f070902e0 | ||
|
|
f9bb673a51 | ||
|
|
c3fa3cb361 | ||
|
|
8ee3a91b18 | ||
|
|
c262491d67 | ||
|
|
2ff0a25c1f | ||
|
|
7176bb2f82 | ||
|
|
785b3bcd49 | ||
|
|
40a95424b0 | ||
|
|
cd28ff03d9 | ||
|
|
7aaba1739a | ||
|
|
7d73c08257 | ||
|
|
d2fd9f12bf | ||
|
|
142367e963 | ||
|
|
7e1916e8e9 | ||
|
|
fe77785833 | ||
|
|
53a9057a2c | ||
|
|
1df0199b91 | ||
|
|
9b298bc024 | ||
|
|
029a4a344f | ||
|
|
3e690f077d | ||
|
|
5c518c7ab8 | ||
|
|
fc578afd40 | ||
|
|
4b3cfee43f | ||
|
|
9369288989 | ||
|
|
8f467159c7 | ||
|
|
5489c3b664 | ||
|
|
1710c83f99 | ||
|
|
219e0316f5 | ||
|
|
60a7f39b16 | ||
|
|
3c9d47b625 | ||
|
|
16c225bb1f | ||
|
|
643c0f3e3c | ||
|
|
d84a6b9041 | ||
|
|
e21922b1f1 | ||
|
|
18e2562146 | ||
|
|
d870e234c3 | ||
|
|
76c9a5b398 | ||
|
|
25cb04e00f | ||
|
|
1c879400fd | ||
|
|
d5f9fa4026 | ||
|
|
11589aff49 | ||
|
|
2a7fd5622a | ||
|
|
df83d9cae4 | ||
|
|
d90f36ba80 | ||
|
|
6c5039c632 | ||
|
|
f44e9355fb | ||
|
|
768f9e870a | ||
|
|
4d15e2a665 | ||
|
|
6a052ea508 | ||
|
|
214f7b5c79 | ||
|
|
7c83710ede | ||
|
|
dac502ef7f | ||
|
|
6874fc54bf | ||
|
|
f74141b4fa | ||
|
|
71c41fcaec | ||
|
|
1d28997146 | ||
|
|
ed3fb49ad0 | ||
|
|
f3310f448a | ||
|
|
1927e06b26 | ||
|
|
d067e4e296 | ||
|
|
0147f7c384 | ||
|
|
1c7a0daf60 | ||
|
|
a45d1b01a8 | ||
|
|
957e412291 | ||
|
|
d5c1bc55c2 | ||
|
|
6152b16fca | ||
|
|
f8ef3769c7 | ||
|
|
b43a44045f | ||
|
|
9787c2dd50 | ||
|
|
9589526703 | ||
|
|
263c976b1a | ||
|
|
9fd4bad0ab | ||
|
|
009b2ebff3 | ||
|
|
583ae2efe7 | ||
|
|
f586be4b1e | ||
|
|
c60fa54d54 | ||
|
|
4532c94d2b | ||
|
|
41ae7e552b | ||
|
|
00f916b16f |
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
- OS: [e.g. Ubuntu]
|
||||
- Version [e.g. 1.1.2]
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -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
|
||||
|
||||
|
||||
135
.github/workflows/manual.yml
vendored
Normal file
135
.github/workflows/manual.yml
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
# This is a basic workflow that is manually triggered
|
||||
|
||||
name: Manual workflow
|
||||
|
||||
# Controls when the action will run. Workflow runs when manually triggered using the UI
|
||||
# or API.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '23 21 * * 1'
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "greet"
|
||||
check_version:
|
||||
name: check the latest version
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
RELEASE_VERSION: ${{ steps.getLatestRelease.outputs.RELEASE_VERSION }}
|
||||
LATEST_VERSION: ${{ steps.checkVersion.outputs.LATEST_VERSION }}
|
||||
steps:
|
||||
- name: Get Latest Release
|
||||
id: getLatestRelease
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const releaseResponse = await github.repos.getLatestRelease({
|
||||
owner: 'Mas0nShi',
|
||||
repo: 'typoraCracker',
|
||||
})
|
||||
const {
|
||||
data: { tag_name: ver }
|
||||
} = releaseResponse;
|
||||
core.setOutput('RELEASE_VERSION', ver);
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.8' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- name: install dependencies
|
||||
run: |
|
||||
sudo apt-get update && DEBIAN_FRONTEND=noninteractive sudo apt-get install innoextract -y
|
||||
python3 -m pip install loguru
|
||||
|
||||
- name: Check Latest Version
|
||||
id: checkVersion
|
||||
run: |
|
||||
python3 auto-analysis/check_version.py
|
||||
output="$(cat auto-analysis/win/x64/LATEST_VERSION)"
|
||||
echo "$output"
|
||||
echo "::set-output name=LATEST_VERSION::$output"
|
||||
|
||||
create_release:
|
||||
needs: check_version
|
||||
runs-on: ubuntu-20.04
|
||||
if: needs.check_version.outputs.RELEASE_VERSION != needs.check_version.outputs.LATEST_VERSION
|
||||
|
||||
steps:
|
||||
- run: echo '${{ needs.check_version.outputs.LATEST_VERSION }}'
|
||||
|
||||
- name: Create Runner Release
|
||||
uses: actions/create-release@v1
|
||||
id: createRelease
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: '${{ needs.check_version.outputs.LATEST_VERSION }}'
|
||||
release_name: '${{ needs.check_version.outputs.LATEST_VERSION }}'
|
||||
prerelease: false
|
||||
|
||||
|
||||
patch_file:
|
||||
needs: [check_version, create_release]
|
||||
runs-on: ubuntu-20.04
|
||||
if: needs.check_version.outputs.RELEASE_VERSION != needs.check_version.outputs.LATEST_VERSION
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.8' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- name: install dependencies
|
||||
run: |
|
||||
sudo apt-get update && DEBIAN_FRONTEND=noninteractive sudo apt-get install innoextract cmake -y
|
||||
python3 -m pip install r2pipe loguru
|
||||
|
||||
- name: build radare2
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/radareorg/radare2
|
||||
radare2/sys/install.sh
|
||||
|
||||
- name: patch version
|
||||
|
||||
run: |
|
||||
python3 auto-analysis/patch.py
|
||||
tar -zcvf auto-analysis/win/x64/build/typoraCracker.tar.gz auto-analysis/win/x64/build/*
|
||||
|
||||
- name: Check release version
|
||||
id: checkReleaseVersion
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
try {
|
||||
const releaseVersion = '${{ needs.check_version.outputs.LATEST_VERSION }}'
|
||||
const releaseResponse = await github.repos.getReleaseByTag({
|
||||
owner: 'Mas0nShi',
|
||||
repo: 'typoraCracker',
|
||||
tag: releaseVersion
|
||||
})
|
||||
const {
|
||||
data: { id: releaseId, html_url: htmlUrl, upload_url: uploadUrl }
|
||||
} = releaseResponse;
|
||||
core.setOutput('id', releaseId);
|
||||
core.setOutput('html_url', htmlUrl);
|
||||
core.setOutput('upload_url', uploadUrl);
|
||||
core.setOutput('version', releaseVersion);
|
||||
} catch (e) {
|
||||
core.setFailed(e.message);
|
||||
}
|
||||
|
||||
- name: Upload win x64 typora.py for typoraCracker
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: '${{ steps.checkReleaseVersion.outputs.upload_url }}'
|
||||
asset_path: '${{ github.workspace }}/auto-analysis/win/x64/build/typoraCracker.tar.gz'
|
||||
asset_name: 'typoraCracker-${{ needs.check_version.outputs.LATEST_VERSION }}-win-x64.tar.gz'
|
||||
asset_content_type: application/x-tgz
|
||||
|
||||
32
README.md
32
README.md
@@ -1,36 +1,42 @@
|
||||
**typoraCracker STOPS MAINTENANCE NOW. [why](https://github.com/Mas0nShi/typoraCracker/issues/39#issuecomment-1083117056)?**
|
||||
|
||||
|
||||
# typora Cracker
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FMas0nShi%2FtyporaCracker?ref=badge_shield)
|
||||
|
||||

|
||||

|
||||
|
||||
A patch and keygen tools for typora.
|
||||
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 ALL OS supported by typora
|
||||
- Supports Version 1.0.0+ (At least for now.)
|
||||
- tested fine in Windows, Ubuntu
|
||||
|
||||
## Usage
|
||||
|
||||
1. `pip install -r requirements.txt`
|
||||
2. `python typroa.py --help`
|
||||
2. `python typora.py --help`
|
||||
3. read and use.
|
||||
4. patch License.js.
|
||||
5. replace app.asar.
|
||||
6. run keygen.
|
||||
7. enjoy it.
|
||||
4. do something.
|
||||
5. pack and replace app.asar.
|
||||
6. 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 +54,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)
|
||||
|
||||
30
README_CN.md
30
README_CN.md
@@ -1,8 +1,14 @@
|
||||
**typoraCracker 停止维护. [为什么](https://github.com/Mas0nShi/typoraCracker/issues/39#issuecomment-1083117056)**
|
||||
|
||||
|
||||
# typora Cracker
|
||||
|
||||
一个typora的Patch和KeyGen工具
|
||||
一个typora的解包&解密,打包&加密工具
|
||||
|
||||
## 敬告
|
||||
|
||||
**请注意:** typoraCracker不会提供破解相关支持,包括但不限于思路、流程、成品。
|
||||
|
||||
```
|
||||
仅供学习和讨论,请不要从事任何非法行为。
|
||||
由此产生的任何问题都将由用户(您)承担。
|
||||
@@ -10,23 +16,23 @@
|
||||
|
||||
## Features
|
||||
|
||||
- 理论上支持Typora支持的所有操作系统
|
||||
- 支持版本1.0.0以上(至少现在是这样)
|
||||
- 测试通过平台:Win/Ubuntu
|
||||
|
||||
## 食用方式
|
||||
|
||||
1. `pip install -r requirements.txt`
|
||||
2. `python typroa.py --help`
|
||||
2. `python typora.py --help`
|
||||
3. 阅读帮助文档及使用。
|
||||
4. 修改导出的 License.js。
|
||||
5. 替换原目录下的 app.asar。
|
||||
6. 运行KeyGen程序。
|
||||
7. 正常激活。
|
||||
4. 做你想做的事。
|
||||
5. 打包并替换原目录下的 app.asar。
|
||||
6. 享受成果。
|
||||
|
||||
|
||||
## 示例
|
||||
|
||||
```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 +50,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 +64,4 @@ serial: XXXXXX-XXXXXX-XXXXXX-XXXXXX
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
MIT LICENSE
|
||||
MIT LICENSE
|
||||
|
||||
22
auto-analysis/check_version.py
Normal file
22
auto-analysis/check_version.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from utils import get_version, download_file, extract_file, log, DOWNLOAD_LINK
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def win_x64_version():
|
||||
url = DOWNLOAD_LINK["win"]["x64"]
|
||||
dir = os.path.join(BASE_DIR, "win/x64")
|
||||
|
||||
download_path = os.path.join(dir, os.path.basename(url))
|
||||
download_file(url, download_path)
|
||||
extract_file(download_path, dir)
|
||||
version = get_version(dir)
|
||||
|
||||
open(os.path.join(dir, "LATEST_VERSION"), "w").write(version)
|
||||
log.success(version)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
win_x64_version()
|
||||
|
||||
0
auto-analysis/linux/arm/LATEST_VERSION
Normal file
0
auto-analysis/linux/arm/LATEST_VERSION
Normal file
0
auto-analysis/linux/x64/LATEST_VERSION
Normal file
0
auto-analysis/linux/x64/LATEST_VERSION
Normal file
12
auto-analysis/patch.py
Normal file
12
auto-analysis/patch.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- 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_x64_run()
|
||||
|
||||
94
auto-analysis/utils.py
Normal file
94
auto-analysis/utils.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- 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
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
|
||||
# Usage:
|
||||
# innoextract
|
||||
#
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
|
||||
DOWNLOAD_LINK = {
|
||||
"win": {
|
||||
"x86": "https://typora.io/windows/typora-setup-ia32.exe",
|
||||
"x64": "https://typora.io/windows/typora-setup-x64.exe",
|
||||
"arm": "https://typora.io/windows/typora-setup-arm64.exe",
|
||||
},
|
||||
"linux": {
|
||||
"x64": "https://download.typora.io/linux/Typora-linux-x64.tar.gz",
|
||||
"arm": "https://download.typora.io/linux/Typora-linux-arm64.tar.gz",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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 win_x64_run():
|
||||
from win.x64 import analysis
|
||||
basedir = os.path.join(BASE_DIR, "win/x64")
|
||||
link = DOWNLOAD_LINK["win"]["x64"]
|
||||
|
||||
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 = analysis.get_aes_key_and_iv(main_node_path)
|
||||
log.success("analysis done")
|
||||
|
||||
patch_file(key.hex(), iv.hex(), basedir)
|
||||
log.success("patch done")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
win_x64_run()
|
||||
|
||||
# hashString = open("LATEST_VERSION", "r").read()
|
||||
# if hashString == "":
|
||||
# log.info("not history for typora version")
|
||||
# exit()
|
||||
|
||||
# basedir = os.path.dirname(__file__)
|
||||
# for h1 in DOWNLOAD_LINK.keys():
|
||||
# h1dir = os.path.join(basedir, h1)
|
||||
# for h2 in DOWNLOAD_LINK.get(h1).keys():
|
||||
# h2dir = os.path.join(h1dir, h2)
|
||||
# print(h2dir)
|
||||
0
auto-analysis/win/arm/LATEST_VERSION
Normal file
0
auto-analysis/win/arm/LATEST_VERSION
Normal file
0
auto-analysis/win/x64/LATEST_VERSION
Normal file
0
auto-analysis/win/x64/LATEST_VERSION
Normal file
37
auto-analysis/win/x64/analysis.py
Normal file
37
auto-analysis/win/x64/analysis.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- 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 get_aes_key_and_iv(file_path):
|
||||
r = r2pipe.open(file_path)
|
||||
# auto analysis
|
||||
r.cmd("aaa")
|
||||
# string "base64" x-cross reference
|
||||
regex = r.cmdj("axtj @@ str.base64")
|
||||
assert len(regex) == 1
|
||||
|
||||
func = regex[0]["fcn_name"]
|
||||
# disasm func
|
||||
r.cmd(f"s {func}")
|
||||
asm = r.cmdj("pdfj")['ops']
|
||||
assert len(asm) != 0
|
||||
|
||||
asm_regex = []
|
||||
for body in asm:
|
||||
if "=[4]" in body["esil"] and body['type'] == 'mov':
|
||||
opcode, value = body["disasm"].split(", ")
|
||||
asm_regex.append({"opcode": opcode, "value": value})
|
||||
|
||||
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)])
|
||||
# print(key, iv)
|
||||
return key, iv
|
||||
0
auto-analysis/win/x86/LATEST_VERSION
Normal file
0
auto-analysis/win/x86/LATEST_VERSION
Normal file
12
example/README.md
Normal file
12
example/README.md
Normal 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
1
example/patch/License.js
Normal file
File diff suppressed because one or more lines are too long
BIN
exports.tar.gz
Normal file
BIN
exports.tar.gz
Normal file
Binary file not shown.
176
masar.py
176
masar.py
@@ -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)
|
||||
@@ -1,4 +0,0 @@
|
||||
jsbeautifier==1.14.0
|
||||
jsmin==3.0.0
|
||||
loguru==0.5.3
|
||||
pycryptodome==3.11.0
|
||||
151
typora.py
151
typora.py
@@ -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()
|
||||
Reference in New Issue
Block a user