From 6465cf03fe88f9c951eef9f80b93728100f34c76 Mon Sep 17 00:00:00 2001 From: Lil-Ran Date: Sun, 2 Mar 2025 22:42:04 +0800 Subject: [PATCH] wip: executable --- .gitignore | 5 +++ CMakeLists.txt | 20 ++------- README.md | 2 - pyarmor-1shot.cpp | 101 ++++++++++++++++++++++++++++++++++++++++++++++ pyc_code.cpp | 2 +- 5 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 pyarmor-1shot.cpp diff --git a/.gitignore b/.gitignore index 7cfe02d..66f21ec 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,8 @@ /.kdev4 __pycache__ tests-out + +.vscode/ +build/ +pyarmor-1shot +pyarmor-1shot.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 476c964..81b0539 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,22 +64,8 @@ add_library(pycxx STATIC bytes/python_3_13.cpp ) -add_executable(pycdas pycdas.cpp) -target_link_libraries(pycdas pycxx) +add_executable(pyarmor-1shot pyarmor-1shot.cpp ASTree.cpp ASTNode.cpp) +target_link_libraries(pyarmor-1shot pycxx) -install(TARGETS pycdas +install(TARGETS pyarmor-1shot RUNTIME DESTINATION bin) - -add_executable(pycdc pycdc.cpp ASTree.cpp ASTNode.cpp) -target_link_libraries(pycdc pycxx) - -install(TARGETS pycdc - RUNTIME DESTINATION bin) - -find_package(Python3 3.6 COMPONENTS Interpreter) -if(Python3_FOUND) - add_custom_target(check - COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/run_tests.py" - WORKING_DIRECTORY "$") - add_dependencies(check pycdc) -endif() diff --git a/README.md b/README.md index 10a4f1c..460d984 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Pyarmor-Static-Unpack-1shot -🚧 **Working in progress** - 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.) diff --git a/pyarmor-1shot.cpp b/pyarmor-1shot.cpp new file mode 100644 index 0000000..a2c18cc --- /dev/null +++ b/pyarmor-1shot.cpp @@ -0,0 +1,101 @@ +/** I want to use functions in pycdas.cpp directly, but not moving them to + * another file, to sync with upstream in the future easily. + */ +#define main pycdas_main +# include "pycdas.cpp" +#undef main + +#include "ASTree.h" + +int main(int argc, char* argv[]) +{ + const char* infile = nullptr; + unsigned disasm_flags = 0; + std::ofstream dc_out_file; + std::ofstream das_out_file; + + for (int arg = 1; arg < argc; ++arg) { + if (strcmp(argv[arg], "--pycode-extra") == 0) { + disasm_flags |= Pyc::DISASM_PYCODE_VERBOSE; + } else if (strcmp(argv[arg], "--show-caches") == 0) { + disasm_flags |= Pyc::DISASM_SHOW_CACHES; + } else if (strcmp(argv[arg], "--help") == 0 || strcmp(argv[arg], "-h") == 0) { + fprintf(stderr, "Usage: %s [options] input.pyc\n\n", argv[0]); + fputs("Options:\n", stderr); + fputs(" --pycode-extra Show extra fields in PyCode object dumps\n", stderr); + fputs(" --show-caches Don't suprress CACHE instructions in Python 3.11+ disassembly\n", stderr); + fputs(" --help Show this help text and then exit\n", stderr); + return 0; + } else if (argv[arg][0] == '-') { + fprintf(stderr, "Error: Unrecognized argument %s\n", argv[arg]); + return 1; + } else { + infile = argv[arg]; + } + } + + if (!infile) { + fputs("No input file specified\n", stderr); + return 1; + } + + std::string prefix_name; + const char *prefix_name_pos = strstr(infile, ".1shot.seq"); + if (prefix_name_pos == NULL) { + prefix_name = infile; + } else { + prefix_name = std::string(infile, prefix_name_pos - infile + 6); + } + + dc_out_file.open(prefix_name + ".cdc.py", std::ios_base::out); + if (dc_out_file.fail()) { + fprintf(stderr, "Error opening file '%s' for writing\n", (prefix_name + ".cdc.py").c_str()); + return 1; + } + + das_out_file.open(prefix_name + ".das", std::ios_base::out); + if (das_out_file.fail()) { + fprintf(stderr, "Error opening file '%s' for writing\n", (prefix_name + ".das").c_str()); + return 1; + } + + PycModule mod; + try { + mod.loadFromOneshotSequenceFile(infile); + } catch (std::exception &ex) { + fprintf(stderr, "Error disassembling %s: %s\n", infile, ex.what()); + return 1; + } + + if (!mod.isValid()) { + fprintf(stderr, "Could not load file %s\n", infile); + return 1; + } + + const char* dispname = strrchr(infile, PATHSEP); + dispname = (dispname == NULL) ? infile : dispname + 1; + + formatted_print(das_out_file, "%s (Python %d.%d%s)\n", dispname, + mod.majorVer(), mod.minorVer(), + (mod.majorVer() < 3 && mod.isUnicode()) ? " -U" : ""); + try { + output_object(mod.code().try_cast(), &mod, 0, disasm_flags, + das_out_file); + } catch (std::exception& ex) { + fprintf(stderr, "Error disassembling %s: %s\n", infile, ex.what()); + return 1; + } + + dc_out_file << "# Source Generated with Decompyle++\n"; + formatted_print(dc_out_file, "# File: %s (Python %d.%d%s)\n\n", dispname, + mod.majorVer(), mod.minorVer(), + (mod.majorVer() < 3 && mod.isUnicode()) ? " Unicode" : ""); + try { + decompyle(mod.code(), &mod, dc_out_file); + } catch (std::exception& ex) { + fprintf(stderr, "Error decompyling %s: %s\n", infile, ex.what()); + return 1; + } + + return 0; +} diff --git a/pyc_code.cpp b/pyc_code.cpp index 884fd4a..d3c07ce 100644 --- a/pyc_code.cpp +++ b/pyc_code.cpp @@ -140,7 +140,7 @@ void PycCode::load(PycData* stream, PycModule* mod) } if (pyarmor_co_descriptor_count > 1) { - fprintf(stderr, "Unsupport multiple Pyarmor CO descriptors (%d in total)\n", pyarmor_co_descriptor_count); + fprintf(stderr, "Do not support multiple Pyarmor CO descriptors (%d in total)\n", pyarmor_co_descriptor_count); fprintf(stderr, "Please open an issue at https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot/issues to request support and help to make this tool better.\n"); }