如果执行未编译的 Python 代码,在使用 Cython 构建的可执行文件中可以看到什么?

What is visible in an executable built with Cython, in case non-compiled Python code is executed?

当我们编写 Cython 代码时(with types),这最终将像 C 编译代码一样被编译,我们无法恢复源代码(除了反汇编,但随后类似于反汇编 C 代码),如 .

所示

但是当我们在 Cython .pyx 文件中编写“正常 Python 代码”(解释代码 没有类型 )并生成可执行文件时会发生什么?其中有多少将在可执行文件的字符串中可见?

示例:

import bottle, random, json
app = bottle.Bottle()
@bottle.route('/')
def index():
    return 'hello'
@bottle.route('/random')
def testrand():
    return str(random.randint(0, 100))
@bottle.route('/jsontest')
def testjson():
    x = json.loads('{ "1": "2" }')
    return 'done'
bottle.run()

在这种情况下,我在 test.c 中看到:

static const char __pyx_k_1_2[] = "{ \"1\": \"2\" }";
static const char __pyx_k_json[] = "json";
static const char __pyx_k_main[] = "__main__";
static const char __pyx_k_name[] = "__name__";
static const char __pyx_k_test[] = "__test__";
static const char __pyx_k_loads[] = "loads";
static const char __pyx_k_import[] = "__import__";
static const char __pyx_k_cline_in_traceback[] = "cline_in_traceback";

所以在示例 2 中,所有这些字符串在可执行文件中不是很容易看到吗?

通常,您将无法避免在生成的可执行文件中包含这些字符串,这正是 python 的工作方式 - 在 运行 时需要它们。

如果我们看一个简单的 C-code:


void do_nothing(){...}

int main(){
  do_nothing();
  return 0;
}

静态编译和link它。 linker 完成后,do_nothing 的调用(假设它没有内联或优化)只是跳转到 memory-address - 函数的名称不再是需要并且可以从生成的可执行文件中删除。

Python 的工作方式不同:没有 linker,我们在 运行 期间不使用原始 memory-addresses 来调用某些功能,而是使用Python-machinery 根据 package/module 和函数的名称为我们找到它 - 因此我们需要这些信息 - 在 运行 时间内的名称。因此必须在 运行 时间内提供。


但是,如果您正在改变所产生的游戏规则 c-file,您可能会使“黑客”的生活变得更加艰难。

当调用Python-functionality需要字符串时,会产生如下代码(如import json):

static const char __pyx_k_json[] = "json";

static PyObject *__pyx_n_s_json;

static __Pyx_StringTabEntry __pyx_string_tab[] = {
  ...
  {&__pyx_n_s_json, __pyx_k_json, sizeof(__pyx_k_json), 0, 0, 1, 1},
  ...
  {0, 0, 0, 0, 0, 0, 0}
};

static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) {
  if (__Pyx_InitStrings(__pyx_string_tab) < 0) __PYX_ERR(0, 1, __pyx_L1_error);
...
}
...
__pyx_t_1 = __Pyx_Import(__pyx_n_s_json, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)

所以可以将 "json" 保存为 "irnm"(每个字符移动 -1),然后在调用 __Pyx_InitStrings 之前的 运行 时间内恢复真实姓名在 __Pyx_InitGlobals.

所以现在,只要将字符串转储到 exe 中,就不会出现字符组合。如果值得的话,甚至可以更进一步,在程序启动后从某处加载真实姓名。