pyarmor.cli 9.2.5__2-py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyarmor/cli/__init__.py +46 -0
- pyarmor/cli/__main__.py +812 -0
- pyarmor/cli/batch.py +62 -0
- pyarmor/cli/bootstrap.py +207 -0
- pyarmor/cli/bug.py +137 -0
- pyarmor/cli/command.py +563 -0
- pyarmor/cli/config.py +306 -0
- pyarmor/cli/context.py +790 -0
- pyarmor/cli/core.data.1 +0 -0
- pyarmor/cli/core.data.2 +0 -0
- pyarmor/cli/core.data.3 +0 -0
- pyarmor/cli/default.cfg +564 -0
- pyarmor/cli/docker.py +192 -0
- pyarmor/cli/generate.py +256 -0
- pyarmor/cli/hdinfo.py +81 -0
- pyarmor/cli/merge.py +232 -0
- pyarmor/cli/mixer.py +109 -0
- pyarmor/cli/model.py +460 -0
- pyarmor/cli/plugin.py +292 -0
- pyarmor/cli/project.py +1281 -0
- pyarmor/cli/public_capsule.zip +0 -0
- pyarmor/cli/register.py +1093 -0
- pyarmor/cli/repack.py +793 -0
- pyarmor/cli/resource.py +265 -0
- pyarmor/cli/shell.py +481 -0
- pyarmor_cli-9.2.5.dist-info/METADATA +74 -0
- pyarmor_cli-9.2.5.dist-info/RECORD +30 -0
- pyarmor_cli-9.2.5.dist-info/WHEEL +6 -0
- pyarmor_cli-9.2.5.dist-info/entry_points.txt +3 -0
- pyarmor_cli-9.2.5.dist-info/top_level.txt +1 -0
pyarmor/cli/merge.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
#############################################################
|
|
5
|
+
# #
|
|
6
|
+
# Copyright @ 2023 - Dashingsoft corp. #
|
|
7
|
+
# All rights reserved. #
|
|
8
|
+
# #
|
|
9
|
+
# Pyarmor #
|
|
10
|
+
# #
|
|
11
|
+
# Version: 8.2.3 - #
|
|
12
|
+
# #
|
|
13
|
+
#############################################################
|
|
14
|
+
#
|
|
15
|
+
#
|
|
16
|
+
# @File: cli/merge.py
|
|
17
|
+
#
|
|
18
|
+
# @Author: Jondy Zhao (pyarmor@163.com)
|
|
19
|
+
#
|
|
20
|
+
# @Create Date: Tue May 30 19:35:02 CST 2023
|
|
21
|
+
#
|
|
22
|
+
import argparse
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
import struct
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger('merge')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_pyscript(filename):
|
|
33
|
+
return os.path.splitext(filename)[-1].lower() in ('.py', '.pyw')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_script(filename):
|
|
37
|
+
with open(filename) as f:
|
|
38
|
+
for line in f:
|
|
39
|
+
if line.startswith('__pyarmor__('):
|
|
40
|
+
i = line.find('(')
|
|
41
|
+
args = line.strip()[i+1:-1].split(', ', 2)
|
|
42
|
+
co = compile(line, '<str>', 'exec')
|
|
43
|
+
return args, co.co_consts[0]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_header(code):
|
|
47
|
+
left_size = len(code)
|
|
48
|
+
offset = 0
|
|
49
|
+
infos = []
|
|
50
|
+
valid = False
|
|
51
|
+
|
|
52
|
+
while left_size > 0:
|
|
53
|
+
pymajor, pyminor = struct.unpack("BB", code[offset+9:offset+11])
|
|
54
|
+
size, = struct.unpack("i", code[offset+56:offset+60])
|
|
55
|
+
if not size:
|
|
56
|
+
valid = True
|
|
57
|
+
size = left_size
|
|
58
|
+
left_size -= size
|
|
59
|
+
infos.append([offset, size, (pymajor, pyminor)])
|
|
60
|
+
offset += size
|
|
61
|
+
|
|
62
|
+
if not valid:
|
|
63
|
+
raise RuntimeError('invalid header in this script')
|
|
64
|
+
|
|
65
|
+
return infos
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def merge_scripts(name, paths, dest):
|
|
69
|
+
scripts = [os.path.join(p, name) for p in paths]
|
|
70
|
+
|
|
71
|
+
refscript = scripts.pop(0)
|
|
72
|
+
result = parse_script(refscript)
|
|
73
|
+
|
|
74
|
+
if result is None:
|
|
75
|
+
logger.info('copy script, it is not obfuscated')
|
|
76
|
+
shutil.copy2(refscript, dest)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
refmark = '--xxxxxx--'
|
|
80
|
+
refitem, refcode = result
|
|
81
|
+
with open(refscript) as f:
|
|
82
|
+
refdata = f.read().replace(refitem[-1], refmark)
|
|
83
|
+
|
|
84
|
+
pieces = []
|
|
85
|
+
|
|
86
|
+
for script in reversed(scripts):
|
|
87
|
+
result = parse_script(script)
|
|
88
|
+
if not result:
|
|
89
|
+
raise RuntimeError('"%s" is not an obfuscated script' % script)
|
|
90
|
+
item, code = result
|
|
91
|
+
infos = parse_header(code)
|
|
92
|
+
off, size, pyver = infos[-1]
|
|
93
|
+
logger.debug('merge py%s.%s at %d (%d)', *pyver, off, size)
|
|
94
|
+
pieces.extend([code[:off+56], struct.pack("i", size), code[off+60:]])
|
|
95
|
+
|
|
96
|
+
for off, size, pyver in parse_header(refcode):
|
|
97
|
+
logger.debug('merge py%s.%s at %d (%d)', *pyver, off, size)
|
|
98
|
+
pieces.append(refcode[off:off+size])
|
|
99
|
+
|
|
100
|
+
logger.info('write "%s"', dest)
|
|
101
|
+
with open(dest, 'w') as f:
|
|
102
|
+
f.write(refdata.replace(refmark, repr(b''.join(pieces))))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def merge_paths(paths, rname, output):
|
|
106
|
+
refpath = os.path.normpath(paths[-1])
|
|
107
|
+
rpath = os.path.join(refpath, rname) if rname else None
|
|
108
|
+
|
|
109
|
+
n = len(refpath) + 1
|
|
110
|
+
for root, dirs, files in os.walk(refpath):
|
|
111
|
+
for x in files:
|
|
112
|
+
if rpath and root.startswith(rpath):
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
name = root[n:]
|
|
116
|
+
destpath = os.path.join(output, name)
|
|
117
|
+
if not os.path.exists(destpath):
|
|
118
|
+
os.makedirs(destpath)
|
|
119
|
+
|
|
120
|
+
dest = os.path.join(destpath, x)
|
|
121
|
+
logger.info('handle "%s"', dest)
|
|
122
|
+
if is_pyscript(x):
|
|
123
|
+
merge_scripts(os.path.join(name, x), paths, dest)
|
|
124
|
+
else:
|
|
125
|
+
shutil.copy2(os.path.join(root, x), dest)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def merge_runtimes(paths, rname, output):
|
|
129
|
+
dest = os.path.join(output, rname)
|
|
130
|
+
if os.path.exists(dest):
|
|
131
|
+
shutil.rmtree(dest)
|
|
132
|
+
os.makedirs(dest)
|
|
133
|
+
|
|
134
|
+
shutil.copy2(os.path.join(paths[0], rname, '__init__.py'), dest)
|
|
135
|
+
|
|
136
|
+
for p in paths:
|
|
137
|
+
logger.info('handle runtime package at "%s"', p)
|
|
138
|
+
rpath = os.path.join(p, rname)
|
|
139
|
+
if not os.path.exists(rpath):
|
|
140
|
+
raise RuntimeError('no runtime package found')
|
|
141
|
+
for x in os.scandir(rpath):
|
|
142
|
+
if x.is_dir():
|
|
143
|
+
logger.info('copy runtime files "%s" to "%s"', x.name, dest)
|
|
144
|
+
shutil.copytree(x.path, os.path.join(dest, x.name))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def scan_runtime(paths, marker=None):
|
|
148
|
+
if marker is None:
|
|
149
|
+
marker = 'from sys import version_info as py_version'
|
|
150
|
+
refpath = os.path.normpath(paths[-1])
|
|
151
|
+
logger.info('scan runtime package in the path "%s"', refpath)
|
|
152
|
+
|
|
153
|
+
n = len(refpath) + 1
|
|
154
|
+
|
|
155
|
+
for root, dirs, files in os.walk(refpath):
|
|
156
|
+
for x in files:
|
|
157
|
+
if x == '__init__.py':
|
|
158
|
+
filename = os.path.join(root, x)
|
|
159
|
+
with open(filename) as f:
|
|
160
|
+
for line in f:
|
|
161
|
+
if line.startswith('#'):
|
|
162
|
+
continue
|
|
163
|
+
if line.startswith(marker):
|
|
164
|
+
return filename[n:-12]
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
raise RuntimeError('no runtime package found')
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def excepthook(type, exc, traceback):
|
|
171
|
+
try:
|
|
172
|
+
msg = exc.args[0] % exc.args[1:]
|
|
173
|
+
except Exception:
|
|
174
|
+
msg = str(exc)
|
|
175
|
+
logging.error(msg)
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def main():
|
|
180
|
+
parser = argparse.ArgumentParser(
|
|
181
|
+
prog='pyarmor-merge',
|
|
182
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
183
|
+
epilog='merge Pyarmor 8 obfuscated scripts')
|
|
184
|
+
|
|
185
|
+
parser.add_argument('-O', '--output',
|
|
186
|
+
default='dist',
|
|
187
|
+
help='Default output path: %(default)s)')
|
|
188
|
+
parser.add_argument('-d', '--debug',
|
|
189
|
+
default=False,
|
|
190
|
+
action='store_true',
|
|
191
|
+
dest='debug',
|
|
192
|
+
help='print debug log (default: %(default)s)')
|
|
193
|
+
group = parser.add_argument_group().add_mutually_exclusive_group()
|
|
194
|
+
group.add_argument('-n', '--no-runtime', action='store_true',
|
|
195
|
+
help='Ignore runtime files')
|
|
196
|
+
group.add_argument('--runtime-name', help='Runtime package name')
|
|
197
|
+
parser.add_argument('path', nargs='+',
|
|
198
|
+
help="Paths or obfuscated scripts")
|
|
199
|
+
|
|
200
|
+
args = parser.parse_args(sys.argv[1:])
|
|
201
|
+
if args.debug:
|
|
202
|
+
logger.setLevel(logging.DEBUG)
|
|
203
|
+
else:
|
|
204
|
+
sys.excepthook = excepthook
|
|
205
|
+
|
|
206
|
+
logger.info('start to merge %s...', str(args.path)[1:-1])
|
|
207
|
+
output = args.output
|
|
208
|
+
|
|
209
|
+
runtime_name = args.runtime_name
|
|
210
|
+
|
|
211
|
+
if not args.no_runtime:
|
|
212
|
+
if not runtime_name:
|
|
213
|
+
runtime_name = scan_runtime(args.path)
|
|
214
|
+
logger.info('runtime package at "%s"', runtime_name)
|
|
215
|
+
|
|
216
|
+
logging.info('merging runtime files...')
|
|
217
|
+
merge_runtimes(args.path, runtime_name, output)
|
|
218
|
+
logging.info('merging runtime files OK')
|
|
219
|
+
|
|
220
|
+
logging.info('merging obfuscated scripts...')
|
|
221
|
+
merge_paths(args.path, runtime_name, output)
|
|
222
|
+
logging.info('merging obfuscated scripts OK')
|
|
223
|
+
|
|
224
|
+
logger.info('merge all the scripts to "%s" successfully', output)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if __name__ == '__main__':
|
|
228
|
+
logging.basicConfig(
|
|
229
|
+
level=logging.INFO,
|
|
230
|
+
format='%(levelname)-8s %(message)s',
|
|
231
|
+
)
|
|
232
|
+
main()
|
pyarmor/cli/mixer.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
#############################################################
|
|
5
|
+
# #
|
|
6
|
+
# Copyright @ 2023 - Dashingsoft corp. #
|
|
7
|
+
# All rights reserved. #
|
|
8
|
+
# #
|
|
9
|
+
# Pyarmor #
|
|
10
|
+
# #
|
|
11
|
+
# Version: 8.0.1 - #
|
|
12
|
+
# #
|
|
13
|
+
#############################################################
|
|
14
|
+
#
|
|
15
|
+
#
|
|
16
|
+
# @File: cli/mixer.py
|
|
17
|
+
#
|
|
18
|
+
# @Author: Jondy Zhao (pyarmor@163.com)
|
|
19
|
+
#
|
|
20
|
+
# @Create Date: 2022-12-06
|
|
21
|
+
#
|
|
22
|
+
import ast
|
|
23
|
+
|
|
24
|
+
from random import randint
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StrNodeTransformer(ast.NodeTransformer):
|
|
28
|
+
|
|
29
|
+
def _reform_str(self, s):
|
|
30
|
+
encoding = getattr(self, 'encoding')
|
|
31
|
+
value = bytearray(s.encode(encoding) if encoding else s.encode())
|
|
32
|
+
key = [randint(0, 255)] * len(value)
|
|
33
|
+
data = [x ^ y for x, y in zip(value, key)]
|
|
34
|
+
expr = 'bytearray([%s]).decode(%s)' % (
|
|
35
|
+
','.join(['%s ^ %s' % k for k in zip(data, key)]),
|
|
36
|
+
'' if encoding is None else repr(encoding))
|
|
37
|
+
return ast.parse(expr).body[0].value
|
|
38
|
+
|
|
39
|
+
def _reform_value(self, value):
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
return self._reform_str(value)
|
|
42
|
+
|
|
43
|
+
elif isinstance(value, dict):
|
|
44
|
+
return ast.Dict(**{
|
|
45
|
+
'keys': [ast.Constant(value=x) for x in value.keys()],
|
|
46
|
+
'values': [self._reform_str(x) if isinstance(x, str)
|
|
47
|
+
else self._reform_value(x) for x in value.values()]
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
elif isinstance(value, (list, tuple, set)):
|
|
51
|
+
elts = [self._reform_str(x) if isinstance(x, str)
|
|
52
|
+
else self._reform_value(x) for x in value]
|
|
53
|
+
if isinstance(value, set):
|
|
54
|
+
return ast.Set(elts=elts)
|
|
55
|
+
else:
|
|
56
|
+
cls = ast.List if isinstance(value, list) else ast.Tuple
|
|
57
|
+
return cls(elts=elts, ctx=ast.Load())
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
return ast.Constant(value=value)
|
|
61
|
+
|
|
62
|
+
def reform_node(self, node):
|
|
63
|
+
value = node.s if isinstance(node, ast.Str) else node.value
|
|
64
|
+
if not isinstance(value, (list, tuple, set, dict, str)):
|
|
65
|
+
return node
|
|
66
|
+
|
|
67
|
+
obfnode = self._reform_value(value)
|
|
68
|
+
ast.copy_location(obfnode, node)
|
|
69
|
+
ast.fix_missing_locations(obfnode)
|
|
70
|
+
return obfnode
|
|
71
|
+
|
|
72
|
+
def filter_node(self, node):
|
|
73
|
+
return isinstance(node, (ast.Str, ast.Constant))
|
|
74
|
+
|
|
75
|
+
def _is_string_value(self, value):
|
|
76
|
+
return isinstance(value, ast.Str) or (
|
|
77
|
+
isinstance(value, ast.Constant) and isinstance(value.value, str))
|
|
78
|
+
|
|
79
|
+
def ignore_docstring(self, node):
|
|
80
|
+
return 1 if (
|
|
81
|
+
isinstance(node, ast.Module) and len(node.body) > 1 and
|
|
82
|
+
isinstance(node.body[1], ast.ImportFrom) and
|
|
83
|
+
node.body[1].module == '__future__' and
|
|
84
|
+
ast.get_docstring(node) is not None) else 0
|
|
85
|
+
|
|
86
|
+
def visit(self, node):
|
|
87
|
+
for field, value in ast.iter_fields(node):
|
|
88
|
+
if isinstance(value, list):
|
|
89
|
+
start = self.ignore_docstring(node) if field == 'body' else 0
|
|
90
|
+
for i in range(start, len(value)):
|
|
91
|
+
if self.filter_node(value[i]):
|
|
92
|
+
value[i] = self.reform_node(value[i])
|
|
93
|
+
elif isinstance(value[i], ast.AST):
|
|
94
|
+
self.visit(value[i])
|
|
95
|
+
elif self.filter_node(value):
|
|
96
|
+
setattr(node, field, self.reform_node(value))
|
|
97
|
+
elif isinstance(value, ast.AST):
|
|
98
|
+
self.visit(value)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class StrProtector(object):
|
|
102
|
+
|
|
103
|
+
def __init__(self, ctx):
|
|
104
|
+
self.ctx = ctx
|
|
105
|
+
|
|
106
|
+
def process(self, res):
|
|
107
|
+
snt = StrNodeTransformer()
|
|
108
|
+
snt.encoding = self.ctx.encoding
|
|
109
|
+
snt.visit(res.mtree)
|