標籤:

[Python] 置換CPython 2.7.13的opcode

之前在一個問題下打過一下醬油,國內有CPython解釋器定製相關的工作么? - RednaxelaFX 的回答 - 知乎,裡面提到一種混淆CPython解釋器的方式是置換其虛擬機的opcode的值,這樣經過修改的CPython把Python源碼編譯成含有CPython位元組碼的 *.pyc / *.pyo 文件,就不能直接用未經修改的CPython解釋器運行,也不能直接用現成的用來反彙編或反編譯CPython位元組碼的工具來處理。這樣就略微提高了Python程序的保密性,假如說要發布一個用Python實現的程序而不想別人讀到源碼的話,這樣就算防不住小人至少也能防住君子吧。

最近有人來問說他自己試著修改了CPython 2.7.13的opcode,但make的時候卻出錯,想問是怎麼回事。這裡就來演示一下最簡單的opcode置換應該怎麼做。

其實超級簡單,只要對應修改2個文件即可。下面演示一下把 BINARY_ADD 與 BINARY_SUBTRACT 的opcode編碼調換的例子:

(我這裡是在CPython 2.7分支的master版代碼上做的diff)

diff --git a/Include/opcode.h b/Include/opcode.hnindex 9ed5487..8f2e9fa 100644n--- a/Include/opcode.hn+++ b/Include/opcode.hn@@ -27,8 +27,8 @@ extern "C" {n #define BINARY_MULTIPLYt20n #define BINARY_DIVIDEt21n #define BINARY_MODULOt22n-#define BINARY_ADDt23n-#define BINARY_SUBTRACTt24n+#define BINARY_ADDt24n+#define BINARY_SUBTRACTt23n #define BINARY_SUBSCRt25n #define BINARY_FLOOR_DIVIDE 26n #define BINARY_TRUE_DIVIDE 27ndiff --git a/Lib/opcode.py b/Lib/opcode.pynindex e403365..49c48fa 100644n--- a/Lib/opcode.pyn+++ b/Lib/opcode.pyn@@ -62,8 +62,8 @@ def_op(BINARY_POWER, 19)n def_op(BINARY_MULTIPLY, 20)n def_op(BINARY_DIVIDE, 21)n def_op(BINARY_MODULO, 22)n-def_op(BINARY_ADD, 23)n-def_op(BINARY_SUBTRACT, 24)n+def_op(BINARY_ADD, 24)n+def_op(BINARY_SUBTRACT, 23)n def_op(BINARY_SUBSCR, 25)n def_op(BINARY_FLOOR_DIVIDE, 26)n def_op(BINARY_TRUE_DIVIDE, 27)n

注意一定要把這兩個文件都對應修改才可以。如果只修改 Include/opcode.h 的話就會在make的時候遇到這樣的錯誤:

$ makengcc -c -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I. -IInclude -I./Include -DPy_BUILD_CORE -o Modules/python.o ./Modules/python.cn...ngcc -u _PyMac_Error -o python.exe ntttModules/python.o ntttlibpython2.7.a -ldl -framework CoreFoundation n./python.exe -E -S -m sysconfig --generate-posix-vars ;ntif test $? -ne 0 ; then nttecho "generate-posix-vars failed" ; nttrm -f ./pybuilddir.txt ; nttexit 1 ; ntfinTraceback (most recent call last):n File "/private/tmp/python2713/Python-2.7.13/Lib/encodings/__init__.py", line 99, in search_functionn mod = __import__(encodings. + modname, fromlist=_import_tail,nTypeError: unsupported operand type(s) for -: str and strngenerate-posix-vars failednmake: *** [pybuilddir.txt] Error 1n

這是因為漏做的修改導致一個 str + str 的操作被當作 str - str 了(哈哈

就這麼簡單。

話說如果真就這樣簡單置換一下 BINARY_ADD 與 BINARY_SUBTRACT 的opcode的話,「防護」效果肯定能有一點但是應該會很有限。如果不添加或刪減任何位元組碼指令,而純粹置換 opcode encoding 的話,我會建議參考以下的一些思路:

  • 帶參數的 opcode 跟不帶參數的 opcode 置換
  • 有控制流語義(跳轉、調用、返回)的 opcode 跟沒有控制流語義的 opcode 置換
  • 不同功能組之間的 opcode 置換
  • 每次發布重新隨機生成一次置換

而如果有條件的話,最好還是增加 / 修改一下 opcode 的指令集。CPython的位元組碼用的是1-byte opcode,編碼空間有256個位置,而現在的CPython才用了一半多一點,還有很多空間可用。例如說

  • 把一些常見的指令序列給它合併起來弄成一個新的 opcode 啦(這種思路叫做 superinstruction);
  • 或者把原本分開的兩個功能的位元組碼指令給它糅合起來變成兩個部分功能重疊的位元組碼指令,一定要以某種順序執行才等價於原本的其中一條位元組碼、另一種順序執行才等價於另一條位元組碼;
  • 等等。

外加 *.pyc / *.pyo 的格式也可以順帶修改一下,破壞現成工具對這種格式的文件的讀取。

這樣的話在CPython的源碼->位元組碼編譯器里也要做些相應修改才能對應。功夫是要多花一點,但「防護」效果會比簡單置換 opcode encoding 要好很多。順帶還可以做點性能優化——更優化的 opcode 指令集可以帶來稍微高一點的性能。

就先寫這麼多。

推薦閱讀:

Tornado與flask的特點和區別有哪些?
Python利用嵌套函數二分搜索列表中大於等於m,小於等於n的數字
Python GUI教程(九):從UI文件中解耦Python代碼

TAG:Python |