mirror of
https://github.com/Mas0nShi/typoraCracker.git
synced 2023-07-10 13:41:20 +08:00
dev commits.
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Mason Shi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,70 +0,0 @@
|
||||
# typora Cracker
|
||||
|
||||

|
||||

|
||||
|
||||
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+ (At least for now.)
|
||||
- tested fine in Windows, Ubuntu
|
||||
|
||||
## Usage
|
||||
|
||||
1. `pip install -r requirements.txt`
|
||||
2. `python typora.py --help`
|
||||
3. read and use.
|
||||
4. do something.
|
||||
5. pack and replace app.asar.
|
||||
6. enjoy it.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```shell
|
||||
> python typora.py --help
|
||||
usage: typora.py [-h] [-u] [-f] asarPath dirPath
|
||||
|
||||
[extract and decryption / pack and encryption] app.asar file from [Typora].
|
||||
|
||||
positional arguments:
|
||||
asarPath app.asar file path/dir [input/ouput]
|
||||
dirPath as tmp and out directory.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-u pack & encryption (default: extract & decryption)
|
||||
-f enabled prettify/compress (default: disabled)
|
||||
|
||||
If you have any questions, please contact [ MasonShi@88.com ]
|
||||
|
||||
> python typora.py {installRoot}/Typora/resources/app.asar workstation/outfile/
|
||||
⋯
|
||||
# (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
|
||||
> node example/keygen.js
|
||||
XXXXXX-XXXXXX-XXXXXX-XXXXXX
|
||||
> typora
|
||||
# (input info)
|
||||
email: crack@example.com
|
||||
serial: XXXXXX-XXXXXX-XXXXXX-XXXXXX
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
MIT LICENSE
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FMas0nShi%2FtyporaCracker?ref=badge_large)
|
||||
@@ -1,64 +0,0 @@
|
||||
# typora Cracker
|
||||
|
||||
一个typora的解包&解密,打包&加密工具
|
||||
|
||||
## 敬告
|
||||
|
||||
**请注意:** typoraCracker不会提供破解相关支持,包括但不限于思路、流程、成品。
|
||||
|
||||
```
|
||||
仅供学习和讨论,请不要从事任何非法行为。
|
||||
由此产生的任何问题都将由用户(您)承担。
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- 支持版本1.0.0以上(至少现在是这样)
|
||||
- 测试通过平台:Win/Ubuntu
|
||||
|
||||
## 食用方式
|
||||
|
||||
1. `pip install -r requirements.txt`
|
||||
2. `python typora.py --help`
|
||||
3. 阅读帮助文档及使用。
|
||||
4. 做你想做的事。
|
||||
5. 打包并替换原目录下的 app.asar。
|
||||
6. 享受成果。
|
||||
|
||||
|
||||
## 示例
|
||||
|
||||
```shell
|
||||
> python typora.py --help
|
||||
usage: typora.py [-h] [-u] [-f] asarPath dirPath
|
||||
|
||||
[extract and decryption / pack and encryption] app.asar file from [Typora].
|
||||
|
||||
positional arguments:
|
||||
asarPath app.asar file path/dir [input/ouput]
|
||||
dirPath as tmp and out directory.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-u pack & encryption (default: extract & decryption)
|
||||
-f enabled prettify/compress (default: disabled)
|
||||
|
||||
If you have any questions, please contact [ MasonShi@88.com ]
|
||||
|
||||
> python typora.py {installRoot}/Typora/resources/app.asar workstation/outfile/
|
||||
⋯
|
||||
# (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
|
||||
> node example/keygen.js
|
||||
XXXXXX-XXXXXX-XXXXXX-XXXXXX
|
||||
> typora
|
||||
# (input info)
|
||||
email: crack@example.com
|
||||
serial: XXXXXX-XXXXXX-XXXXXX-XXXXXX
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
MIT LICENSE
|
||||
176
exports/masar.py
176
exports/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
|
||||
@@ -1,183 +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, 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, exists, abspath
|
||||
from loguru import logger as log
|
||||
from masar import extract_asar, pack_asar
|
||||
from shutil import rmtree
|
||||
from argparse import ArgumentParser
|
||||
import struct
|
||||
import sys
|
||||
|
||||
# DEBUG
|
||||
DEBUG = False
|
||||
|
||||
log.remove()
|
||||
if DEBUG:
|
||||
log.add(sys.stderr, level="DEBUG")
|
||||
else:
|
||||
log.add(sys.stderr, level="INFO")
|
||||
|
||||
AES_KEY = {AES_KEY}
|
||||
AES_IV = {AES_IV}
|
||||
|
||||
|
||||
def _mkDir(_path):
|
||||
if not exists(_path):
|
||||
makedirs(_path)
|
||||
else:
|
||||
if _path == psplit(__file__)[0]:
|
||||
log.warning("plz try not to use the root dir.")
|
||||
else:
|
||||
log.warning(f"May FolderExists: {_path}")
|
||||
|
||||
|
||||
def decScript(b64: bytes, prettify: bool):
|
||||
lCode = b64decode(b64)
|
||||
# iv
|
||||
aesIv = AES_IV
|
||||
# cipher text
|
||||
cipherText = lCode[:]
|
||||
# AES 256 CBC
|
||||
ins = AES.new(key=AES_KEY, 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 prettify: bool
|
||||
:param asarPath: asar out dir
|
||||
:param path: out dir
|
||||
:return: None
|
||||
"""
|
||||
# try to create empty dir to save extract files
|
||||
path = pjoin(path, "typoraCrackerTemp")
|
||||
|
||||
if exists(path):
|
||||
rmtree(path)
|
||||
_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
|
||||
if exists(outPath):
|
||||
rmtree(outPath)
|
||||
_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}")
|
||||
|
||||
rmtree(path)
|
||||
log.debug("remove temp dir")
|
||||
|
||||
|
||||
def encScript(_code: bytes, compress):
|
||||
if compress:
|
||||
_code = jsmin(_code.decode(), quote_chars="'\"`").encode()
|
||||
aesIv = AES_IV
|
||||
cipherText = _code
|
||||
ins = AES.new(key=AES_KEY, 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
|
||||
"""
|
||||
# check out path
|
||||
if isfile(outPath):
|
||||
log.error("plz input Directory for app.asar")
|
||||
raise NotADirectoryError
|
||||
|
||||
_mkDir(outPath)
|
||||
|
||||
encFilePath = pjoin(psplit(outPath)[0], "typoraCrackerTemp")
|
||||
if exists(encFilePath):
|
||||
rmtree(encFilePath)
|
||||
_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")
|
||||
|
||||
rmtree(encFilePath)
|
||||
log.debug("remove temp dir")
|
||||
|
||||
|
||||
def main():
|
||||
argParser = 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