From 1d5b35a9d61e22469665408242bf56a0fdf150ad Mon Sep 17 00:00:00 2001 From: Lil-Ran Date: Fri, 7 Mar 2025 00:10:45 +0800 Subject: [PATCH] fix: py3.12 py3.13 limited support --- ASTree.cpp | 122 ++++++++++++++++++++++++++++++++++++++++-------- CMakeLists.txt | 4 ++ README.md | 2 +- helpers/shot.py | 4 +- 4 files changed, 110 insertions(+), 22 deletions(-) diff --git a/ASTree.cpp b/ASTree.cpp index 9ac5ac7..ef3d486 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -552,6 +552,9 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } break; case Pyc::CALL_A: + // BEGIN ONESHOT TEMPORARY PATCH + case Pyc::CALL_KW_A: + // END ONESHOT PATCH case Pyc::CALL_FUNCTION_A: case Pyc::INSTRUMENTED_CALL_A: { @@ -583,6 +586,17 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) stack.pop(); PycRef loadbuild = stack.top(); stack.pop(); + // BEGIN ONESHOT TEMPORARY PATCH + if (loadbuild == nullptr) + { + loadbuild = stack.top(); + stack.pop(); + } + if (stack.top() == nullptr) + { + stack.pop(); + } + // END ONESHOT PATCH int loadbuild_type = loadbuild.type(); if (loadbuild_type == ASTNode::NODE_LOADBUILDCLASS) { PycRef call = new ASTCall(function, pparamList, kwparamList); @@ -596,13 +610,19 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) stack_hist.pop(); } - /* - KW_NAMES(i) - Stores a reference to co_consts[consti] into an internal variable for use by CALL. - co_consts[consti] must be a tuple of strings. - New in version 3.11. - */ - if (mod->verCompare(3, 11) >= 0) { + // BEGIN ONESHOT TEMPORARY PATCH + if (mod->verCompare(3, 13) >= 0 && opcode == Pyc::CALL_KW_A) { + PycRef kw_names = stack.top().cast()->object().cast(); + stack.pop(); + kwparams = kw_names->values().size(); + pparams = operand - kwparams; + for (auto it = kw_names->values().rbegin(); it != kw_names->values().rend(); it++) { + kwparamList.push_front(std::make_pair(new ASTObject(*it), stack.top())); + stack.pop(); + } + } + else if (mod->verCompare(3, 11) >= 0) { + // END ONESHOT PATCH PycRef object_or_map = stack.top(); if (object_or_map.type() == ASTNode::NODE_KW_NAMES_MAP) { stack.pop(); @@ -642,12 +662,22 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) pparamList.push_front(param); } } + // BEGIN ONESHOT TEMPORARY PATCH + // For Python 3.13 and later + if (mod->verCompare(3, 13) >= 0) { + PycRef self_or_null = stack.top(); + stack.pop(); + if (self_or_null != nullptr) { + pparamList.push_front(self_or_null); + } + } PycRef func = stack.top(); stack.pop(); if ((opcode == Pyc::CALL_A || opcode == Pyc::INSTRUMENTED_CALL_A) && - stack.top() == nullptr) { + mod->verCompare(3, 13) < 0 && stack.top() == nullptr) { stack.pop(); } + // END ONESHOT PATCH stack.push(new ASTCall(func, pparamList, kwparamList)); @@ -763,14 +793,23 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } PycRef var = stack.top(); stack.pop(); + + ASTCall::pparam_t pparamList; + if (mod->verCompare(3, 13) >= 0) + { + PycRef param = stack.top(); + stack.pop(); + if (param != nullptr) + pparamList.push_front(param); + } PycRef func = stack.top(); stack.pop(); - if (stack.top() == nullptr) + if (mod->verCompare(3, 13) < 0 && stack.top() == nullptr) { stack.pop(); } - PycRef call = new ASTCall(func, ASTCall::pparam_t(), ASTCall::kwparam_t()); + PycRef call = new ASTCall(func, pparamList, ASTCall::kwparam_t()); if (operand & 0x01) call.cast()->setKW(kw); call.cast()->setVar(var); @@ -1741,6 +1780,9 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) break; case Pyc::MAKE_CLOSURE_A: case Pyc::MAKE_FUNCTION_A: + // BEGIN ONESHOT TEMPORARY PATCH + case Pyc::MAKE_FUNCTION: + // END ONESHOT PATCH { PycRef fun_code = stack.top(); stack.pop(); @@ -1754,19 +1796,49 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } ASTFunction::defarg_t defArgs, kwDefArgs; - const int defCount = operand & 0xFF; - const int kwDefCount = (operand >> 8) & 0xFF; - for (int i = 0; i < defCount; ++i) { - defArgs.push_front(stack.top()); - stack.pop(); + // BEGIN ONESHOT TEMPORARY PATCH + if (mod->verCompare(3, 6) < 0) + { + const int defCount = operand & 0xFF; + const int kwDefCount = (operand >> 8) & 0xFF; + for (int i = 0; i < defCount; ++i) { + defArgs.push_front(stack.top()); + stack.pop(); + } + for (int i = 0; i < kwDefCount; ++i) { + kwDefArgs.push_front(stack.top()); + stack.pop(); + } + if ((operand >> 16) & 0x7FFF) { + // a tuple listing the parameter names for the annotations (only if there are any annotation objects) + stack.pop(); + } } - for (int i = 0; i < kwDefCount; ++i) { - kwDefArgs.push_front(stack.top()); - stack.pop(); + else if (mod->verCompare(3, 13) < 0) + { + if (operand & 0x08) + stack.pop(); + if (operand & 0x04) + stack.pop(); + if (operand & 0x02) + stack.pop(); + if (operand & 0x01) + stack.pop(); } + // END ONESHOT PATCH stack.push(new ASTFunction(fun_code, defArgs, kwDefArgs)); } break; + // BEGIN ONESHOT TEMPORARY PATCH + case Pyc::SET_FUNCTION_ATTRIBUTE_A: + { + PycRef func = stack.top(); + stack.pop(); + stack.pop(); + stack.push(func); + } + break; + // END ONESHOT PATCH case Pyc::NOP: break; case Pyc::POP_BLOCK: @@ -2676,7 +2748,7 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } break; // BEGIN ONESHOT TEMPORARY PATCH - // These opcodes are not implemented + // THESE OPCODES ARE NOT IMPLEMENTED HERE case Pyc::COPY_A: { FastStack tmp_stack(20); @@ -2691,10 +2763,12 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } stack.push(value); } + break; case Pyc::PUSH_EXC_INFO: { stack.push(stack.top()); } + break; case Pyc::JUMP_IF_NOT_EXC_MATCH_A: { PycRef ex_type = stack.top(); @@ -2702,8 +2776,18 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) PycRef cur_ex = stack.top(); stack.pop(); } + break; case Pyc::RERAISE: case Pyc::RERAISE_A: + case Pyc::MAKE_CELL_A: + case Pyc::COPY_FREE_VARS_A: + case Pyc::TO_BOOL: + case Pyc::CALL_INTRINSIC_1_A: + break; + case Pyc::CALL_INTRINSIC_2_A: + { + stack.pop(); + } break; // END ONESHOT PATCH default: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0399714..7049d97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,10 @@ if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-error=shadow ${CMAKE_CXX_FLAGS}") endif() +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++ ${CMAKE_EXE_LINKER_FLAGS}") +endif() + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(pyarmor-1shot diff --git a/README.md b/README.md index e0fe4fe..59416b0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Generally this project aims to statically convert (without executing) armored data - which can be regarded as an encrypted variant of pyc files - back to disassembly and (experimentally) source code. Therefore we forked the awesome [Decompyle++](https://github.com/zrax/pycdc) (aka pycdc). -Currently we are trying to support Pyarmor 8.0 - latest (9.1.0), Python 3.7 - 3.13, platforms covering Windows, Linux, macOS, and Android, with obfuscating options as many as possible. (However, we only have limited tests.) +Currently we are trying to support Pyarmor 8.0 - latest (9.1.1), Python 3.7 - 3.13, platforms covering Windows, Linux, macOS, and Android, with obfuscating options as many as possible. (However, we only have limited tests.) If the data starts with `PY` followed by six digits, it is supported. Otherwise, if it starts with `PYARMOR`, it is generated by Pyarmor 7 or before, and is not supported. diff --git a/helpers/shot.py b/helpers/shot.py index 5d98d31..94263d9 100644 --- a/helpers/shot.py +++ b/helpers/shot.py @@ -59,8 +59,8 @@ def decrypt_process(runtimes: Dict[str, RuntimeInfo], sequences: List[Tuple[str, stderr=subprocess.PIPE, timeout=SUBPROCESS_TIMEOUT, ) - stdout = sp.stdout.decode().splitlines() - stderr = sp.stderr.decode().splitlines() + stdout = sp.stdout.decode('latin-1').splitlines() + stderr = sp.stderr.decode('latin-1').splitlines() for line in stdout: logger.warning(f'PYCDC: {line} ({path})') for line in stderr: