mirror of
https://github.com/bikini/patchwork
synced 2026-06-27 08:08:41 +00:00
Harden Abyss assets with sealed packets
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from .core import Obfuscator, obfuscate, obfuscate_file
|
||||
from .audit import analyze_source, build_manifest, build_stats, verify_manifest
|
||||
|
||||
__version__ = '0.6.0'
|
||||
__version__ = '0.7.0'
|
||||
|
||||
__all__ = [
|
||||
'Obfuscator',
|
||||
|
||||
+127
-16
@@ -89,6 +89,24 @@ class EncodedAbyssAssets:
|
||||
opcodes: dict[str, int]
|
||||
|
||||
|
||||
_JUMP_OPS = {
|
||||
"JUMP",
|
||||
"JUMP_IF_FALSE",
|
||||
"JUMP_IF_TRUE_KEEP",
|
||||
"JUMP_IF_FALSE_KEEP",
|
||||
"FOR_ITER",
|
||||
}
|
||||
|
||||
_PACKET_LAYOUTS = (
|
||||
(0, 1, 2),
|
||||
(0, 2, 1),
|
||||
(1, 0, 2),
|
||||
(1, 2, 0),
|
||||
(2, 0, 1),
|
||||
(2, 1, 0),
|
||||
)
|
||||
|
||||
|
||||
class _Label:
|
||||
def __init__(self) -> None:
|
||||
self.index: int | None = None
|
||||
@@ -237,7 +255,7 @@ class AbyssCompiler:
|
||||
return {
|
||||
"name": node.name,
|
||||
"code": self.emitter.resolve(),
|
||||
"consts": [_encode_const(value) for value in self.constants],
|
||||
"consts": list(self.constants),
|
||||
"globals": sorted(self.global_names),
|
||||
"locals": sorted(self.local_names),
|
||||
"externals": sorted(self.external_names),
|
||||
@@ -628,22 +646,78 @@ def _encode_const(value: Any) -> dict[str, Any]:
|
||||
raise UnsupportedAbyssNode(f"unsupported constant type {type(value).__name__}")
|
||||
|
||||
|
||||
def _json_bytes(value: Any) -> bytes:
|
||||
return json.dumps(value, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
||||
|
||||
|
||||
def _seal_value(value: Any, rng: random.Random, salt: int) -> list[Any]:
|
||||
raw = _json_bytes(value)
|
||||
key = gen_bytes(rng, rng.randint(9, 24))
|
||||
add = rng.randrange(256)
|
||||
step = rng.randrange(1, 256, 2)
|
||||
encoded = bytes(((byte ^ key[index % len(key)]) + add + ((index + salt) * step)) & 255 for index, byte in enumerate(raw))
|
||||
share = gen_bytes(rng, len(encoded))
|
||||
other = bytes(left ^ right for left, right in zip(encoded, share))
|
||||
return [
|
||||
base64.b85encode(share).decode("ascii"),
|
||||
base64.b85encode(other).decode("ascii"),
|
||||
base64.b85encode(key).decode("ascii"),
|
||||
add,
|
||||
step,
|
||||
]
|
||||
|
||||
|
||||
def _tag_for(opcode: int, argc: int, index: int, salt: int) -> int:
|
||||
return ((opcode * 2654435761) ^ (argc * 2246822519) ^ ((index + salt) * 3266489917)) & 0xFFFFFFFF
|
||||
|
||||
|
||||
def _encode_packet(inst: list[Any], index: int, rng: random.Random, salt: int) -> list[Any]:
|
||||
opcode = inst[0]
|
||||
args = inst[1:]
|
||||
tag = _tag_for(opcode, len(args), index, salt)
|
||||
fields = [
|
||||
_seal_value(opcode, rng, salt + index * 5 + 1),
|
||||
_seal_value(args, rng, salt + index * 5 + 2),
|
||||
_seal_value(tag, rng, salt + index * 5 + 3),
|
||||
]
|
||||
layout_id = rng.randrange(len(_PACKET_LAYOUTS))
|
||||
layout = _PACKET_LAYOUTS[layout_id]
|
||||
return [layout_id, fields[layout[0]], fields[layout[1]], fields[layout[2]]]
|
||||
|
||||
|
||||
def _rebased_instruction(inst: list[Any], base: int) -> list[Any]:
|
||||
op = inst[0]
|
||||
if op in _JUMP_OPS:
|
||||
return [op, base + inst[1], *inst[2:]]
|
||||
return list(inst)
|
||||
|
||||
|
||||
def encode_assets(assets: list[dict[str, Any]], rng: random.Random) -> EncodedAbyssAssets:
|
||||
values = rng.sample(range(1, 251), len(_OPS))
|
||||
opcodes = dict(zip(_OPS, values))
|
||||
encoded_funcs: list[dict[str, Any]] = []
|
||||
for asset in assets:
|
||||
encoded_code = [[opcodes[inst[0]], *inst[1:]] for inst in asset["code"]]
|
||||
merged_code: list[list[Any]] = []
|
||||
salt = rng.randrange(1 << 30)
|
||||
for fn_index, asset in enumerate(assets):
|
||||
entry = len(merged_code)
|
||||
rebased_code = [_rebased_instruction(inst, entry) for inst in asset["code"]]
|
||||
merged_code.extend(rebased_code)
|
||||
encoded_funcs.append(
|
||||
{
|
||||
"n": asset["name"],
|
||||
"c": asset["consts"],
|
||||
"g": asset["globals"],
|
||||
"l": asset["locals"],
|
||||
"b": encoded_code,
|
||||
"c": [
|
||||
_seal_value(_encode_const(value), rng, salt + (fn_index + 1) * 100000 + const_index)
|
||||
for const_index, value in enumerate(asset["consts"])
|
||||
],
|
||||
"e": _seal_value(entry, rng, salt + 200000 + fn_index),
|
||||
"g": _seal_value(asset["globals"], rng, salt + 300000 + fn_index),
|
||||
"l": _seal_value(asset["locals"], rng, salt + 400000 + fn_index),
|
||||
}
|
||||
)
|
||||
document = json.dumps({"v": 1, "f": encoded_funcs}, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
||||
encoded_packets = [
|
||||
_encode_packet([opcodes[inst[0]], *inst[1:]], index, rng, salt)
|
||||
for index, inst in enumerate(merged_code)
|
||||
]
|
||||
document = _json_bytes({"v": 2, "m": {"s": salt}, "f": encoded_funcs, "p": encoded_packets})
|
||||
key = gen_bytes(rng, rng.randint(24, 48))
|
||||
encrypted = bytes(byte ^ key[index % len(key)] for index, byte in enumerate(document))
|
||||
return EncodedAbyssAssets(
|
||||
@@ -657,10 +731,42 @@ def build_runtime_stmts(encoded: EncodedAbyssAssets, rng: random.Random) -> list
|
||||
blocks = _runtime_dispatch_blocks(encoded.opcodes)
|
||||
rng.shuffle(blocks)
|
||||
dispatch_blocks = "\n".join(blocks)
|
||||
layouts_src = repr(_PACKET_LAYOUTS)
|
||||
source = f'''
|
||||
{ASSETS_NAME} = ({encoded.payload!r}, {encoded.key!r})
|
||||
__pw_ab_cache__ = None
|
||||
|
||||
def __pw_ab_open__(_box, _salt):
|
||||
_base64 = __import__('base64')
|
||||
_json = __import__('json')
|
||||
_share = _base64.b85decode(_box[0].encode('ascii'))
|
||||
_other = _base64.b85decode(_box[1].encode('ascii'))
|
||||
_key = _base64.b85decode(_box[2].encode('ascii'))
|
||||
_add = _box[3]
|
||||
_step = _box[4]
|
||||
_encoded = bytes(_a ^ _b for _a, _b in zip(_share, _other))
|
||||
_raw = bytearray(len(_encoded))
|
||||
for _i, _b in enumerate(_encoded):
|
||||
_raw[_i] = ((_b - _add - ((_i + _salt) * _step)) & 255) ^ _key[_i % len(_key)]
|
||||
return _json.loads(bytes(_raw).decode('utf-8'))
|
||||
|
||||
def __pw_ab_tag__(_opcode, _argc, _index, _salt):
|
||||
return ((_opcode * 2654435761) ^ (_argc * 2246822519) ^ ((_index + _salt) * 3266489917)) & 4294967295
|
||||
|
||||
def __pw_ab_packet__(_packet, _index, _meta):
|
||||
_layouts = {layouts_src}
|
||||
_layout = _layouts[_packet[0] % len(_layouts)]
|
||||
_fields = [None, None, None]
|
||||
for _pos, _slot in enumerate(_layout):
|
||||
_fields[_slot] = _packet[_pos + 1]
|
||||
_salt = _meta['s']
|
||||
_opcode = __pw_ab_open__(_fields[0], _salt + _index * 5 + 1)
|
||||
_args = __pw_ab_open__(_fields[1], _salt + _index * 5 + 2)
|
||||
_tag = __pw_ab_open__(_fields[2], _salt + _index * 5 + 3)
|
||||
if _tag != __pw_ab_tag__(_opcode, len(_args), _index, _salt):
|
||||
raise RuntimeError('invalid abyss packet')
|
||||
return [_opcode, *_args]
|
||||
|
||||
def __pw_ab_const__(_x):
|
||||
_t = _x['t']
|
||||
if _t == 'none':
|
||||
@@ -689,8 +795,12 @@ def __pw_ab_load__():
|
||||
_raw_key = _base64.b85decode(_key.encode('ascii'))
|
||||
_raw = bytes(_b ^ _raw_key[_i % len(_raw_key)] for _i, _b in enumerate(_enc))
|
||||
_doc = _json.loads(_raw.decode('utf-8'))
|
||||
for _fn in _doc['f']:
|
||||
_fn['c'] = [__pw_ab_const__(_item) for _item in _fn['c']]
|
||||
_meta = _doc['m']
|
||||
for _fi, _fn in enumerate(_doc['f']):
|
||||
_fn['c'] = [__pw_ab_const__(__pw_ab_open__(_item, _meta['s'] + (_fi + 1) * 100000 + _ci)) for _ci, _item in enumerate(_fn['c'])]
|
||||
_fn['e'] = __pw_ab_open__(_fn['e'], _meta['s'] + 200000 + _fi)
|
||||
_fn['g'] = __pw_ab_open__(_fn['g'], _meta['s'] + 300000 + _fi)
|
||||
_fn['l'] = __pw_ab_open__(_fn['l'], _meta['s'] + 400000 + _fi)
|
||||
__pw_ab_cache__ = _doc
|
||||
return __pw_ab_cache__
|
||||
|
||||
@@ -790,7 +900,7 @@ def __pw_ab_format__(_value, _conversion, _has_spec, _stack):
|
||||
_value = ascii(_value)
|
||||
return format(_value, _spec)
|
||||
|
||||
def __pw_ab_exec__(_fn, _initial_locals, _globals):
|
||||
def __pw_ab_exec__(_doc, _fn, _initial_locals, _globals):
|
||||
_builtins = _globals.get('__builtins__', __builtins__)
|
||||
if not isinstance(_builtins, dict):
|
||||
_builtins = _builtins.__dict__
|
||||
@@ -798,18 +908,19 @@ def __pw_ab_exec__(_fn, _initial_locals, _globals):
|
||||
_declared_locals = set(_fn.get('l', ()))
|
||||
_locals = dict(_initial_locals)
|
||||
_consts = _fn['c']
|
||||
_code = _fn['b']
|
||||
_code = _doc['p']
|
||||
_meta = _doc['m']
|
||||
_stack = []
|
||||
_ip = 0
|
||||
_ip = _fn['e']
|
||||
while True:
|
||||
_inst = _code[_ip]
|
||||
_inst = __pw_ab_packet__(_code[_ip], _ip, _meta)
|
||||
_op = _inst[0]
|
||||
{dispatch_blocks}
|
||||
raise RuntimeError('invalid abyss opcode')
|
||||
|
||||
def {DISPATCH_NAME}(_fid, _env):
|
||||
_doc = __pw_ab_load__()
|
||||
return __pw_ab_exec__(_doc['f'][_fid], _env, globals())
|
||||
return __pw_ab_exec__(_doc, _doc['f'][_fid], _env, globals())
|
||||
'''
|
||||
return ast.parse(source).body
|
||||
|
||||
|
||||
Reference in New Issue
Block a user