-
Notifications
You must be signed in to change notification settings - Fork 298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Access to raw code objects #11
Comments
If restrict mode is enable, that is the default behavior, there is a limit:
That is, you CAN NOT disassemble obfuscated module "mymod" by "dis", because no any other script can import "mymod" out of obfuscated scripts. Refer to Restricted mode But if restrict mode is disable as you do, you can import obfuscated scripts from other script. Although byte code can not be disassembled at the begin, but the byte code of code object will be restored after its first called, so you can disassemble those code objects which have been executed. I have taken it into account to obfuscate byte code after it returns, but it will affect performance. Before I find good way, restrict mode is one solution, of course, it's not apply to all of the cases. You're a great guy! |
Well, in this case the Odoo example is more or less useless. Because I can even use the raw bytecode (.pyc) rather than encrypting in unrestricted mode to get the same level of obfuscation. Pyarmor should wrap all functions and methods:
Any downsides to this? |
The only downside of wrap all functions is to reduce the performance, but it's a way. I'll try to implement it and test the performance. If it's acceptable, add this feature in next minor release. Thanks. |
The workaround may be like this
|
But it should go into a try/finally: def wraparmor(func):
def wrapper(*args, **kwargs):
__wraparmor__(func.func_code)
try:
return func(*args, **kwargs)
finally:
__wraparmor__(func.func_code)
wrapper.__module__ = func.__module__
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
wrapper.__dict__.update(func.__dict__)
return wrapper With the downside, that we loose the function signatures... |
Nice. And I read the source of built-in decorator |
The final decorator will be def wraparmor(func):
def wrapper(*args, **kwargs):
__wraparmor__(func)
try:
return func(*args, **kwargs)
finally:
__wraparmor__(func, 1)
wrapper.__module__ = func.__module__
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
wrapper.__dict__.update(func.__dict__)
func.__refcalls__ = 0
return wrapper Add an attribute |
Released in v3.7.0 Here is the basic usage: Use decorator to protect code objects when disable restrict mode Here is an example: Protect module with decorator "wraparmor" |
Seems to work. Even the debugger crashes ;) but this needs further testing... One thing to note: If an exception occurs in def wraparmor(func):
def wrapper(*args, **kwargs):
__wraparmor__(func)
try:
return func(*args, **kwargs)
+ except Exception as err:
+ raise err
finally:
__wraparmor__(func, 1)
wrapper.__module__ = func.__module__
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
wrapper.__dict__.update(func.__dict__)
func.__refcalls__ = 0
return wrapper This way it is possible to pass the corresponding frame. It has the downside of a wrong location of the exception and makes debugging more difficult. But if you have some secret variables in a particular function someone could use this decorator instead. |
Users must also know that globals are mutable but locals are not. So you should always wrap the whole module within a function and just make public functions global: @wraparmor
def __load__():
@wraparmor
def my_private_func():
pass
global my_public_func
@wraparmor
def my_public_func():
pass
__load__()
del __load__ |
One more thing: Would it be possible to allow the import of the entry module only, but restrict the direct import of any other module? |
Good idea! The workaround would be Only entry module can run "pyarmor_runtime()" Rationale:
If there is more than one entry, end users just generate different project license for each entry script. Why didn't I think of it, it's terrific! After all, the way to use decorator is somewhat complicated, and just as you have mentioned, need more and more tests. |
Next issue: Only encrypted modules must be allowed to call |
From v3.7.1, static PyObject *wrapcaller = NULL;
static PyObject*
__wraparmor__(PyObject *self, PyObject *args)
{
PyObject *co_code = PyEval_GetFrame()->f_code->co_code;
// First time call, set wrapcaller to code object of function wrapper in decorator
if (!wrapcaller)
wrapcaller = co_code;
// If it's not called from the same code object, return NULL, and no exception set
else if (wrapcaller != co_code)
return NULL;
// Go on ... |
Adding checkpoints in c function |
Great, |
As soon as my time allows I will migrate a project to pyarmor and buy a license. I let you know if I run into troubles. Great work! |
Is it somehow possible to prevent a wrapped function dectrypting itself by just calling it? |
But I think if wrapped function will be encrypted again as soon as it returns in any way, it should not be a problem. |
Well, I think you are right. Just triggered an exception, got the respective wrapper frame, the wrapped function in its locals and tried to call it directly. It did not work. So everthing seems to be fine. But shouldn't it be callable this way? |
I have refined |
Unfortunately ipython crahes as well now, since it tries to access the func_code apparently. |
It's not possible to raise SystemError because
|
Patching IPython works as well: https://stackoverflow.com/a/28758396 |
The problem is accessing |
It's no problem. |
Unfortunately it doesn't work in v3.8.8 either. |
Further more it should be possible to call This is required to support dynamically created functions and it solves the problem with generator functions. Are there any issues calling |
Ok, encryption depends on |
Can still the callback get data from |
Call |
If I call an external function from an encrypted function regardless of an exception, Furthermore calling |
The latter would also solve issues with generator functions, since the wrapper can initially set |
Have a look at this example: def wraparmor(func, shift_error=False, is_generator=False):
func.__refcalls__ = is_generator and 1 or 0
def wrapper(*args, **kwargs):
__wraparmor__(func)
tb = None
try:
return func(*args, **kwargs)
except Exception as err:
tb = exc_info()[2]
if shift_error:
raise err
raise
finally:
__wraparmor__(func, tb, 1)
return wrapper
def wrapgenerator(func):
return wraparmor(func, is_generator=True)
@wrapgenerator
def myfunc():
for i in range(10):
yield i You cannot encrypt |
And here an example of the first issue. The encrypted module: @wraparmor
def do_something(progress_callback):
foo = 'secret'
progress_callback() End user script: from encrypted_module import do_something
def mycallback():
frame = sys._getframe(1)
print(frame.f_code.co_name, frame.f_locals)
do_something(mycallback) |
No extra parameter if (co_flags & CO_GENERATOR) {
// Do not obfuscate byte-code
} And |
Even better, yes. |
About the callback issue, I have almost same test case as your example, the callback can only get an empty dictionary in v3.8.8, does still the cached version takes effect? |
No, version is 3.8.8. |
Here it doesn't work. I still get the locals. |
The getter is set when any |
Here it's latest version http://pyarmor.dashingsoft.com/downloads/platforms/linux_x86_64/_pytransform.so In this version
I need to be sure the callback issuse is fixed before publish. |
Latest version does work! |
Ok, I got it. The following example causes a segfault: def factory():
@wraparmor
def nestedfunc(f=None):
if f:
f()
return nestedfunc
func1 = factory()
func2 = factory()
func1(func2) The problem is, that the codeobject of |
Fixed in v3.8.10. Use |
So |
It's still required in v3.8.10. But it can be removed in next version as long as |
The example above works now. But there is still an issue with a similar construct. Strange. |
I'm not sure whether |
Since v3.9 it runs into a segfault again, even if obfuscated without |
Maybe it's better use the previous version of Pyarmor, only update the latest dynamic library
|
Just updating the dynamic libraries results in a segfault as well. |
I obfuscated the following Python script.
examples/test/mymod.py:
Build pyarmor:
In Python shell:
Is it the correct behaviour that you have access to the raw code objects of a module?
So, what is the advantage of using pyarmor? Thanks.
The text was updated successfully, but these errors were encountered: