Svenskithesource / PyArmor-Unpacker

A deobfuscator for PyArmor.
GNU General Public License v3.0
493 stars 73 forks source link

Support python2.x on Linux #26

Open w1nds opened 1 year ago

w1nds commented 1 year ago

decrypt python 2.7 under Linux, hope it will be supported.

Injector:https://github.com/gaffe23/linux-inject so:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <link.h>
#include<pthread.h>
#include<unistd.h>
void my_init(void) __attribute__((constructor)); 
void my_fini(void) __attribute__((destructor)); 

void logfile(const char *szText)
{
    FILE *fp = fopen("/tmp/slog.txt","a+");
    if(fp)
    {
        fwrite(szText,strlen(szText),1,fp);
        fclose(fp);
    }
}

enum PyGILState_STATE{ 
    PyGILState_LOCKED, 
    PyGILState_UNLOCKED 
};

struct PyCompilerFlags{
    int cf_flags;  /* bitmask of CO_xxx flags relevant to future */
    int cf_feature_version;  /* minor Python version (PyCF_ONLY_AST) */
};

typedef void (*pPy_SetProgramName)(char*);
typedef void(* _PyEval_InitThreads)();
typedef PyGILState_STATE(* _PyGILState_Ensure)();
typedef void(* _PyGILState_Release)(PyGILState_STATE);
typedef int(* _PyRun_SimpleStringFlags)(const char*, PyCompilerFlags*);

void InitCPython()
{
    pPy_SetProgramName Py_SetProgramName = NULL;
    _PyEval_InitThreads PyEval_InitThreads;
    _PyGILState_Ensure PyGILState_Ensure;
    _PyGILState_Release PyGILState_Release;
    _PyRun_SimpleStringFlags PyRun_SimpleStringFlags;
    void * hPython  = dlopen("/usr/lib64/libpython2.7.so.1.0",RTLD_GLOBAL | RTLD_LAZY);
    if (hPython)
    {
        Py_SetProgramName = (pPy_SetProgramName)dlsym(hPython, "Py_SetProgramName");
        PyEval_InitThreads = (_PyEval_InitThreads)(dlsym(hPython, "PyEval_InitThreads"));
        PyGILState_Ensure = (_PyGILState_Ensure)(dlsym(hPython, "PyGILState_Ensure"));
        PyGILState_Release = (_PyGILState_Release)(dlsym(hPython, "PyGILState_Release"));
        PyRun_SimpleStringFlags = (_PyRun_SimpleStringFlags)(dlsym(hPython, "PyRun_SimpleStringFlags"));

        Py_SetProgramName("PyInjector");
        PyEval_InitThreads();
        PyGILState_STATE s = PyGILState_Ensure();
        PyRun_SimpleStringFlags("import os\nwith open(\"/wtest/code.py\",\"r\") as file:\n   data = file.read()\nexec(data)",0); // more easy to execute wanted code this way
        PyGILState_Release(s);
    }
    else
    {
        logfile("open libpython2 failed\n");
    }    
}

void* fun(void *arg)
{
    InitCPython();
}
void my_init(void)  
{  
    logfile("init\n");
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);
    // InitCPython();
}  
void my_fini(void)  
{  
}
Svenskithesource commented 1 year ago

Hey, thanks for making methods 1 and 2 work on Linux! Did you try both methods? Can you maybe include the build command you used to compile the library? Definitely interested in including this.

w1nds commented 1 year ago

Sorry, I haven't tried it on Linux python 3.x, I tried 2.x directly。 build the library command gcc test.cpp -o test.so -fPIC -shared -ldl

Svenskithesource commented 1 year ago

Hey, I'm not asking to try it on Python 3.x, I was just wondering if you tried both method 1 and method 2. Since those are the methods that require injecting. Thanks for the build command btw!

w1nds commented 1 year ago

Yes, I tried method 1 and 2, both suggest some syntax errors, I tried to modify some syntax errors in method 2, until suggest inspect module object has no attribute 'Signature' ,then I came to submit the issue, haha...

Svenskithesource commented 1 year ago

Ah, that wasn't clear in your first message. Currently, I'm not planning on supporting Python 2.x. You can try to figure out what functions I used are non-existing in Python 2.x and try to find alternatives for them. I'll gladly accept a pull request if you figure it out!

w1nds commented 1 year ago

some questions, thx

  1. i used method 2 in linux for python2.7。 the py script import an package encryped by pyarmor,for example:

    # -*- coding: utf-8 -*-
    import re
    import sys
    from test.fuck.handle import main
    if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

    everything under the test package is encrypted,i inject then run method2 ,but the dumped file is _usr_lib64_python2.7_threading.pyc,not the test.fuck.handle.main function I want。

  2. I rebuild libpython for python2.7,modifyed PyEval_EvalFrameEx function to dump the PyCodeObject,but I found that the dumped pyc is the same as the original file after decompiling,it's also__pyarmor__(__name__, __file__, 'v\xd0

sorry for my poor english ,I don't know if you understand my description O(∩_∩)O~

Svenskithesource commented 1 year ago

Is the test module importing any external libraries? If so you can create a .py file with the same name and put an input() which will give you time to dump it while Python is executing the test module. It could be possible that you'll have to change the script to make it find the test module in the threads, as the name of the frame will probably be the name of the module that you're hijacking. You could also just put the method 2 inside of the hijacked module so that when the script imports the module you can just do sys._getframe(1) to get the test's module.