From 832f35413b7cbd9b0572fc32fa1d8d5f27faa5d1 Mon Sep 17 00:00:00 2001 From: Lil-Ran Date: Sun, 2 Mar 2025 23:48:24 +0800 Subject: [PATCH] feat: mix str for disasm --- bytecode.cpp | 8 +++++--- bytecode.h | 2 +- helpers/shot.py | 25 ++++++++++++++++++------- pyc_string.cpp | 26 ++++++++++++++++++++++++++ pyc_string.h | 4 ++++ pycdas.cpp | 2 +- 6 files changed, 55 insertions(+), 12 deletions(-) diff --git a/bytecode.cpp b/bytecode.cpp index 7821459..2fb1f7d 100644 --- a/bytecode.cpp +++ b/bytecode.cpp @@ -116,7 +116,7 @@ int Pyc::ByteToOpcode(int maj, int min, int opcode) } void print_const(std::ostream& pyc_output, PycRef obj, PycModule* mod, - const char* parent_f_string_quote) + const char* parent_f_string_quote, bool das_decrypt_print) { if (obj == NULL) { pyc_output << ""; @@ -131,7 +131,9 @@ void print_const(std::ostream& pyc_output, PycRef obj, PycModule* mod case PycObject::TYPE_ASCII_INTERNED: case PycObject::TYPE_SHORT_ASCII: case PycObject::TYPE_SHORT_ASCII_INTERNED: - obj.cast()->print(pyc_output, mod, false, parent_f_string_quote); + das_decrypt_print + ? obj.cast()->dasPrintAndDecrypt(pyc_output, mod, false, parent_f_string_quote) + : obj.cast()->print(pyc_output, mod, false, parent_f_string_quote); break; case PycObject::TYPE_TUPLE: case PycObject::TYPE_SMALL_TUPLE: @@ -362,7 +364,7 @@ void bc_disasm(std::ostream& pyc_output, PycRef code, PycModule* mod, try { auto constParam = code->getConst(operand); formatted_print(pyc_output, "%d: ", operand); - print_const(pyc_output, constParam, mod); + print_const(pyc_output, constParam, mod, nullptr, true); } catch (const std::out_of_range &) { formatted_print(pyc_output, "%d ", operand); } diff --git a/bytecode.h b/bytecode.h index 7e4179e..1e4a4e9 100644 --- a/bytecode.h +++ b/bytecode.h @@ -28,7 +28,7 @@ int ByteToOpcode(int maj, int min, int opcode); } void print_const(std::ostream& pyc_output, PycRef obj, PycModule* mod, - const char* parent_f_string_quote = nullptr); + const char* parent_f_string_quote = nullptr, bool das_decrypt_print = false); void bc_next(PycBuffer& source, PycModule* mod, int& opcode, int& operand, int& pos); void bc_disasm(std::ostream& pyc_output, PycRef code, PycModule* mod, int indent, unsigned flags); diff --git a/helpers/shot.py b/helpers/shot.py index 8724cf2..e3ce6a9 100644 --- a/helpers/shot.py +++ b/helpers/shot.py @@ -15,8 +15,9 @@ def general_aes_ctr_decrypt(data: bytes, key: bytes, nonce: bytes) -> bytes: return cipher.decrypt(data) -def decrypt_process(runtimes: dict[str, RuntimeInfo], sequences: list[tuple[str, bytes]], output_dir: str = None): +def decrypt_process(runtimes: dict[str, RuntimeInfo], sequences: list[tuple[str, bytes]], args): logger = logging.getLogger('shot') + output_dir: str = args.output_dir or args.directory for path, data in sequences: try: serial_number = data[2:8].decode('utf-8') @@ -28,8 +29,9 @@ def decrypt_process(runtimes: dict[str, RuntimeInfo], sequences: list[tuple[str, if not os.path.exists(dest_dir): os.makedirs(dest_dir) - with open(dest_path + '.1shot.raw', 'wb') as f: - f.write(data) + if args.export_raw_data: + with open(dest_path + '.1shot.raw', 'wb') as f: + f.write(data) cipher_text_offset = int.from_bytes(data[28:32], 'little') cipher_text_length = int.from_bytes(data[32:36], 'little') @@ -63,7 +65,7 @@ def decrypt_process(runtimes: dict[str, RuntimeInfo], sequences: list[tuple[str, for line in stderr: if line.startswith('Warning'): logger.warning(f'STDERR {line} ({path})') - else: + elif not line.startswith('Unsupported opcode:') or args.show_err_opcode: logger.error(f'STDERR {line} ({path})') except Exception as e: logger.error(f'Decrypt failed: {e} ({path})') @@ -90,6 +92,16 @@ def parse_args(): help='save output files in another directory instead of in-place, with folder structure remain unchanged', type=str, ) + parser.add_argument( + '--export-raw-data', + help='save data found in source files as-is', + action='store_true', + ) + parser.add_argument( + '--show-err-opcode', + help='show pycdc unsupported opcode error', + action='store_true', + ) return parser.parse_args() @@ -145,8 +157,7 @@ def main(): if not handled \ and specified_runtime is None \ and file_name.startswith('pyarmor_runtime') \ - and not file_name.endswith(('.lnk', '.i64', '.idb', '.id0', '.id1', - '.id2', '.nam', '.til', '.bak')): + and file_name.endswith(('.pyd', '.so', '.dylib')): try: new_runtime = RuntimeInfo(file_path) runtimes[new_runtime.serial_number] = new_runtime @@ -189,7 +200,7 @@ def main(): # TODO: is pyc or single marshalled binary? # print(runtimes, [(i[0], i[1][:16]) for i in sequences], args.output_dir or args.directory) - decrypt_process(runtimes, sequences, args.output_dir or args.directory) + decrypt_process(runtimes, sequences, args) if __name__ == '__main__': diff --git a/pyc_string.cpp b/pyc_string.cpp index 5dd7806..b7d4a83 100644 --- a/pyc_string.cpp +++ b/pyc_string.cpp @@ -2,6 +2,7 @@ #include "pyc_module.h" #include "data.h" #include +#include "plusaes.hpp" static bool check_ascii(const std::string& data) { @@ -154,3 +155,28 @@ void PycString::print(std::ostream &pyc_output, PycModule* mod, bool triple, pyc_output << (useQuotes ? '"' : '\''); } } + +void PycString::dasPrintAndDecrypt(std::ostream &stream, PycModule *mod, bool triple, const char *parent_f_string_quote) +{ + if (m_value.empty() || !(m_value[0] & 0x80) + || (m_value[0] & 0x7F) == 0 || (m_value[0] & 0x7F) > 4) + return print(stream, mod, triple, parent_f_string_quote); + + std::string result(m_value.substr(1)); + unsigned char nonce[16] = {0}; + memcpy(nonce, mod->pyarmor_mix_str_aes_nonce, 12); + nonce[15] = 2; + + plusaes::crypt_ctr( + (unsigned char *)&result[0], + result.length(), + mod->pyarmor_aes_key, + 16, + &nonce); + + PycString decrypted(TYPE_UNICODE); + decrypted.setValue(result); + decrypted.print(stream, mod, triple, parent_f_string_quote); + stream << " # "; + print(stream, mod, triple, parent_f_string_quote); +} diff --git a/pyc_string.h b/pyc_string.h index 43ae2ed..7e528bd 100644 --- a/pyc_string.h +++ b/pyc_string.h @@ -30,6 +30,10 @@ public: void print(std::ostream& stream, class PycModule* mod, bool triple = false, const char* parent_f_string_quote = nullptr); + void dasPrintAndDecrypt(std::ostream& stream, class PycModule* mod, + bool triple = false, + const char* parent_f_string_quote = nullptr); + private: std::string m_value; }; diff --git a/pycdas.cpp b/pycdas.cpp index 681e21b..b8c8333 100644 --- a/pycdas.cpp +++ b/pycdas.cpp @@ -165,7 +165,7 @@ void output_object(PycRef obj, PycModule* mod, int indent, case PycObject::TYPE_SHORT_ASCII: case PycObject::TYPE_SHORT_ASCII_INTERNED: iputs(pyc_output, indent, ""); - obj.cast()->print(pyc_output, mod); + obj.cast()->dasPrintAndDecrypt(pyc_output, mod); pyc_output << "\n"; break; case PycObject::TYPE_TUPLE: