Compare commits
23 Commits
v0.0.1
...
a05ddec0d8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a05ddec0d8 | ||
![]() |
d8c6fdf711 | ||
![]() |
577720302e | ||
![]() |
38799f5cfb | ||
![]() |
0e7be40367 | ||
![]() |
ff0c1450b4 | ||
![]() |
e8e10f1419 | ||
![]() |
a267bfb47f | ||
![]() |
8b0ea9450e | ||
![]() |
7d2039d24e | ||
![]() |
e64ea4bdec | ||
![]() |
4badfa6321 | ||
![]() |
6e0089e01c | ||
![]() |
3afcfbc6a7 | ||
![]() |
aa292c7682 | ||
![]() |
5fe61462a2 | ||
![]() |
ad5f39db56 | ||
![]() |
97ec04789d | ||
![]() |
6dae4e801f | ||
![]() |
040732920b | ||
![]() |
a93fd14672 | ||
![]() |
a4a6a24f3e | ||
![]() |
307e0b8fd7 |
2
.github/workflows/msvc-ci.yml
vendored
2
.github/workflows/msvc-ci.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
cmake --build build --config Release --target check
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pycdc-release
|
||||
path: build\Release\*.exe
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,8 +6,5 @@
|
||||
/.kdev4
|
||||
__pycache__
|
||||
tests-out
|
||||
|
||||
.vscode/
|
||||
build/
|
||||
pyarmor-1shot
|
||||
pyarmor-1shot.exe
|
||||
.vscode/
|
||||
|
529
ASTree.cpp
529
ASTree.cpp
@@ -1,11 +1,11 @@
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include "ASTree.h"
|
||||
#include "FastStack.h"
|
||||
#include "pyc_numeric.h"
|
||||
#include "bytecode.h"
|
||||
#include "plusaes.hpp"
|
||||
|
||||
// This must be a triple quote (''' or """), to handle interpolated string literals containing the opposite quote style.
|
||||
// E.g. f'''{"interpolated "123' literal"}''' -> valid.
|
||||
@@ -73,134 +73,6 @@ static void CheckIfExpr(FastStack& stack, PycRef<ASTBlock> curblock)
|
||||
stack.push(new ASTTernary(std::move(if_block), std::move(if_expr), std::move(else_expr)));
|
||||
}
|
||||
|
||||
PycRef<ASTNode> PyarmorMixStrDecrypt(const std::string &inputString, PycModule *mod)
|
||||
{
|
||||
std::string result(inputString.substr(1));
|
||||
if (inputString[0] & 0x80)
|
||||
{
|
||||
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);
|
||||
}
|
||||
switch (inputString[0] & 0x7F)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
PycRef<PycString> new_str = new PycString(PycString::TYPE_UNICODE);
|
||||
new_str->setValue(result);
|
||||
return new ASTObject(new_str.cast<PycObject>());
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
PycBuffer buf(result.data(), (int)result.length());
|
||||
return new ASTObject(LoadObject(&buf, mod));
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
PycRef<PycString> new_str = new PycString(PycString::TYPE_UNICODE);
|
||||
new_str->setValue(result);
|
||||
return new ASTImport(new ASTName(new_str), nullptr);
|
||||
}
|
||||
case 4:
|
||||
default:
|
||||
{
|
||||
fprintf(stderr, "Unknown PyarmorAssert string first byte: %d\n", inputString[0] & 0x7F);
|
||||
PycRef<PycString> new_str = new PycString(PycString::TYPE_UNICODE);
|
||||
new_str->setValue((char)(inputString[0] & 0x7F) + result);
|
||||
return new ASTObject(new_str.cast<PycObject>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CallOrPyarmorBuiltins(FastStack &stack, PycRef<ASTBlock> &curblock, PycModule *mod)
|
||||
{
|
||||
if (stack.empty() || stack.top()->type() != ASTNode::NODE_CALL)
|
||||
return;
|
||||
|
||||
PycRef<ASTCall> call = stack.top().cast<ASTCall>();
|
||||
if (call->func().type() != ASTNode::NODE_OBJECT)
|
||||
return;
|
||||
|
||||
PycRef<PycString> func_name = call->func().cast<ASTObject>()->object().try_cast<PycString>();
|
||||
if (func_name == nullptr || !func_name->startsWith("__pyarmor_"))
|
||||
return;
|
||||
|
||||
const std::string& name = func_name->strValue();
|
||||
if (name.find("__pyarmor_assert_") == std::string::npos)
|
||||
{
|
||||
PycRef<PycString> new_str = new PycString(PycString::TYPE_UNICODE);
|
||||
new_str->setValue(name + "(...)");
|
||||
stack.pop();
|
||||
stack.push(new ASTObject(new_str.cast<PycObject>()));
|
||||
// str '__pyarmor_enter_12345__(...)'
|
||||
return;
|
||||
}
|
||||
|
||||
if (call->pparams().size() != 1) // pyarmor_assert takes exactly one parameter
|
||||
return;
|
||||
|
||||
const auto& param = call->pparams().front();
|
||||
if (param.type() == ASTNode::NODE_OBJECT)
|
||||
{
|
||||
PycRef<PycString> obj = param.cast<ASTObject>()->object().try_cast<PycString>();
|
||||
if (obj == nullptr)
|
||||
return;
|
||||
PycRef<ASTNode> new_node = PyarmorMixStrDecrypt(obj->strValue(), mod);
|
||||
stack.pop();
|
||||
stack.push(new_node);
|
||||
// result of __pyarmor_assert__(b'something')
|
||||
return;
|
||||
}
|
||||
|
||||
if (param.type() == ASTNode::NODE_TUPLE)
|
||||
{
|
||||
const auto &tuple = param.cast<ASTTuple>();
|
||||
if (tuple->values().size() <= 1 || tuple->values().size() > 3)
|
||||
return;
|
||||
if (tuple->values()[1].type() != ASTNode::NODE_OBJECT)
|
||||
return;
|
||||
auto enc_str = tuple->values()[1].cast<ASTObject>()->object().try_cast<PycString>();
|
||||
if (enc_str == nullptr)
|
||||
return;
|
||||
PycRef<ASTNode> attr_name = PyarmorMixStrDecrypt(enc_str->strValue(), mod);
|
||||
if (attr_name->type() != ASTNode::NODE_OBJECT)
|
||||
return;
|
||||
auto name_str = attr_name.cast<ASTObject>()->object().try_cast<PycString>();
|
||||
if (name_str == nullptr)
|
||||
return;
|
||||
PycRef<ASTNode> attr_ref = new ASTBinary(tuple->values()[0], new ASTName(name_str), ASTBinary::BIN_ATTR);
|
||||
if (tuple->values().size() == 2)
|
||||
{
|
||||
stack.pop();
|
||||
stack.push(attr_ref);
|
||||
// __pyarmor_assert__((from, b'enc_attr')) -> from.enc_attr
|
||||
return;
|
||||
}
|
||||
else if (tuple->values().size() == 3)
|
||||
{
|
||||
stack.pop();
|
||||
curblock->append(new ASTStore(tuple->values()[2], attr_ref));
|
||||
// __pyarmor_assert__((from, b'enc_attr', value)) -> from.enc_attr = value
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (param.type() == ASTNode::NODE_NAME)
|
||||
{
|
||||
stack.pop();
|
||||
stack.push(param);
|
||||
// __pyarmor_assert__(name) -> name
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
{
|
||||
PycBuffer source(code->code()->value(), code->code()->length());
|
||||
@@ -552,9 +424,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> 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:
|
||||
{
|
||||
@@ -586,17 +455,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
stack.pop();
|
||||
PycRef<ASTNode> 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<ASTNode> call = new ASTCall(function, pparamList, kwparamList);
|
||||
@@ -610,19 +468,13 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
stack_hist.pop();
|
||||
}
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
if (mod->verCompare(3, 13) >= 0 && opcode == Pyc::CALL_KW_A) {
|
||||
PycRef<PycTuple> kw_names = stack.top().cast<ASTObject>()->object().cast<PycTuple>();
|
||||
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
|
||||
/*
|
||||
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) {
|
||||
PycRef<ASTNode> object_or_map = stack.top();
|
||||
if (object_or_map.type() == ASTNode::NODE_KW_NAMES_MAP) {
|
||||
stack.pop();
|
||||
@@ -662,28 +514,14 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
pparamList.push_front(param);
|
||||
}
|
||||
}
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
// For Python 3.13 and later
|
||||
if (mod->verCompare(3, 13) >= 0) {
|
||||
PycRef<ASTNode> self_or_null = stack.top();
|
||||
stack.pop();
|
||||
if (self_or_null != nullptr) {
|
||||
pparamList.push_front(self_or_null);
|
||||
}
|
||||
}
|
||||
PycRef<ASTNode> func = stack.top();
|
||||
stack.pop();
|
||||
if ((opcode == Pyc::CALL_A || opcode == Pyc::INSTRUMENTED_CALL_A) &&
|
||||
mod->verCompare(3, 13) < 0 && stack.top() == nullptr) {
|
||||
stack.top() == nullptr) {
|
||||
stack.pop();
|
||||
}
|
||||
// END ONESHOT PATCH
|
||||
|
||||
stack.push(new ASTCall(func, pparamList, kwparamList));
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
CallOrPyarmorBuiltins(stack, curblock, mod);
|
||||
// END ONESHOT PATCH
|
||||
}
|
||||
break;
|
||||
case Pyc::CALL_FUNCTION_VAR_A:
|
||||
@@ -711,10 +549,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
PycRef<ASTNode> call = new ASTCall(func, pparamList, kwparamList);
|
||||
call.cast<ASTCall>()->setVar(var);
|
||||
stack.push(call);
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
CallOrPyarmorBuiltins(stack, curblock, mod);
|
||||
// END ONESHOT PATCH
|
||||
}
|
||||
break;
|
||||
case Pyc::CALL_FUNCTION_KW_A:
|
||||
@@ -742,10 +576,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
PycRef<ASTNode> call = new ASTCall(func, pparamList, kwparamList);
|
||||
call.cast<ASTCall>()->setKW(kw);
|
||||
stack.push(call);
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
CallOrPyarmorBuiltins(stack, curblock, mod);
|
||||
// END ONESHOT PATCH
|
||||
}
|
||||
break;
|
||||
case Pyc::CALL_FUNCTION_VAR_KW_A:
|
||||
@@ -776,49 +606,8 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
call.cast<ASTCall>()->setKW(kw);
|
||||
call.cast<ASTCall>()->setVar(var);
|
||||
stack.push(call);
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
CallOrPyarmorBuiltins(stack, curblock, mod);
|
||||
// END ONESHOT PATCH
|
||||
}
|
||||
break;
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
case Pyc::CALL_FUNCTION_EX_A:
|
||||
{
|
||||
PycRef<ASTNode> kw;
|
||||
if (operand & 0x01)
|
||||
{
|
||||
kw = stack.top();
|
||||
stack.pop();
|
||||
}
|
||||
PycRef<ASTNode> var = stack.top();
|
||||
stack.pop();
|
||||
|
||||
ASTCall::pparam_t pparamList;
|
||||
if (mod->verCompare(3, 13) >= 0)
|
||||
{
|
||||
PycRef<ASTNode> param = stack.top();
|
||||
stack.pop();
|
||||
if (param != nullptr)
|
||||
pparamList.push_front(param);
|
||||
}
|
||||
PycRef<ASTNode> func = stack.top();
|
||||
stack.pop();
|
||||
if (mod->verCompare(3, 13) < 0 && stack.top() == nullptr)
|
||||
{
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
PycRef<ASTNode> call = new ASTCall(func, pparamList, ASTCall::kwparam_t());
|
||||
if (operand & 0x01)
|
||||
call.cast<ASTCall>()->setKW(kw);
|
||||
call.cast<ASTCall>()->setVar(var);
|
||||
stack.push(call);
|
||||
|
||||
CallOrPyarmorBuiltins(stack, curblock, mod);
|
||||
}
|
||||
break;
|
||||
// END ONESHOT PATCH
|
||||
case Pyc::CALL_METHOD_A:
|
||||
{
|
||||
ASTCall::pparam_t pparamList;
|
||||
@@ -845,10 +634,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
PycRef<ASTNode> func = stack.top();
|
||||
stack.pop();
|
||||
stack.push(new ASTCall(func, pparamList, ASTCall::kwparam_t()));
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
CallOrPyarmorBuiltins(stack, curblock, mod);
|
||||
// END ONESHOT PATCH
|
||||
}
|
||||
break;
|
||||
case Pyc::CONTINUE_LOOP_A:
|
||||
@@ -1113,7 +898,10 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
case Pyc::INSTRUMENTED_FOR_ITER_A:
|
||||
{
|
||||
PycRef<ASTNode> iter = stack.top(); // Iterable
|
||||
stack.pop();
|
||||
if (mod->verCompare(3, 12) < 0) {
|
||||
// Do not pop the iterator for py 3.12+
|
||||
stack.pop();
|
||||
}
|
||||
/* Pop it? Don't pop it? */
|
||||
|
||||
int end;
|
||||
@@ -1379,10 +1167,13 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
}
|
||||
break;
|
||||
case Pyc::JUMP_ABSOLUTE_A:
|
||||
// bpo-47120: Replaced JUMP_ABSOLUTE by the relative jump JUMP_BACKWARD.
|
||||
case Pyc::JUMP_BACKWARD_A:
|
||||
case Pyc::JUMP_BACKWARD_NO_INTERRUPT_A:
|
||||
{
|
||||
int offs = operand;
|
||||
if (mod->verCompare(3, 10) >= 0)
|
||||
offs *= sizeof(uint16_t); // // BPO-27129
|
||||
offs *= sizeof(uint16_t); // // BPO-27129
|
||||
|
||||
if (offs < pos) {
|
||||
if (curblock->blktype() == ASTBlock::BLK_FOR) {
|
||||
@@ -1441,8 +1232,12 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
break;
|
||||
}
|
||||
|
||||
stack = stack_hist.top();
|
||||
stack_hist.pop();
|
||||
if (!stack_hist.empty()) {
|
||||
stack = stack_hist.top();
|
||||
stack_hist.pop();
|
||||
} else {
|
||||
fprintf(stderr, "Warning: Stack history is empty, something wrong might have happened\n");
|
||||
}
|
||||
|
||||
PycRef<ASTBlock> prev = curblock;
|
||||
PycRef<ASTBlock> nil;
|
||||
@@ -1523,19 +1318,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
bool push = true;
|
||||
|
||||
do {
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
// This implementation is probably wrong
|
||||
// Pyarmor jumps forward on top level scope
|
||||
auto &top = blocks.top();
|
||||
if (top == defblock) {
|
||||
pos += offs;
|
||||
for (int i = 0; i < offs; i++) {
|
||||
source.getByte();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// END ONESHOT PATCH
|
||||
|
||||
blocks.pop();
|
||||
|
||||
if (!blocks.empty())
|
||||
@@ -1603,8 +1385,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
blocks.push(except);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Something TERRIBLE happened!! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fprintf(stderr, "Something TERRIBLE happened!!\n");
|
||||
}
|
||||
prev = nil;
|
||||
} else {
|
||||
@@ -1613,10 +1394,10 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
|
||||
} while (prev != nil);
|
||||
|
||||
curblock = blocks.top();
|
||||
|
||||
if (curblock->blktype() == ASTBlock::BLK_EXCEPT) {
|
||||
curblock->setEnd(pos+offs);
|
||||
if (!blocks.empty()) {
|
||||
curblock = blocks.top();
|
||||
if (curblock->blktype() == ASTBlock::BLK_EXCEPT)
|
||||
curblock->setEnd(pos+offs);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1780,9 +1561,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> 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<ASTNode> fun_code = stack.top();
|
||||
stack.pop();
|
||||
@@ -1796,49 +1574,19 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
}
|
||||
|
||||
ASTFunction::defarg_t defArgs, kwDefArgs;
|
||||
// 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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
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();
|
||||
for (int i = 0; i < kwDefCount; ++i) {
|
||||
kwDefArgs.push_front(stack.top());
|
||||
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<ASTNode> func = stack.top();
|
||||
stack.pop();
|
||||
stack.pop();
|
||||
stack.push(func);
|
||||
}
|
||||
break;
|
||||
// END ONESHOT PATCH
|
||||
case Pyc::NOP:
|
||||
break;
|
||||
case Pyc::POP_BLOCK:
|
||||
@@ -1869,8 +1617,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
stack = stack_hist.top();
|
||||
stack_hist.pop();
|
||||
} else {
|
||||
fprintf(stderr, "Warning: Stack history is empty, something wrong might have happened at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fprintf(stderr, "Warning: Stack history is empty, something wrong might have happened\n");
|
||||
}
|
||||
}
|
||||
PycRef<ASTBlock> tmp = curblock;
|
||||
@@ -1942,10 +1689,37 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
case Pyc::POP_EXCEPT:
|
||||
/* Do nothing. */
|
||||
break;
|
||||
case Pyc::END_FOR:
|
||||
{
|
||||
stack.pop();
|
||||
|
||||
if ((opcode == Pyc::END_FOR) && (mod->majorVer() == 3) && (mod->minorVer() == 12)) {
|
||||
// one additional pop for python 3.12
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
// end for loop here
|
||||
/* TODO : Ensure that FOR loop ends here.
|
||||
Due to CACHE instructions at play, the end indicated in
|
||||
the for loop by pycdas is not correct, it is off by
|
||||
some small amount. */
|
||||
if (curblock->blktype() == ASTBlock::BLK_FOR) {
|
||||
PycRef<ASTBlock> prev = blocks.top();
|
||||
blocks.pop();
|
||||
|
||||
curblock = blocks.top();
|
||||
curblock->append(prev.cast<ASTNode>());
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Wrong block type %i for END_FOR\n", curblock->blktype());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Pyc::POP_TOP:
|
||||
{
|
||||
PycRef<ASTNode> value = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (!curblock->inited()) {
|
||||
if (curblock->blktype() == ASTBlock::BLK_WITH) {
|
||||
curblock.cast<ASTWithBlock>()->setExpr(value);
|
||||
@@ -2000,7 +1774,8 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
else
|
||||
curblock->append(new ASTPrint(stack.top(), stream));
|
||||
stack.pop();
|
||||
stream->setProcessed();
|
||||
if (stream)
|
||||
stream->setProcessed();
|
||||
}
|
||||
break;
|
||||
case Pyc::PRINT_NEWLINE:
|
||||
@@ -2028,7 +1803,8 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
else
|
||||
curblock->append(new ASTPrint(nullptr, stream));
|
||||
stack.pop();
|
||||
stream->setProcessed();
|
||||
if (stream)
|
||||
stream->setProcessed();
|
||||
}
|
||||
break;
|
||||
case Pyc::RAISE_VARARGS_A:
|
||||
@@ -2051,8 +1827,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
blocks.pop();
|
||||
curblock = blocks.top();
|
||||
curblock->append(prev.cast<ASTNode>());
|
||||
|
||||
bc_next(source, mod, opcode, operand, pos);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -2154,8 +1928,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
stack.pop();
|
||||
|
||||
if (none != NULL) {
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fprintf(stderr, "Something TERRIBLE happened!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2263,8 +2036,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
if (tup.type() == ASTNode::NODE_TUPLE)
|
||||
tup.cast<ASTTuple>()->add(attr);
|
||||
else
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Something TERRIBLE happened!\n", stderr);
|
||||
|
||||
if (--unpack <= 0) {
|
||||
stack.pop();
|
||||
@@ -2299,8 +2071,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
if (tup.type() == ASTNode::NODE_TUPLE)
|
||||
tup.cast<ASTTuple>()->add(name);
|
||||
else
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Something TERRIBLE happened!\n", stderr);
|
||||
|
||||
if (--unpack <= 0) {
|
||||
stack.pop();
|
||||
@@ -2340,8 +2111,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
if (tup.type() == ASTNode::NODE_TUPLE)
|
||||
tup.cast<ASTTuple>()->add(name);
|
||||
else
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Something TERRIBLE happened!\n", stderr);
|
||||
|
||||
if (--unpack <= 0) {
|
||||
stack.pop();
|
||||
@@ -2400,8 +2170,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
if (tup.type() == ASTNode::NODE_TUPLE)
|
||||
tup.cast<ASTTuple>()->add(name);
|
||||
else
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Something TERRIBLE happened!\n", stderr);
|
||||
|
||||
if (--unpack <= 0) {
|
||||
stack.pop();
|
||||
@@ -2443,8 +2212,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
if (tup.type() == ASTNode::NODE_TUPLE)
|
||||
tup.cast<ASTTuple>()->add(name);
|
||||
else
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Something TERRIBLE happened!\n", stderr);
|
||||
|
||||
if (--unpack <= 0) {
|
||||
stack.pop();
|
||||
@@ -2565,8 +2333,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
if (tup.type() == ASTNode::NODE_TUPLE)
|
||||
tup.cast<ASTTuple>()->add(save);
|
||||
else
|
||||
fprintf(stderr, "Something TERRIBLE happened! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Something TERRIBLE happened!\n", stderr);
|
||||
|
||||
if (--unpack <= 0) {
|
||||
stack.pop();
|
||||
@@ -2721,9 +2488,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
as no-ops. */
|
||||
break;
|
||||
case Pyc::PUSH_NULL:
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
case Pyc::BEGIN_FINALLY:
|
||||
// END ONESHOT PATCH
|
||||
stack.push(nullptr);
|
||||
break;
|
||||
case Pyc::GEN_START_A:
|
||||
@@ -2747,54 +2511,81 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
stack.push(next_tup);
|
||||
}
|
||||
break;
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
// THESE OPCODES ARE NOT IMPLEMENTED HERE
|
||||
case Pyc::BINARY_SLICE:
|
||||
{
|
||||
PycRef<ASTNode> end = stack.top();
|
||||
stack.pop();
|
||||
PycRef<ASTNode> start = stack.top();
|
||||
stack.pop();
|
||||
PycRef<ASTNode> dest = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (start.type() == ASTNode::NODE_OBJECT
|
||||
&& start.cast<ASTObject>()->object() == Pyc_None) {
|
||||
start = NULL;
|
||||
}
|
||||
|
||||
if (end.type() == ASTNode::NODE_OBJECT
|
||||
&& end.cast<ASTObject>()->object() == Pyc_None) {
|
||||
end = NULL;
|
||||
}
|
||||
|
||||
PycRef<ASTNode> slice;
|
||||
if (start == NULL && end == NULL) {
|
||||
slice = new ASTSlice(ASTSlice::SLICE0);
|
||||
} else if (start == NULL) {
|
||||
slice = new ASTSlice(ASTSlice::SLICE2, start, end);
|
||||
} else if (end == NULL) {
|
||||
slice = new ASTSlice(ASTSlice::SLICE1, start, end);
|
||||
} else {
|
||||
slice = new ASTSlice(ASTSlice::SLICE3, start, end);
|
||||
}
|
||||
stack.push(new ASTSubscr(dest, slice));
|
||||
}
|
||||
break;
|
||||
case Pyc::STORE_SLICE:
|
||||
{
|
||||
PycRef<ASTNode> end = stack.top();
|
||||
stack.pop();
|
||||
PycRef<ASTNode> start = stack.top();
|
||||
stack.pop();
|
||||
PycRef<ASTNode> dest = stack.top();
|
||||
stack.pop();
|
||||
PycRef<ASTNode> values = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (start.type() == ASTNode::NODE_OBJECT
|
||||
&& start.cast<ASTObject>()->object() == Pyc_None) {
|
||||
start = NULL;
|
||||
}
|
||||
|
||||
if (end.type() == ASTNode::NODE_OBJECT
|
||||
&& end.cast<ASTObject>()->object() == Pyc_None) {
|
||||
end = NULL;
|
||||
}
|
||||
|
||||
PycRef<ASTNode> slice;
|
||||
if (start == NULL && end == NULL) {
|
||||
slice = new ASTSlice(ASTSlice::SLICE0);
|
||||
} else if (start == NULL) {
|
||||
slice = new ASTSlice(ASTSlice::SLICE2, start, end);
|
||||
} else if (end == NULL) {
|
||||
slice = new ASTSlice(ASTSlice::SLICE1, start, end);
|
||||
} else {
|
||||
slice = new ASTSlice(ASTSlice::SLICE3, start, end);
|
||||
}
|
||||
|
||||
curblock->append(new ASTStore(values, new ASTSubscr(dest, slice)));
|
||||
}
|
||||
break;
|
||||
case Pyc::COPY_A:
|
||||
{
|
||||
FastStack tmp_stack(20);
|
||||
for (int i = 0; i < operand - 1; i++) {
|
||||
tmp_stack.push(stack.top());
|
||||
stack.pop();
|
||||
}
|
||||
auto value = stack.top();
|
||||
for (int i = 0; i < operand - 1; i++) {
|
||||
stack.push(tmp_stack.top());
|
||||
tmp_stack.pop();
|
||||
}
|
||||
PycRef<ASTNode> value = stack.top(operand);
|
||||
stack.push(value);
|
||||
}
|
||||
break;
|
||||
case Pyc::PUSH_EXC_INFO:
|
||||
{
|
||||
stack.push(stack.top());
|
||||
}
|
||||
break;
|
||||
case Pyc::JUMP_IF_NOT_EXC_MATCH_A:
|
||||
{
|
||||
PycRef<ASTNode> ex_type = stack.top();
|
||||
stack.pop();
|
||||
PycRef<ASTNode> 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:
|
||||
fprintf(stderr, "Unsupported opcode: %s (%d) at %s\n",
|
||||
Pyc::OpcodeName(opcode),
|
||||
opcode,
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fprintf(stderr, "Unsupported opcode: %s (%d)\n", Pyc::OpcodeName(opcode), opcode);
|
||||
cleanBuild = false;
|
||||
return new ASTNodeList(defblock->nodes());
|
||||
}
|
||||
@@ -2806,8 +2597,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
}
|
||||
|
||||
if (stack_hist.size()) {
|
||||
fprintf(stderr, "Warning: Stack history is not empty! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Warning: Stack history is not empty!\n", stderr);
|
||||
|
||||
while (stack_hist.size()) {
|
||||
stack_hist.pop();
|
||||
@@ -2815,8 +2605,7 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
|
||||
}
|
||||
|
||||
if (blocks.size() > 1) {
|
||||
fprintf(stderr, "Warning: block stack is not empty! at %s\n",
|
||||
mod->verCompare(3, 11) >= 0 ? code->qualName()->value() : code->name()->value());
|
||||
fputs("Warning: block stack is not empty!\n", stderr);
|
||||
|
||||
while (blocks.size() > 1) {
|
||||
PycRef<ASTBlock> tmp = blocks.top();
|
||||
@@ -2991,6 +2780,8 @@ void print_formatted_value(PycRef<ASTFormattedValue> formatted_value, PycModule*
|
||||
pyc_output << "}";
|
||||
}
|
||||
|
||||
static std::unordered_set<ASTNode *> node_seen;
|
||||
|
||||
void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
|
||||
{
|
||||
if (node == NULL) {
|
||||
@@ -2999,6 +2790,12 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
|
||||
return;
|
||||
}
|
||||
|
||||
if (node_seen.find((ASTNode *)node) != node_seen.end()) {
|
||||
fputs("WARNING: Circular reference detected\n", stderr);
|
||||
return;
|
||||
}
|
||||
node_seen.insert((ASTNode *)node);
|
||||
|
||||
switch (node->type()) {
|
||||
case ASTNode::NODE_BINARY:
|
||||
case ASTNode::NODE_COMPARE:
|
||||
@@ -3220,12 +3017,6 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
|
||||
case ASTNode::NODE_BLOCK:
|
||||
{
|
||||
PycRef<ASTBlock> blk = node.cast<ASTBlock>();
|
||||
|
||||
// BEGIN ONESHOT TEMPORARY PATCH
|
||||
if (blk->blktype() == ASTBlock::BLK_MAIN)
|
||||
break;
|
||||
// END ONESHOT PATCH
|
||||
|
||||
if (blk->blktype() == ASTBlock::BLK_ELSE && blk->size() == 0)
|
||||
break;
|
||||
|
||||
@@ -3660,10 +3451,12 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
|
||||
pyc_output << "<NODE:" << node->type() << ">";
|
||||
fprintf(stderr, "Unsupported Node type: %d\n", node->type());
|
||||
cleanBuild = false;
|
||||
node_seen.erase((ASTNode *)node);
|
||||
return;
|
||||
}
|
||||
|
||||
cleanBuild = true;
|
||||
node_seen.erase((ASTNode *)node);
|
||||
}
|
||||
|
||||
bool print_docstring(PycRef<PycObject> obj, int indent, PycModule* mod,
|
||||
@@ -3680,8 +3473,16 @@ bool print_docstring(PycRef<PycObject> obj, int indent, PycModule* mod,
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::unordered_set<PycCode *> code_seen;
|
||||
|
||||
void decompyle(PycRef<PycCode> code, PycModule* mod, std::ostream& pyc_output)
|
||||
{
|
||||
if (code_seen.find((PycCode *)code) != code_seen.end()) {
|
||||
fputs("WARNING: Circular reference detected\n", stderr);
|
||||
return;
|
||||
}
|
||||
code_seen.insert((PycCode *)code);
|
||||
|
||||
PycRef<ASTNode> source = BuildFromCode(code, mod);
|
||||
|
||||
PycRef<ASTNodeList> clean = source.cast<ASTNodeList>();
|
||||
@@ -3775,4 +3576,6 @@ void decompyle(PycRef<PycCode> code, PycModule* mod, std::ostream& pyc_output)
|
||||
start_line(cur_indent, pyc_output);
|
||||
pyc_output << "# WARNING: Decompyle incomplete\n";
|
||||
}
|
||||
|
||||
code_seen.erase((PycCode *)code);
|
||||
}
|
||||
|
@@ -17,19 +17,15 @@ if (ENABLE_STACK_DEBUG)
|
||||
endif()
|
||||
|
||||
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}")
|
||||
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-error=shadow -Werror ${CMAKE_CXX_FLAGS}")
|
||||
elseif(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "/WX ${CMAKE_CXX_FLAGS}")
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_executable(pyarmor-1shot
|
||||
pyarmor-1shot.cpp
|
||||
ASTree.cpp
|
||||
ASTNode.cpp
|
||||
add_library(pycxx STATIC
|
||||
bytecode.cpp
|
||||
data.cpp
|
||||
pyc_code.cpp
|
||||
@@ -68,5 +64,22 @@ add_executable(pyarmor-1shot
|
||||
bytes/python_3_13.cpp
|
||||
)
|
||||
|
||||
install(TARGETS pyarmor-1shot
|
||||
RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/helpers)
|
||||
add_executable(pycdas pycdas.cpp)
|
||||
target_link_libraries(pycdas pycxx)
|
||||
|
||||
install(TARGETS pycdas
|
||||
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 "$<TARGET_FILE_DIR:pycdc>")
|
||||
add_dependencies(check pycdc)
|
||||
endif()
|
||||
|
24
FastStack.h
24
FastStack.h
@@ -30,14 +30,30 @@ public:
|
||||
{
|
||||
if (m_ptr > -1)
|
||||
m_stack[m_ptr--] = nullptr;
|
||||
else {
|
||||
#ifdef BLOCK_DEBUG
|
||||
fprintf(stderr, "pop from empty stack\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
PycRef<ASTNode> top() const
|
||||
PycRef<ASTNode> top(int i = 1) const
|
||||
{
|
||||
if (m_ptr > -1)
|
||||
return m_stack[m_ptr];
|
||||
else
|
||||
if (i > 0) {
|
||||
int idx = m_ptr + 1 - i;
|
||||
if ((m_ptr > -1) && (idx >= 0))
|
||||
return m_stack[idx];
|
||||
else {
|
||||
#ifdef BLOCK_DEBUG
|
||||
fprintf(stderr, "insufficient values on stack\n");
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "incorrect operand %i\n", i);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
|
52
README.md
52
README.md
@@ -1,52 +0,0 @@
|
||||
# Pyarmor-Static-Unpack-1shot
|
||||
|
||||
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.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.
|
||||
|
||||
We cannot wait to make it public. Detailed write-up will be available soon. For those who are curious, temporarily you can check out [the similar work of G DATA Advanced Analytics](https://cyber.wtf/2025/02/12/unpacking-pyarmor-v8-scripts/).
|
||||
|
||||
## Build
|
||||
|
||||
``` bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
cmake --install .
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Make sure the executable `pyarmor-1shot` (`pyarmor-1shot.exe` on Windows) exists in `helpers` directory, and run `helpers/shot.py` in Python 3 (no need to use the same version with obfuscated scripts) with the "root" directory of obfuscated scripts. It will recursively find and handle `pyarmor_runtime` and as much armored data as possible. For example:
|
||||
|
||||
``` bash
|
||||
$ ls /path/to/scripts
|
||||
__pycache__ pyarmor_runtime_000000 obf_main.py plain_src.py util.pyc packed.so folder_with_other_scripts readme.unrelated
|
||||
$ python /path/to/helpers/shot.py /path/to/scripts
|
||||
```
|
||||
|
||||
When necessary, specify a `pyarmor_runtime` executable with `-r path/to/pyarmor_runtime[.pyd|.so|.dylib]`.
|
||||
|
||||
All files generated from this tool have a `.1shot.` in file names. If you want to save them in another directory instead of in-place, use `-o another/path/`. Folder structure will remain unchanged.
|
||||
|
||||
Note:
|
||||
|
||||
- Subdirectories called `__pycache__` or `site-packages` will not be touched, and symbolic links will not be followed, to avoid repeat or forever loop and save time. If you really need them, run the script later in these directories (as "root" directory) and specify the runtime.
|
||||
- Archives, executables generated by PyInstaller and so on, must be unpacked by other tools before decrypting, or you will encounter undefined behavior.
|
||||
|
||||
## Feedback
|
||||
|
||||
Feel free to open an issue if you have any questions, suggestions, or problems. Don't forget to attach the armored data and the `pyarmor_runtime` executable if possible.
|
||||
|
||||
## Todo (PR Welcome!)
|
||||
|
||||
- [ ] Write-up
|
||||
- [ ] Multi-platform pyarmor_runtime executable
|
||||
- [ ] Accept more input forms
|
||||
- [ ] Tests for different Pyarmor and Python versions
|
||||
- [ ] Support more obfuscating options
|
||||
- [ ] Use asyncio for concurrency
|
||||
- [ ] Pyarmor 7 and before (Later or never.)
|
27
bytecode.cpp
27
bytecode.cpp
@@ -116,7 +116,7 @@ int Pyc::ByteToOpcode(int maj, int min, int opcode)
|
||||
}
|
||||
|
||||
void print_const(std::ostream& pyc_output, PycRef<PycObject> obj, PycModule* mod,
|
||||
const char* parent_f_string_quote, bool das_decrypt_print)
|
||||
const char* parent_f_string_quote)
|
||||
{
|
||||
if (obj == NULL) {
|
||||
pyc_output << "<NULL>";
|
||||
@@ -131,9 +131,7 @@ void print_const(std::ostream& pyc_output, PycRef<PycObject> obj, PycModule* mod
|
||||
case PycObject::TYPE_ASCII_INTERNED:
|
||||
case PycObject::TYPE_SHORT_ASCII:
|
||||
case PycObject::TYPE_SHORT_ASCII_INTERNED:
|
||||
das_decrypt_print
|
||||
? obj.cast<PycString>()->dasPrintAndDecrypt(pyc_output, mod, false, parent_f_string_quote)
|
||||
: obj.cast<PycString>()->print(pyc_output, mod, false, parent_f_string_quote);
|
||||
obj.cast<PycString>()->print(pyc_output, mod, false, parent_f_string_quote);
|
||||
break;
|
||||
case PycObject::TYPE_TUPLE:
|
||||
case PycObject::TYPE_SMALL_TUPLE:
|
||||
@@ -364,7 +362,7 @@ void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
|
||||
try {
|
||||
auto constParam = code->getConst(operand);
|
||||
formatted_print(pyc_output, "%d: ", operand);
|
||||
print_const(pyc_output, constParam, mod, nullptr, true);
|
||||
print_const(pyc_output, constParam, mod);
|
||||
} catch (const std::out_of_range &) {
|
||||
formatted_print(pyc_output, "%d <INVALID>", operand);
|
||||
}
|
||||
@@ -474,6 +472,10 @@ void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
|
||||
case Pyc::INSTRUMENTED_POP_JUMP_IF_FALSE_A:
|
||||
case Pyc::INSTRUMENTED_POP_JUMP_IF_TRUE_A:
|
||||
{
|
||||
/* TODO: Fix offset based on CACHE instructions.
|
||||
Offset is relative to next non-CACHE instruction
|
||||
and thus will be printed lower than actual value.
|
||||
See TODO @ END_FOR ASTree.cpp */
|
||||
int offs = operand;
|
||||
if (mod->verCompare(3, 10) >= 0)
|
||||
offs *= sizeof(uint16_t); // BPO-27129
|
||||
@@ -598,3 +600,18 @@ void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
|
||||
pyc_output << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void bc_exceptiontable(std::ostream& pyc_output, PycRef<PycCode> code,
|
||||
int indent)
|
||||
{
|
||||
for (const auto& entry : code->exceptionTableEntries()) {
|
||||
|
||||
for (int i=0; i<indent; i++)
|
||||
pyc_output << " ";
|
||||
|
||||
pyc_output << entry.start_offset << " to " << entry.end_offset
|
||||
<< " -> " << entry.target << " [" << entry.stack_depth
|
||||
<< "] " << (entry.push_lasti ? "lasti": "")
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ int ByteToOpcode(int maj, int min, int opcode);
|
||||
}
|
||||
|
||||
void print_const(std::ostream& pyc_output, PycRef<PycObject> obj, PycModule* mod,
|
||||
const char* parent_f_string_quote = nullptr, bool das_decrypt_print = false);
|
||||
const char* parent_f_string_quote = nullptr);
|
||||
void bc_next(PycBuffer& source, PycModule* mod, int& opcode, int& operand, int& pos);
|
||||
void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
|
||||
int indent, unsigned flags);
|
||||
void bc_exceptiontable(std::ostream& pyc_output, PycRef<PycCode> code,
|
||||
int indent);
|
||||
|
28
data.cpp
28
data.cpp
@@ -53,35 +53,43 @@ bool PycFile::atEof() const
|
||||
int PycFile::getByte()
|
||||
{
|
||||
int ch = fgetc(m_stream);
|
||||
if (ch == EOF)
|
||||
ungetc(ch, m_stream);
|
||||
if (ch == EOF) {
|
||||
fputs("PycFile::getByte(): Unexpected end of stream\n", stderr);
|
||||
std::exit(1);
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
int PycFile::getBuffer(int bytes, void* buffer)
|
||||
void PycFile::getBuffer(int bytes, void* buffer)
|
||||
{
|
||||
return (int)fread(buffer, 1, bytes, m_stream);
|
||||
if (fread(buffer, 1, bytes, m_stream) != (size_t)bytes) {
|
||||
fputs("PycFile::getBuffer(): Unexpected end of stream\n", stderr);
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* PycBuffer */
|
||||
int PycBuffer::getByte()
|
||||
{
|
||||
if (atEof())
|
||||
return EOF;
|
||||
if (atEof()) {
|
||||
fputs("PycBuffer::getByte(): Unexpected end of stream\n", stderr);
|
||||
std::exit(1);
|
||||
}
|
||||
int ch = (int)(*(m_buffer + m_pos));
|
||||
++m_pos;
|
||||
return ch & 0xFF; // Make sure it's just a byte!
|
||||
}
|
||||
|
||||
int PycBuffer::getBuffer(int bytes, void* buffer)
|
||||
void PycBuffer::getBuffer(int bytes, void* buffer)
|
||||
{
|
||||
if (m_pos + bytes > m_size)
|
||||
bytes = m_size - m_pos;
|
||||
if (m_pos + bytes > m_size) {
|
||||
fputs("PycBuffer::getBuffer(): Unexpected end of stream\n", stderr);
|
||||
std::exit(1);
|
||||
}
|
||||
if (bytes != 0)
|
||||
memcpy(buffer, (m_buffer + m_pos), bytes);
|
||||
m_pos += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int formatted_print(std::ostream& stream, const char* format, ...)
|
||||
|
6
data.h
6
data.h
@@ -19,7 +19,7 @@ public:
|
||||
virtual bool atEof() const = 0;
|
||||
|
||||
virtual int getByte() = 0;
|
||||
virtual int getBuffer(int bytes, void* buffer) = 0;
|
||||
virtual void getBuffer(int bytes, void* buffer) = 0;
|
||||
int get16();
|
||||
int get32();
|
||||
Pyc_INT64 get64();
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
bool atEof() const override;
|
||||
|
||||
int getByte() override;
|
||||
int getBuffer(int bytes, void* buffer) override;
|
||||
void getBuffer(int bytes, void* buffer) override;
|
||||
|
||||
private:
|
||||
FILE* m_stream;
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
bool atEof() const override { return (m_pos == m_size); }
|
||||
|
||||
int getByte() override;
|
||||
int getBuffer(int bytes, void* buffer) override;
|
||||
void getBuffer(int bytes, void* buffer) override;
|
||||
|
||||
private:
|
||||
const unsigned char* m_buffer;
|
||||
|
@@ -1,96 +0,0 @@
|
||||
import hashlib
|
||||
|
||||
|
||||
GLOBAL_CERT = bytes.fromhex('''
|
||||
30 82 01 0a 02 82 01 01 00 bf 65 30 f3 bd 67 e7
|
||||
a6 9d f8 db 18 b2 b9 c1 c0 5f fe fb e5 4b 91 df
|
||||
6f 38 da 51 cc ea c4 d3 04 bd 95 27 86 c1 13 ca
|
||||
73 15 44 4d 97 f5 10 b9 52 21 72 16 c8 b2 84 5f
|
||||
45 56 32 e7 c2 6b ad 2b d9 df 52 d6 e9 d1 2a ba
|
||||
35 e4 43 ab 54 e7 91 c5 ce d1 f1 ba a5 9f f4 ca
|
||||
db 89 04 3d f8 9f 6a 8b 8a 29 39 f8 4c 0d b8 a0
|
||||
6d 51 c4 74 24 64 fe 1a 23 97 f3 61 ea de c8 97
|
||||
dc 57 60 34 be 2c 18 50 3b d1 76 3b 49 2a 39 9a
|
||||
37 18 53 8f 1d 4c 82 b1 a0 33 43 57 19 ad 67 e7
|
||||
af 09 fb 04 54 a9 ea c0 c1 e9 32 6c 77 92 7f 9f
|
||||
7c 08 7c e8 a1 5d a4 fc 40 e6 6e 18 db bf 45 53
|
||||
4b 5c a7 9d f2 8f 7e 6c 04 b0 4d ee 99 25 9a 87
|
||||
84 6e 9e fe 3c 72 ec b0 64 dd 2e db ad 32 fa 1d
|
||||
4b 2c 1a 78 85 7c bc 2c d0 d7 83 77 5f 92 d5 db
|
||||
59 10 96 53 2e 5d c7 42 12 b8 61 cb 2c 5f 46 14
|
||||
9e 93 b0 53 21 a2 74 34 2d 02 03 01 00 01
|
||||
''')
|
||||
|
||||
|
||||
class RuntimeInfo:
|
||||
def __init__(self, file_path: str) -> None:
|
||||
self.file_path = file_path
|
||||
if file_path.endswith('.pyd'):
|
||||
self.extract_info_win64()
|
||||
else:
|
||||
# TODO: implement for other platforms
|
||||
self.extract_info_win64()
|
||||
|
||||
self.serial_number = self.part_1[12:18].decode()
|
||||
self.runtime_aes_key = self.calc_aes_key()
|
||||
|
||||
def __str__(self) -> str:
|
||||
trial = self.serial_number == '000000'
|
||||
product = ''
|
||||
for c in self.part_3[2:]:
|
||||
if 32 <= c <= 126:
|
||||
product += chr(c)
|
||||
else:
|
||||
break
|
||||
return f'''\
|
||||
========================
|
||||
Pyarmor Runtime ({'Trial' if trial else self.serial_number}) Information:
|
||||
Product: {product}
|
||||
AES key: {self.runtime_aes_key.hex()}
|
||||
Mix string AES nonce: {self.mix_str_aes_nonce().hex()}
|
||||
========================'''
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'RuntimeInfo(part_1={self.part_1}, part_2={self.part_2}, part_3={self.part_3})'
|
||||
|
||||
def extract_info_win64(self) -> None:
|
||||
'''
|
||||
Try to find useful information from `pyarmor_runtime.pyd` file,
|
||||
and store all three parts in the object.
|
||||
'''
|
||||
with open(self.file_path, 'rb') as f:
|
||||
data = f.read(16 * 1024 * 1024)
|
||||
cur = data.index(b'pyarmor-vax')
|
||||
|
||||
if data[cur+11:cur+18] == b'\x00' * 7:
|
||||
raise ValueError(f'{self.file_path} is a runtime template')
|
||||
|
||||
self.part_1 = data[cur:cur+20]
|
||||
|
||||
cur += 36
|
||||
part_2_offset = int.from_bytes(data[cur:cur+4], 'little')
|
||||
part_2_len = int.from_bytes(data[cur+4:cur+8], 'little')
|
||||
part_3_offset = int.from_bytes(data[cur+8:cur+12], 'little')
|
||||
cur += 16
|
||||
self.part_2 = data[cur+part_2_offset:cur+part_2_offset+part_2_len]
|
||||
|
||||
cur += part_3_offset
|
||||
part_3_len = int.from_bytes(data[cur+4:cur+8], 'little')
|
||||
cur += 32
|
||||
self.part_3 = data[cur:cur+part_3_len]
|
||||
|
||||
def calc_aes_key(self) -> bytes:
|
||||
return hashlib.md5(self.part_1 + self.part_2 + self.part_3 + GLOBAL_CERT).digest()
|
||||
|
||||
def mix_str_aes_nonce(self) -> bytes:
|
||||
return self.part_3[:12]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if len(sys.argv) < 2:
|
||||
print('Usage: python runtime.py path/to/pyarmor_runtime[.pyd|.so|.dylib]')
|
||||
exit(1)
|
||||
for i in sys.argv[1:]:
|
||||
runtime = RuntimeInfo(i)
|
||||
print(runtime)
|
240
helpers/shot.py
240
helpers/shot.py
@@ -1,240 +0,0 @@
|
||||
import argparse
|
||||
from Crypto.Cipher import AES
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from runtime import RuntimeInfo
|
||||
|
||||
|
||||
SUBPROCESS_TIMEOUT = 30
|
||||
|
||||
|
||||
def general_aes_ctr_decrypt(data: bytes, key: bytes, nonce: bytes) -> bytes:
|
||||
cipher = AES.new(key, AES.MODE_CTR, nonce=nonce, initial_value=2)
|
||||
return cipher.decrypt(data)
|
||||
|
||||
|
||||
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')
|
||||
runtime = runtimes[serial_number]
|
||||
logger.info(f'Decrypting: {serial_number} ({path})')
|
||||
|
||||
dest_path = os.path.join(output_dir, path) if output_dir else path
|
||||
dest_dir = os.path.dirname(dest_path)
|
||||
if not os.path.exists(dest_dir):
|
||||
os.makedirs(dest_dir)
|
||||
|
||||
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')
|
||||
nonce = data[36:40] + data[44:52]
|
||||
with open(dest_path + '.1shot.seq', 'wb') as f:
|
||||
f.write(b'\xa1' + runtime.runtime_aes_key)
|
||||
f.write(b'\xa2' + runtime.mix_str_aes_nonce())
|
||||
f.write(b'\xf0\xff')
|
||||
f.write(data[:cipher_text_offset])
|
||||
f.write(general_aes_ctr_decrypt(
|
||||
data[cipher_text_offset:cipher_text_offset+cipher_text_length], runtime.runtime_aes_key, nonce))
|
||||
f.write(data[cipher_text_offset+cipher_text_length:])
|
||||
|
||||
exe_name = 'pyarmor-1shot.exe' if os.name == 'nt' else 'pyarmor-1shot'
|
||||
exe_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), exe_name)
|
||||
# TODO: multi process
|
||||
sp = subprocess.run(
|
||||
[
|
||||
exe_path,
|
||||
dest_path + '.1shot.seq',
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=SUBPROCESS_TIMEOUT,
|
||||
)
|
||||
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:
|
||||
if line.startswith((
|
||||
'Warning: Stack history is empty',
|
||||
'Warning: Stack history is not empty',
|
||||
'Warning: block stack is not empty',
|
||||
)):
|
||||
if args.show_warn_stack or args.show_all:
|
||||
logger.warning(f'PYCDC: {line} ({path})')
|
||||
elif line.startswith('Unsupported opcode:'):
|
||||
if args.show_err_opcode or args.show_all:
|
||||
logger.error(f'PYCDC: {line} ({path})')
|
||||
elif line.startswith('Something TERRIBLE happened'):
|
||||
if args.show_all:
|
||||
logger.error(f'PYCDC: {line} ({path})')
|
||||
else:
|
||||
logger.error(f'PYCDC: {line} ({path})')
|
||||
if sp.returncode != 0:
|
||||
logger.warning(f'PYCDC returned {sp.returncode} ({path})')
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f'Decrypt failed: {e} ({path})')
|
||||
continue
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Pyarmor Static Unpack 1 Shot Entry')
|
||||
parser.add_argument(
|
||||
'directory',
|
||||
help='the "root" directory of obfuscated scripts',
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
'-r',
|
||||
'--runtime',
|
||||
help='path to pyarmor_runtime[.pyd|.so|.dylib]',
|
||||
type=str, # argparse.FileType('rb'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'-o',
|
||||
'--output-dir',
|
||||
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-all',
|
||||
help='show all pycdc errors and warnings',
|
||||
action='store_true',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--show-err-opcode',
|
||||
help='show pycdc unsupported opcode errors',
|
||||
action='store_true',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--show-warn-stack',
|
||||
help='show pycdc stack related warnings',
|
||||
action='store_true',
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(levelname)-8s %(asctime)-28s %(message)s',
|
||||
)
|
||||
logger = logging.getLogger('shot')
|
||||
|
||||
print(r'''
|
||||
____ ____
|
||||
( __ ) ( __ )
|
||||
| |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| |
|
||||
| | ____ _ ___ _ _ | |
|
||||
| | | _ \ _ _ __ _ _ __ _ _ __ ___ _ _ / / __|| |_ ___ | |_ | |
|
||||
| | | |_) | || |/ _` | '__| ' ` \ / _ \| '_| | \__ \| ' \ / _ \| __| | |
|
||||
| | | __/| || | (_| | | | || || | (_) | | | |__) | || | (_) | |_ | |
|
||||
| | |_| \_, |\__,_|_| |_||_||_|\___/|_| |_|___/|_||_|\___/ \__| | |
|
||||
| | |__/ | |
|
||||
|__|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|__|
|
||||
(____) (____)
|
||||
|
||||
For technology exchange only. Use at your own risk.
|
||||
GitHub: https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot
|
||||
''')
|
||||
|
||||
if args.runtime:
|
||||
specified_runtime = RuntimeInfo(args.runtime)
|
||||
runtimes = {specified_runtime.serial_number: specified_runtime}
|
||||
else:
|
||||
specified_runtime = None
|
||||
runtimes = {}
|
||||
|
||||
sequences: List[Tuple[str, bytes]] = []
|
||||
|
||||
if args.output_dir and not os.path.exists(args.output_dir):
|
||||
os.makedirs(args.output_dir)
|
||||
|
||||
dir_path: str
|
||||
dirs: List[str]
|
||||
files: List[str]
|
||||
for dir_path, dirs, files in os.walk(args.directory, followlinks=False):
|
||||
for d in ['__pycache__', 'site-packages']:
|
||||
if d in dirs:
|
||||
dirs.remove(d)
|
||||
for file_name in files:
|
||||
if '.1shot.' in file_name:
|
||||
continue
|
||||
handled = False
|
||||
file_path = os.path.join(dir_path, file_name)
|
||||
relative_path = os.path.relpath(file_path, args.directory)
|
||||
|
||||
# is pyarmor_runtime?
|
||||
if not handled \
|
||||
and specified_runtime is None \
|
||||
and file_name.startswith('pyarmor_runtime') \
|
||||
and file_name.endswith(('.pyd', '.so', '.dylib')):
|
||||
try:
|
||||
new_runtime = RuntimeInfo(file_path)
|
||||
runtimes[new_runtime.serial_number] = new_runtime
|
||||
logger.info(
|
||||
f'Found new runtime: {new_runtime.serial_number} ({file_path})')
|
||||
print(new_runtime)
|
||||
handled = True
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
with open(file_path, 'rb') as f:
|
||||
beacon = f.read(16 * 1024 * 1024)
|
||||
except:
|
||||
logger.error(f'Failed to read file: {relative_path}')
|
||||
continue
|
||||
|
||||
# is UTF-8 source?
|
||||
# TODO: only support natural one line now
|
||||
if not handled and b'__pyarmor__(__name__, __file__,' in beacon:
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('__pyarmor__(') and line.rstrip().endswith(')'):
|
||||
co = compile(line, '<str>', 'exec')
|
||||
bytes_raw = co.co_consts[0]
|
||||
assert type(bytes_raw) is bytes
|
||||
assert bytes_raw.startswith(b'PY')
|
||||
assert len(bytes_raw) > 64
|
||||
break
|
||||
logger.info(f'Found data in source: {relative_path}')
|
||||
# FIXME: bytes_raw can be kept from last iteration
|
||||
sequences.append((relative_path, bytes_raw))
|
||||
del bytes_raw
|
||||
handled = True
|
||||
except Exception as e:
|
||||
logger.error(f'Assume source, but {e} ({file_path})')
|
||||
|
||||
# TODO: is Nuitka package?
|
||||
# TODO: is pyc or single marshalled binary?
|
||||
|
||||
if not runtimes:
|
||||
logger.error('No runtime found')
|
||||
return
|
||||
if not sequences:
|
||||
logger.error('No armored data found')
|
||||
return
|
||||
decrypt_process(runtimes, sequences, args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1340
plusaes.hpp
1340
plusaes.hpp
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
||||
/** 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.1shot.seq\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<PycObject>(), &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;
|
||||
}
|
158
pyc_code.cpp
158
pyc_code.cpp
@@ -1,7 +1,6 @@
|
||||
#include "pyc_code.h"
|
||||
#include "pyc_module.h"
|
||||
#include "data.h"
|
||||
#include "plusaes.hpp"
|
||||
|
||||
/* == Marshal structure for Code object ==
|
||||
1.0 1.3 1.5 2.1 2.3 3.0 3.8 3.11
|
||||
@@ -65,14 +64,11 @@ void PycCode::load(PycData* stream, PycModule* mod)
|
||||
else
|
||||
m_flags = 0;
|
||||
|
||||
bool pyarmor_co_obfuscated_flag = m_flags & 0x20000000;
|
||||
|
||||
if (mod->verCompare(3, 8) < 0) {
|
||||
// Remap flags to new values introduced in 3.8
|
||||
// Pyarmor CO_OBFUSCATED flag always locates at 0x20000000
|
||||
if (m_flags & 0xD0000000)
|
||||
fprintf(stderr, "Remapping flags (%08X) may not be correct\n", m_flags);
|
||||
m_flags = (m_flags & 0x1FFF) | ((m_flags & 0xFFFE000) << 4) | (m_flags & 0x20000000);
|
||||
if (m_flags & 0xF0000000)
|
||||
throw std::runtime_error("Cannot remap unexpected flags");
|
||||
m_flags = (m_flags & 0xFFFF) | ((m_flags & 0xFFF0000) << 4);
|
||||
}
|
||||
|
||||
m_code = LoadObject(stream, mod).cast<PycString>();
|
||||
@@ -121,113 +117,6 @@ void PycCode::load(PycData* stream, PycModule* mod)
|
||||
m_exceptTable = LoadObject(stream, mod).cast<PycString>();
|
||||
else
|
||||
m_exceptTable = new PycString;
|
||||
|
||||
// Pyarmor extra fields
|
||||
|
||||
if (!pyarmor_co_obfuscated_flag)
|
||||
return;
|
||||
|
||||
unsigned char extra_data[256] = {0};
|
||||
unsigned char extra_length = stream->getByte();
|
||||
stream->getBuffer(extra_length, extra_data);
|
||||
|
||||
unsigned char pyarmor_fn_count = extra_data[0] & 3;
|
||||
unsigned char pyarmor_co_descriptor_count = (extra_data[0] >> 2) & 3;
|
||||
if (extra_data[0] & 0xF0)
|
||||
{
|
||||
fprintf(stderr, "Unsupported Pyarmor CO extra flag (%02X)\n", extra_data[0]);
|
||||
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");
|
||||
}
|
||||
if (pyarmor_co_descriptor_count > 1)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
unsigned char *extra_ptr = extra_data + 4;
|
||||
for (unsigned char i = 0; i < pyarmor_fn_count; i++)
|
||||
{
|
||||
unsigned char item_length = (*extra_ptr >> 6) + 2;
|
||||
// Ignore the details
|
||||
extra_ptr += item_length;
|
||||
}
|
||||
for (unsigned char i = 0; i < pyarmor_co_descriptor_count; i++)
|
||||
{
|
||||
unsigned char item_length = (*extra_ptr >> 6) + 2;
|
||||
unsigned char *item_end = extra_ptr + item_length;
|
||||
// Ignore low 6 bits
|
||||
extra_ptr++;
|
||||
unsigned long consts_index = 0;
|
||||
while (extra_ptr < item_end)
|
||||
{
|
||||
consts_index = (consts_index << 8) | *extra_ptr;
|
||||
extra_ptr++;
|
||||
}
|
||||
|
||||
pyarmorDecryptCoCode(consts_index, mod);
|
||||
}
|
||||
}
|
||||
|
||||
void PycCode::pyarmorDecryptCoCode(unsigned long consts_index, PycModule *mod)
|
||||
{
|
||||
PycRef<PycString> descriptor = getConst(consts_index).cast<PycString>();
|
||||
const std::string &descriptor_str = descriptor->strValue();
|
||||
if (descriptor_str.length() < 20)
|
||||
{
|
||||
fprintf(stderr, "Pyarmor CO descriptor is too short\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const PyarmorCoDescriptor *desc = (const PyarmorCoDescriptor *)(descriptor_str.data() + 8);
|
||||
bool copy_prologue = desc->flags & 0x8;
|
||||
bool xor_aes_nonce = desc->flags & 0x4;
|
||||
bool short_code = desc->flags & 0x2;
|
||||
|
||||
unsigned int nonce_index = short_code
|
||||
? desc->short_nonce_index
|
||||
: desc->short_nonce_index + desc->decrypt_begin_index + desc->decrypt_length;
|
||||
unsigned char nonce[16] = {0};
|
||||
memcpy(nonce, m_code->value() + nonce_index, 12);
|
||||
nonce[15] = 2;
|
||||
if (xor_aes_nonce)
|
||||
{
|
||||
if (!mod->pyarmor_co_code_aes_nonce_xor_enabled)
|
||||
{
|
||||
fprintf(stderr, "FATAL: Pyarmor CO code AES nonce XOR is not enabled but used\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char *xor_key = mod->pyarmor_co_code_aes_nonce_xor_key;
|
||||
for (int i = 0; i < 12; i++)
|
||||
nonce[i] ^= xor_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::string &code_bytes = (std::string &)m_code->strValue();
|
||||
|
||||
plusaes::crypt_ctr(
|
||||
(unsigned char *)&code_bytes[desc->decrypt_begin_index],
|
||||
desc->decrypt_length,
|
||||
mod->pyarmor_aes_key,
|
||||
16,
|
||||
&nonce);
|
||||
|
||||
if (copy_prologue)
|
||||
{
|
||||
memcpy(
|
||||
&code_bytes[0],
|
||||
&code_bytes[desc->decrypt_length],
|
||||
desc->decrypt_begin_index);
|
||||
// Assume tail of code is not used there
|
||||
memset(
|
||||
&code_bytes[desc->decrypt_length],
|
||||
9, // NOP
|
||||
desc->decrypt_begin_index);
|
||||
}
|
||||
|
||||
// When running, the first 8 bytes are set to &PyCodeObject
|
||||
std::string new_str = "<COAddr>" + descriptor_str.substr(8);
|
||||
descriptor->setValue(new_str);
|
||||
}
|
||||
|
||||
PycRef<PycString> PycCode::getCellVar(PycModule* mod, int idx) const
|
||||
@@ -239,3 +128,44 @@ PycRef<PycString> PycCode::getCellVar(PycModule* mod, int idx) const
|
||||
? m_freeVars->get(idx - m_cellVars->size()).cast<PycString>()
|
||||
: m_cellVars->get(idx).cast<PycString>();
|
||||
}
|
||||
|
||||
int _parse_varint(PycBuffer& data, int& pos) {
|
||||
int b = data.getByte();
|
||||
pos += 1;
|
||||
|
||||
int val = b & 0x3F;
|
||||
while (b & 0x40) {
|
||||
val <<= 6;
|
||||
|
||||
b = data.getByte();
|
||||
pos += 1;
|
||||
|
||||
val |= (b & 0x3F);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
std::vector<PycExceptionTableEntry> PycCode::exceptionTableEntries() const
|
||||
{
|
||||
PycBuffer data(m_exceptTable->value(), m_exceptTable->length());
|
||||
|
||||
std::vector<PycExceptionTableEntry> entries;
|
||||
|
||||
int pos = 0;
|
||||
while (!data.atEof()) {
|
||||
|
||||
int start = _parse_varint(data, pos) * 2;
|
||||
int length = _parse_varint(data, pos) * 2;
|
||||
int end = start + length;
|
||||
|
||||
int target = _parse_varint(data, pos) * 2;
|
||||
int dl = _parse_varint(data, pos);
|
||||
|
||||
int depth = dl >> 1;
|
||||
bool lasti = bool(dl & 1);
|
||||
|
||||
entries.push_back(PycExceptionTableEntry(start, end, target, depth, lasti));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
24
pyc_code.h
24
pyc_code.h
@@ -8,14 +8,16 @@
|
||||
class PycData;
|
||||
class PycModule;
|
||||
|
||||
struct PyarmorCoDescriptor
|
||||
{
|
||||
unsigned char flags;
|
||||
unsigned char short_nonce_index;
|
||||
unsigned char _;
|
||||
unsigned char decrypt_begin_index;
|
||||
unsigned int decrypt_length;
|
||||
unsigned int _enter_count;
|
||||
class PycExceptionTableEntry {
|
||||
public:
|
||||
int start_offset; // inclusive
|
||||
int end_offset; // exclusive
|
||||
int target;
|
||||
int stack_depth;
|
||||
bool push_lasti;
|
||||
|
||||
PycExceptionTableEntry(int m_start_offset, int m_end_offset, int m_target, int m_stack_depth, bool m_push_lasti) :
|
||||
start_offset(m_start_offset), end_offset(m_end_offset), target(m_target), stack_depth(m_stack_depth), push_lasti(m_push_lasti) {};
|
||||
};
|
||||
|
||||
class PycCode : public PycObject {
|
||||
@@ -45,8 +47,6 @@ public:
|
||||
CO_FUTURE_GENERATOR_STOP = 0x800000, // 3.5 ->
|
||||
CO_FUTURE_ANNOTATIONS = 0x1000000, // 3.7 ->
|
||||
CO_NO_MONITORING_EVENTS = 0x2000000, // 3.13 ->
|
||||
|
||||
CO_PYARMOR_OBFUSCATED = 0x20000000, // Pyarmor all
|
||||
};
|
||||
|
||||
PycCode(int type = TYPE_CODE)
|
||||
@@ -55,8 +55,6 @@ public:
|
||||
|
||||
void load(PycData* stream, PycModule* mod) override;
|
||||
|
||||
void pyarmorDecryptCoCode(unsigned long consts_index, PycModule *mod);
|
||||
|
||||
int argCount() const { return m_argCount; }
|
||||
int posOnlyArgCount() const { return m_posOnlyArgCount; }
|
||||
int kwOnlyArgCount() const { return m_kwOnlyArgCount; }
|
||||
@@ -101,6 +99,8 @@ public:
|
||||
m_globalsUsed.emplace_back(std::move(varname));
|
||||
}
|
||||
|
||||
std::vector<PycExceptionTableEntry> exceptionTableEntries() const;
|
||||
|
||||
private:
|
||||
int m_argCount, m_posOnlyArgCount, m_kwOnlyArgCount, m_numLocals;
|
||||
int m_stackSize, m_flags;
|
||||
|
237
pyc_module.cpp
237
pyc_module.cpp
@@ -1,7 +1,6 @@
|
||||
#include "pyc_module.h"
|
||||
#include "data.h"
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
void PycModule::setVersion(unsigned int magic)
|
||||
{
|
||||
@@ -252,242 +251,6 @@ void PycModule::loadFromMarshalledFile(const char* filename, int major, int mino
|
||||
m_code = LoadObject(&in, this).cast<PycCode>();
|
||||
}
|
||||
|
||||
void PycModule::loadFromOneshotSequenceFile(const char *filename)
|
||||
{
|
||||
PycFile in(filename);
|
||||
if (!in.isOpen())
|
||||
{
|
||||
fprintf(stderr, "Error opening file %s\n", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
bool oneshot_seq_header = true;
|
||||
while (oneshot_seq_header)
|
||||
{
|
||||
int indicator = in.getByte();
|
||||
switch (indicator)
|
||||
{
|
||||
case 0xA1:
|
||||
in.getBuffer(16, this->pyarmor_aes_key);
|
||||
break;
|
||||
case 0xA2:
|
||||
in.getBuffer(12, this->pyarmor_mix_str_aes_nonce);
|
||||
break;
|
||||
case 0xF0:
|
||||
break;
|
||||
case 0xFF:
|
||||
oneshot_seq_header = false;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown 1-shot sequence indicator %02X\n", indicator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write only. Some fields unknown to us or not needed for decryption are discarded.
|
||||
char discard_buffer[64];
|
||||
|
||||
char pyarmor_header[64];
|
||||
in.getBuffer(64, pyarmor_header);
|
||||
this->m_maj = pyarmor_header[9];
|
||||
this->m_min = pyarmor_header[10];
|
||||
this->m_unicode = (m_maj >= 3);
|
||||
|
||||
unsigned int remain_header_length = *(unsigned int *)(pyarmor_header + 28) - 64;
|
||||
while (remain_header_length)
|
||||
{
|
||||
unsigned int discard_length = (remain_header_length > 64) ? 64 : remain_header_length;
|
||||
in.getBuffer(discard_length, discard_buffer);
|
||||
remain_header_length -= discard_length;
|
||||
}
|
||||
|
||||
// For 1-shot sequence, the following part has been decrypted once.
|
||||
unsigned int code_object_offset = in.get32();
|
||||
unsigned int xor_key_procedure_length = in.get32();
|
||||
this->pyarmor_co_code_aes_nonce_xor_enabled = (xor_key_procedure_length > 0);
|
||||
unsigned int remain_second_part_length = code_object_offset - 8;
|
||||
while (remain_second_part_length)
|
||||
{
|
||||
unsigned int discard_length = (remain_second_part_length > 64) ? 64 : remain_second_part_length;
|
||||
in.getBuffer(discard_length, discard_buffer);
|
||||
remain_second_part_length -= discard_length;
|
||||
}
|
||||
|
||||
if (this->pyarmor_co_code_aes_nonce_xor_enabled)
|
||||
{
|
||||
char *procedure_buffer = (char *)malloc(xor_key_procedure_length);
|
||||
in.getBuffer(xor_key_procedure_length, procedure_buffer);
|
||||
pyarmorCoCodeAesNonceXorKeyCalculate(
|
||||
procedure_buffer,
|
||||
xor_key_procedure_length,
|
||||
this->pyarmor_co_code_aes_nonce_xor_key);
|
||||
free(procedure_buffer);
|
||||
}
|
||||
|
||||
m_code = LoadObject(&in, this).cast<PycCode>();
|
||||
}
|
||||
|
||||
#define GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(CUR, REF) \
|
||||
do \
|
||||
{ \
|
||||
unsigned char _INSIDE_LOW_NIBBLE = (CUR)[1] & 0xF; \
|
||||
if (valid_index[_INSIDE_LOW_NIBBLE] != -1) \
|
||||
{ \
|
||||
(REF) = registers[_INSIDE_LOW_NIBBLE]; \
|
||||
(CUR) += 2; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
unsigned int _INSIDE_SIZE = (CUR)[1] & 0x7; \
|
||||
if (_INSIDE_SIZE == 1) \
|
||||
{ \
|
||||
(REF) = *(char *)((CUR) + 2); \
|
||||
(CUR) += 3; \
|
||||
} \
|
||||
else if (_INSIDE_SIZE == 2) \
|
||||
{ \
|
||||
(REF) = *(short *)((CUR) + 2); \
|
||||
(CUR) += 4; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
(REF) = *(int *)((CUR) + 2); \
|
||||
(CUR) += 6; \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void pyarmorCoCodeAesNonceXorKeyCalculate(const char *in_buffer, unsigned int in_buffer_length, unsigned char *out_buffer)
|
||||
{
|
||||
unsigned char *cur = (unsigned char *)in_buffer + 16;
|
||||
unsigned char *end = (unsigned char *)in_buffer + in_buffer_length;
|
||||
int registers[8] = {0};
|
||||
const int valid_index[16] = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
-1,
|
||||
7, /* origin is 15 */
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
};
|
||||
|
||||
while (cur < end)
|
||||
{
|
||||
int operand_2 = 0;
|
||||
unsigned char high_nibble = 0;
|
||||
unsigned char reg = 0;
|
||||
switch (*cur)
|
||||
{
|
||||
case 1:
|
||||
// terminator
|
||||
cur++;
|
||||
break;
|
||||
case 2:
|
||||
high_nibble = cur[1] >> 4;
|
||||
GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(cur, operand_2);
|
||||
registers[high_nibble] += operand_2;
|
||||
break;
|
||||
case 3:
|
||||
high_nibble = cur[1] >> 4;
|
||||
GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(cur, operand_2);
|
||||
registers[high_nibble] -= operand_2;
|
||||
break;
|
||||
case 4:
|
||||
high_nibble = cur[1] >> 4;
|
||||
GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(cur, operand_2);
|
||||
registers[high_nibble] *= operand_2;
|
||||
/** We found that in x86_64, machine code is
|
||||
* imul reg64, reg/imm
|
||||
* so we get the low bits of the result.
|
||||
*/
|
||||
break;
|
||||
case 5:
|
||||
high_nibble = cur[1] >> 4;
|
||||
GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(cur, operand_2);
|
||||
registers[high_nibble] /= operand_2;
|
||||
/** We found that in x86_64, machine code is
|
||||
* mov r10d, imm32 ; when necessary
|
||||
* mov rax, reg64
|
||||
* cqo
|
||||
* idiv r10/reg64 ; r10/reg64 is the operand_2
|
||||
* mov reg64, rax
|
||||
* so rax (0) is tampered.
|
||||
*/
|
||||
registers[0] = registers[high_nibble];
|
||||
break;
|
||||
case 6:
|
||||
high_nibble = cur[1] >> 4;
|
||||
GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(cur, operand_2);
|
||||
registers[high_nibble] ^= operand_2;
|
||||
break;
|
||||
case 7:
|
||||
high_nibble = cur[1] >> 4;
|
||||
GET_REAL_OPERAND_2_AND_ADD_CURRENT_PTR(cur, operand_2);
|
||||
registers[high_nibble] = operand_2;
|
||||
break;
|
||||
case 8:
|
||||
/** We found that in x86_64, machine code is
|
||||
* mov reg1, ptr [reg2]
|
||||
* This hardly happens.
|
||||
*/
|
||||
cur += 2;
|
||||
break;
|
||||
case 9:
|
||||
reg = cur[1] & 0x7;
|
||||
*(int *)out_buffer = registers[reg];
|
||||
cur += 2;
|
||||
break;
|
||||
case 0xA:
|
||||
/**
|
||||
* This happens when 4 bytes of total 12 bytes nonce are calculated,
|
||||
* and the result is to be stored in the memory. So the address from
|
||||
* register 7 (15) is moved to one of the registers.
|
||||
*
|
||||
* We don't really care about the address and the register number.
|
||||
* So we just skip 6 bytes (0A ... and 02 ...).
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* [0A [1F] 00] - [00][011][111] - mov rbx<3>, [rbp<7>-18h]
|
||||
* [rbp-18h] is the address
|
||||
* [02 [39] 0C] - [0011][1][001] - add rbx<3>, 0Ch
|
||||
* 0Ch is a fixed offset
|
||||
* [09 [98] ] - [10][011][000] - mov [rbx<3>], eax<0>
|
||||
* eax<0> is the value to be stored
|
||||
*
|
||||
* Another example:
|
||||
*
|
||||
* [0A [07] 00] - [00][000][111] - mov rax<0>, [rbp<7>-18h]
|
||||
* [02 [09] 0C] - [0000][1][001] - add rax<0>, 0Ch
|
||||
* [0B [83] 04] - [10][000][011] - mov [rax<0>+4], ebx<3>
|
||||
* 4 means [4..8] of 12 bytes nonce
|
||||
*/
|
||||
cur += 6;
|
||||
break;
|
||||
case 0xB:
|
||||
reg = cur[1] & 0x7;
|
||||
*(int *)(out_buffer + cur[2]) = registers[reg];
|
||||
cur += 3;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown opcode %d at %lld\n", *cur, (long long)(cur - (unsigned char *)in_buffer));
|
||||
memset(out_buffer, 0, 12);
|
||||
cur = end;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PycRef<PycString> PycModule::getIntern(int ref) const
|
||||
{
|
||||
if (ref < 0 || (size_t)ref >= m_interns.size())
|
||||
|
@@ -46,7 +46,6 @@ public:
|
||||
|
||||
void loadFromFile(const char* filename);
|
||||
void loadFromMarshalledFile(const char *filename, int major, int minor);
|
||||
void loadFromOneshotSequenceFile(const char* filename);
|
||||
bool isValid() const { return (m_maj >= 0) && (m_min >= 0); }
|
||||
|
||||
int majorVer() const { return m_maj; }
|
||||
@@ -81,11 +80,6 @@ public:
|
||||
|
||||
static bool isSupportedVersion(int major, int minor);
|
||||
|
||||
unsigned char pyarmor_aes_key[16];
|
||||
unsigned char pyarmor_mix_str_aes_nonce[12];
|
||||
bool pyarmor_co_code_aes_nonce_xor_enabled;
|
||||
unsigned char pyarmor_co_code_aes_nonce_xor_key[12];
|
||||
|
||||
private:
|
||||
void setVersion(unsigned int magic);
|
||||
|
||||
@@ -98,6 +92,4 @@ private:
|
||||
std::vector<PycRef<PycObject>> m_refs;
|
||||
};
|
||||
|
||||
void pyarmorCoCodeAesNonceXorKeyCalculate(const char *in_buffer, unsigned int in_buffer_length, unsigned char *out_buffer);
|
||||
|
||||
#endif
|
||||
|
@@ -2,7 +2,6 @@
|
||||
#include "pyc_module.h"
|
||||
#include "data.h"
|
||||
#include <stdexcept>
|
||||
#include "plusaes.hpp"
|
||||
|
||||
static bool check_ascii(const std::string& data)
|
||||
{
|
||||
@@ -155,28 +154,3 @@ 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);
|
||||
}
|
||||
|
@@ -30,10 +30,6 @@ 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;
|
||||
};
|
||||
|
27
pycdas.cpp
27
pycdas.cpp
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <unordered_set>
|
||||
#include "pyc_module.h"
|
||||
#include "pyc_numeric.h"
|
||||
#include "bytecode.h"
|
||||
@@ -23,7 +24,7 @@ static const char* flag_names[] = {
|
||||
"CO_FUTURE_PRINT_FUNCTION", "CO_FUTURE_UNICODE_LITERALS", "CO_FUTURE_BARRY_AS_BDFL",
|
||||
"CO_FUTURE_GENERATOR_STOP",
|
||||
"CO_FUTURE_ANNOTATIONS", "CO_NO_MONITORING_EVENTS", "<0x4000000>", "<0x8000000>",
|
||||
"<0x10000000>", "CO_PYARMOR_OBFUSCATED", "<0x40000000>", "<0x80000000>"
|
||||
"<0x10000000>", "<0x20000000>", "<0x40000000>", "<0x80000000>"
|
||||
};
|
||||
|
||||
static void print_coflags(unsigned long flags, std::ostream& pyc_output)
|
||||
@@ -73,6 +74,8 @@ static void iprintf(std::ostream& pyc_output, int indent, const char* fmt, ...)
|
||||
va_end(varargs);
|
||||
}
|
||||
|
||||
static std::unordered_set<PycObject *> out_seen;
|
||||
|
||||
void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
|
||||
unsigned flags, std::ostream& pyc_output)
|
||||
{
|
||||
@@ -81,6 +84,12 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
|
||||
return;
|
||||
}
|
||||
|
||||
if (out_seen.find((PycObject *)obj) != out_seen.end()) {
|
||||
fputs("WARNING: Circular reference detected\n", stderr);
|
||||
return;
|
||||
}
|
||||
out_seen.insert((PycObject *)obj);
|
||||
|
||||
switch (obj->type()) {
|
||||
case PycObject::TYPE_CODE:
|
||||
case PycObject::TYPE_CODE2:
|
||||
@@ -104,7 +113,7 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
|
||||
unsigned int orig_flags = codeObj->flags();
|
||||
if (mod->verCompare(3, 8) < 0) {
|
||||
// Remap flags back to the value stored in the PyCode object
|
||||
orig_flags = (orig_flags & 0x1FFF) | ((orig_flags & 0xDFFE0000) >> 4) | (orig_flags & 0x20000000);
|
||||
orig_flags = (orig_flags & 0xFFFF) | ((orig_flags & 0xFFF00000) >> 4);
|
||||
}
|
||||
iprintf(pyc_output, indent + 1, "Flags: 0x%08X", orig_flags);
|
||||
print_coflags(codeObj->flags(), pyc_output);
|
||||
@@ -145,16 +154,16 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
|
||||
iputs(pyc_output, indent + 1, "[Disassembly]\n");
|
||||
bc_disasm(pyc_output, codeObj, mod, indent + 2, flags);
|
||||
|
||||
if (mod->verCompare(3, 11) >= 0) {
|
||||
iputs(pyc_output, indent + 1, "[Exception Table]\n");
|
||||
bc_exceptiontable(pyc_output, codeObj, indent+2);
|
||||
}
|
||||
|
||||
if (mod->verCompare(1, 5) >= 0 && (flags & Pyc::DISASM_PYCODE_VERBOSE) != 0) {
|
||||
iprintf(pyc_output, indent + 1, "First Line: %d\n", codeObj->firstLine());
|
||||
iputs(pyc_output, indent + 1, "[Line Number Table]\n");
|
||||
output_object(codeObj->lnTable().cast<PycObject>(), mod, indent + 2, flags, pyc_output);
|
||||
}
|
||||
|
||||
if (mod->verCompare(3, 11) >= 0 && (flags & Pyc::DISASM_PYCODE_VERBOSE) != 0) {
|
||||
iputs(pyc_output, indent + 1, "[Exception Table]\n");
|
||||
output_object(codeObj->exceptTable().cast<PycObject>(), mod, indent + 2, flags, pyc_output);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PycObject::TYPE_STRING:
|
||||
@@ -165,7 +174,7 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
|
||||
case PycObject::TYPE_SHORT_ASCII:
|
||||
case PycObject::TYPE_SHORT_ASCII_INTERNED:
|
||||
iputs(pyc_output, indent, "");
|
||||
obj.cast<PycString>()->dasPrintAndDecrypt(pyc_output, mod);
|
||||
obj.cast<PycString>()->print(pyc_output, mod);
|
||||
pyc_output << "\n";
|
||||
break;
|
||||
case PycObject::TYPE_TUPLE:
|
||||
@@ -246,6 +255,8 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
|
||||
default:
|
||||
iprintf(pyc_output, indent, "<TYPE: %d>\n", obj->type());
|
||||
}
|
||||
|
||||
out_seen.erase((PycObject *)obj);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
|
BIN
tests/compiled/binary_slice.3.12.pyc
Normal file
BIN
tests/compiled/binary_slice.3.12.pyc
Normal file
Binary file not shown.
BIN
tests/compiled/store_slice.3.12.pyc
Normal file
BIN
tests/compiled/store_slice.3.12.pyc
Normal file
Binary file not shown.
BIN
tests/compiled/test_loops3.3.12.pyc
Normal file
BIN
tests/compiled/test_loops3.3.12.pyc
Normal file
Binary file not shown.
BIN
tests/compiled/test_raise_varargs.3.12.pyc
Normal file
BIN
tests/compiled/test_raise_varargs.3.12.pyc
Normal file
Binary file not shown.
7
tests/input/binary_slice.py
Normal file
7
tests/input/binary_slice.py
Normal file
@@ -0,0 +1,7 @@
|
||||
l = [1,2,3,4,5,6]
|
||||
print(l[1:3])
|
||||
print(l[:2])
|
||||
print(l[3:])
|
||||
print(l[-4:])
|
||||
print(l[:-2])
|
||||
print(l[:])
|
8
tests/input/store_slice.py
Normal file
8
tests/input/store_slice.py
Normal file
@@ -0,0 +1,8 @@
|
||||
a = [0] * 16
|
||||
l = [1,2,3]
|
||||
a[13:] = l
|
||||
a[5] = 10
|
||||
a[:2] = [1,2]
|
||||
a[:] = range(16)
|
||||
|
||||
print(a)
|
34
tests/input/test_loops3.py
Normal file
34
tests/input/test_loops3.py
Normal file
@@ -0,0 +1,34 @@
|
||||
def loop1():
|
||||
iterable = [1, 2, 3]
|
||||
for item in iterable:
|
||||
pass
|
||||
|
||||
loop1()
|
||||
|
||||
def loop2():
|
||||
for i in range(2):
|
||||
print(i)
|
||||
|
||||
loop2()
|
||||
|
||||
def loop3():
|
||||
def loop():
|
||||
x = (1,2,3)
|
||||
l = []
|
||||
for i in x:
|
||||
l.append(i)
|
||||
return l
|
||||
|
||||
return loop()
|
||||
|
||||
loop3()
|
||||
|
||||
def loop4():
|
||||
for i in range(3):
|
||||
for j in range(2):
|
||||
print(i*j)
|
||||
|
||||
loop4()
|
||||
|
||||
for j in [1,2,3][::-1]:
|
||||
print("hi", j)
|
7
tests/input/test_raise_varargs.py
Normal file
7
tests/input/test_raise_varargs.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import struct
|
||||
|
||||
def bytes_to_words(b):
|
||||
'''Convert a byte string (little-endian) to a list of 32-bit words.'''
|
||||
if len(b) % 4 != 0:
|
||||
raise ValueError('Input bytes length must be a multiple of 4 for word conversion.')
|
||||
return struct.unpack('<' + 'I' * (len(b) // 4), b)
|
7
tests/tokenized/binary_slice.txt
Normal file
7
tests/tokenized/binary_slice.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
l = [ 1 , 2 , 3 , 4 , 5 , 6 ] <EOL>
|
||||
print ( l [ 1 : 3 ] ) <EOL>
|
||||
print ( l [ : 2 ] ) <EOL>
|
||||
print ( l [ 3 : ] ) <EOL>
|
||||
print ( l [ - 4 : ] ) <EOL>
|
||||
print ( l [ : - 2 ] ) <EOL>
|
||||
print ( l [ : ] ) <EOL>
|
7
tests/tokenized/store_slice.txt
Normal file
7
tests/tokenized/store_slice.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
a = [ 0 ] * 16 <EOL>
|
||||
l = [ 1 , 2 , 3 ] <EOL>
|
||||
a [ 13 : ] = l <EOL>
|
||||
a [ 5 ] = 10 <EOL>
|
||||
a [ : 2 ] = [ 1 , 2 ] <EOL>
|
||||
a [ : ] = range ( 16 ) <EOL>
|
||||
print ( a ) <EOL>
|
46
tests/tokenized/test_loops3.txt
Normal file
46
tests/tokenized/test_loops3.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
def loop1 ( ) : <EOL>
|
||||
<INDENT>
|
||||
iterable = [ 1 , 2 , 3 ] <EOL>
|
||||
for item in iterable : <EOL>
|
||||
<INDENT>
|
||||
pass <EOL>
|
||||
<OUTDENT>
|
||||
<OUTDENT>
|
||||
loop1 ( ) <EOL>
|
||||
def loop2 ( ) : <EOL>
|
||||
<INDENT>
|
||||
for i in range ( 2 ) : <EOL>
|
||||
<INDENT>
|
||||
print ( i ) <EOL>
|
||||
<OUTDENT>
|
||||
<OUTDENT>
|
||||
loop2 ( ) <EOL>
|
||||
def loop3 ( ) : <EOL>
|
||||
<INDENT>
|
||||
def loop ( ) : <EOL>
|
||||
<INDENT>
|
||||
x = ( 1 , 2 , 3 ) <EOL>
|
||||
l = [ ] <EOL>
|
||||
for i in x : <EOL>
|
||||
<INDENT>
|
||||
l . append ( i ) <EOL>
|
||||
<OUTDENT>
|
||||
return l <EOL>
|
||||
<OUTDENT>
|
||||
return loop ( ) <EOL>
|
||||
<OUTDENT>
|
||||
loop3 ( ) <EOL>
|
||||
def loop4 ( ) : <EOL>
|
||||
<INDENT>
|
||||
for i in range ( 3 ) : <EOL>
|
||||
<INDENT>
|
||||
for j in range ( 2 ) : <EOL>
|
||||
<INDENT>
|
||||
print ( i * j ) <EOL>
|
||||
<OUTDENT>
|
||||
<OUTDENT>
|
||||
<OUTDENT>
|
||||
loop4 ( ) <EOL>
|
||||
for j in [ 1 , 2 , 3 ] [ : : - 1 ] : <EOL>
|
||||
<INDENT>
|
||||
print ( 'hi' , j ) <EOL>
|
9
tests/tokenized/test_raise_varargs.txt
Normal file
9
tests/tokenized/test_raise_varargs.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
import struct <EOL>
|
||||
def bytes_to_words ( b ) : <EOL>
|
||||
<INDENT>
|
||||
'Convert a byte string (little-endian) to a list of 32-bit words.' <EOL>
|
||||
if len ( b ) % 4 != 0 : <EOL>
|
||||
<INDENT>
|
||||
raise ValueError ( 'Input bytes length must be a multiple of 4 for word conversion.' ) <EOL>
|
||||
<OUTDENT>
|
||||
return struct . unpack ( '<' + 'I' * ( len ( b ) // 4 ) , b ) <EOL>
|
Reference in New Issue
Block a user