feat: mix str for disasm

This commit is contained in:
2025-03-02 23:48:24 +08:00
parent 6465cf03fe
commit 832f35413b
6 changed files with 55 additions and 12 deletions

View File

@@ -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)
const char* parent_f_string_quote, bool das_decrypt_print)
{
if (obj == NULL) {
pyc_output << "<NULL>";
@@ -131,7 +131,9 @@ 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:
obj.cast<PycString>()->print(pyc_output, mod, false, parent_f_string_quote);
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);
break;
case PycObject::TYPE_TUPLE:
case PycObject::TYPE_SMALL_TUPLE:
@@ -362,7 +364,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);
print_const(pyc_output, constParam, mod, nullptr, true);
} catch (const std::out_of_range &) {
formatted_print(pyc_output, "%d <INVALID>", operand);
}

View File

@@ -28,7 +28,7 @@ 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);
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<PycCode> code, PycModule* mod,
int indent, unsigned flags);

View File

@@ -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__':

View File

@@ -2,6 +2,7 @@
#include "pyc_module.h"
#include "data.h"
#include <stdexcept>
#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);
}

View File

@@ -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;
};

View File

@@ -165,7 +165,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>()->print(pyc_output, mod);
obj.cast<PycString>()->dasPrintAndDecrypt(pyc_output, mod);
pyc_output << "\n";
break;
case PycObject::TYPE_TUPLE: