《由淺入深的cython逆向指南.pdf》由會員分享,可在線閱讀,更多相關《由淺入深的cython逆向指南.pdf(36頁珍藏版)》請在三個皮匠報告上搜索。
1、由淺入深的由淺入深的 Cython Cython 逆向指南逆向指南 c10udlnk.About UsAbout Us山石網科安全技術研究院山石網科安全技術研究院 研究方向包括WEB安全、移動安全、智能安全和信創安全等,參編過多份安全標準。曾為上合峰會、財富論壇、港珠澳大橋的開通等重大活動提供了網絡安保工作并獲得感謝信。獲得過蘋果、紅帽等知名企業的官網致謝,連續多年進入微軟全球最具價值安全研究員榜單,公布過多個具有全球影響力的嚴重漏洞,在 Blackhat、Defcon、HITB、CanSecWest 等國際安全大會分享過研究成果,獲得了諸多 CVE 漏洞編號。榮獲過紅帽杯企業組冠軍,補天杯最
2、具價值漏洞獎,強網擬態防御國際精英挑戰賽一等獎,第五空間網絡安全大賽特等獎,津門杯國際網絡安全創新大賽一等獎,隴警杯網絡安全大賽第一名等優異成績。About About MeMeREer inREer inNEURON,S1uM4i,Sloth y2020=y2020=首次接觸 Python 字節碼,從 Python 不熟練到手撕字節碼一天速成。y2021:2023=y2021:2023=.研究的魔改 pyc 越來越多,能直接被反編譯的都是時代的眼淚;.分析了 Python 字節碼隱寫工具 stegosaurus,在版本不兼容的情況下手動摳出一個 flag,賽后做了一些魔改使其兼容 Pytho
3、n 3.10(https:/ 逆向開始卷到了 Cython,靜態分析慢慢上手,結合動態交互開始能解出 flag 了。y2023=y2023=畢設選題做的是 Python 代碼保護相關,深入研究了 Oxyry、pyc_obscure、Py*or 等混淆保護,設計了一套加密器。y2024=y2024=結合 xasm 實現對手撕字節碼的自動化(https:/ Cython 逆向的自動化(https:/ CythonCython及及 Cython Cython 逆向逆向Cython 是什么?和 Python、CPython 的關系CTF 逆向中的 Cython關于關于 CythonCython htt
4、ps:/cython.org/https:/ 一個基于 Pyrex 的 Python 編譯器,它使為為 Python Python 編寫編寫 C C 擴展擴展就像編寫 Python 本身一樣簡單。將代碼段從動態 Python 語義移動到靜態且快速的 C 語義,并可以直接操作外部庫中定義的類型。源代碼被轉換為優化的 C/C+代碼并編譯為 Python 擴展模塊,從而加快了程序的執行速度,并可與外部 C 庫緊密集成,同時保持 Python 語言的編寫簡易性。#關于 Python 的 C 擴展:https:/docs.python.org/zh-cn/3/extending/extending.ht
5、ml#“C 擴展接口特指 CPython,擴展模塊無法在其他 Python 版本上工作。在大多數情況下,應該避免寫 C 擴展,來保持可移植性。舉個例子,如果你的用例調用了 C 庫或系統調用,你應該考慮使用 ctypes 模塊或 cffi 庫,而不是自己寫 C 代碼?!本帉懢帉?P Python ython 的的 C C 擴展擴展 添加 C 函數到擴展模塊:在模塊的 method table 中添加對外調用接口:在模塊的定義結構中引用,并創建 C 擴展的 Entry Point:直接用 Python 編寫 cythonize 一把梭 然后就擁有了對應的 C 擴展文件和編譯好的庫用用 Cython
6、 Cython 編寫編寫 P Python ython 的的 C C 擴展擴展 CythonCython=一個可以將 Python 轉化為 C 的編譯器,也是一種語言(Python 的超集)PythonPython=一種眾所周知的編程語言,可以讓你快速工作并更有效地集成系統。CPythonCPython=Python 解釋器的源碼,并給用戶提供 Python-C API 支持以編寫 C 擴展.#(https:/ Python 代碼或只提供字節碼文本 .pyc 字節碼文件#pyc反編譯 魔改的.pyc 字節碼文件#修改魔改部分,反編譯.修改或刪除文件頭.co_code 中插入花指令.在冗余字節中
7、嵌入密鑰.魔改 Python 解釋器+僅可用該解釋器運行的代碼 Pyinstaller 打包#解包,pyc 反編譯 cython cython 編譯的擴展(編譯的擴展(.pyd/.so.pyd/.so)#人工手撕人工手撕+動態動態.給 main 函數,擴展中寫入容易猜的加密(如 xor).main 函數中只寫調用邏輯,全部加密都在擴展中.#Pyinstaller Pyinstaller 打包打包時對 import 進來的 Python 代碼都會調用 Cython 來編譯以加快調用速度;或者單給單給擴展擴展和代碼和代碼。Python Python in CTF Reversein CTF Rev
8、erseCython Cython 逆向逆向之之動態取巧與靜態動態取巧與靜態硬看硬看動態 import,摸清大概思路靜態分析,補充函數細節動靜結合,將 flag 收入囊中 首先判斷給的擴展的文件格式和對應 Python 版本。.#有些題目看文件名就知道這些信息了,但大部分題目會改掉文件名.可見是 Linux 中編譯的擴展,x86-64。.Python 版本為 3.10。通過動態交互來取巧通過動態交互來取巧解題解題 我們使用對應平臺和架構的環境對應平臺和架構的環境,運行對應版本的對應版本的 Python Python,即可成功導入該模塊。.使用 dir dir 可以看到該模塊對外的函數接口和全局
9、變量:.k k 是全局變量,可以輕松看到它的值。.而 checkFlagcheckFlag 和 encenc 這兩個函數由于是 cyfunction,所以沒辦法使用類似 disdis 等模塊套出它們的字節碼,因為它們這類在 C 層面上編寫的函數根本就沒有 Python 字節碼:)通過動態交互來取巧通過動態交互來取巧解題解題 不過,我們還有其他方法可以旁敲側擊函數的一些性質。.比如 co_varnamesco_varnames 里包含其用到的變量名(參數+局部變量),co_argcountco_argcount 表示其參數個數,與 co_varnamesco_varnames 結合就知道這個函數
10、的參數名和用到的局部變量名了。當然,我們還可以實際調用這兩個函數,來測試其功能,甚至還有一些可以通過觸發報錯來探測邏輯:.一眼 xor 了屬于是。#當然一些題目會 ban Traceback通過動態交互來取巧通過動態交互來取巧解題解題 假設沒有 Traceback,只有報錯類型,同樣也能測試出一些細節。.說明傳入的參數是個可切片的數據類型可切片的數據類型,比如字符串或者列表。.說明參數需要是 charchar。.說明參數長度不夠長度不夠,逐個長度爆破直到不報錯為止。.現在知道了參數 a 的一些信息:需要長度為長度為1616的字符串的字符串。通過動態交互來取巧通過動態交互來取巧解題解題 chal
11、.enc(1)TypeError:int object is not subscriptable chal.enc(1,2)TypeError:ord()expected string of length 1,but int found chal.enc(aaabc)IndexError:string index out of range chal.enc(aaabcaaaaaaaaaa)IndexError:string index out of range chal.enc(aaabcaaaaaaaaaaa)53,9,8,17,42,18,32,42,4,24,64,64,64,64,64
12、,64 我們前面輸出的 k k 剛好也是 16 字節,那首先可以猜一些一一對應的常見加密方法一一對應的常見加密方法,比如 xor;或者密鑰塊和密鑰塊和明文塊的長度都為明文塊的長度都為 16 16 的對稱密碼算法,比如 AES、SM4 等,或使用了使用了 ECB ECB 加密加密模式(沒有模式(沒有 iv iv)的 IDEA、Blowfish、TEA 系列等,或密鑰長度和明文長度不定的流密碼密鑰長度和明文長度不定的流密碼如 RC4 等。#RE 主打一個猜.總之一種種加密試過去,在該題被解出的次數多被解出的次數多/難度不大難度不大/比賽不難比賽不難的情況下很容易猜出來。通過動態交互來取巧通過動態交
13、互來取巧解題解題 那么該題只剩被比對的數組沒有獲取到了。常量在動態交互中獲取不到,反編譯.so 文件試試(以 ghidra 為例):.可以看到這里有一個 plVar11=(long plVar11=(long*)PyList_New(0 x10);)PyList_New(0 x10);,開了一個長度為 16 的列表,而后有一系列賦值過程,這個很有可能就是我們要的數組。.這些全局變量可以通過找交叉引用找到它們代表的 PyObject:.DAT_0010cad8=PyLong_FromLong(DAT_0010cad8=PyLong_FromLong(0 x6d)0 x6d),代表 DAT_001
14、0cad8 DAT_0010cad8 這個變量其實是0 x6d 0 x6d 的PyObject,可以理解為 plVar110=plVar110=0 x6d 0 x6d,以此類推可以拿到這個被比對數組。.#pplVar1=(long*)plVar113.#=plVar11.ob_item通過動態交互來取巧解題通過動態交互來取巧解題+是時候來點靜態是時候來點靜態分析了分析了 HookHook 大法好!可以將任何自定義的函數代替擴展里原來的函數。.定義一個函數 hook_funchook_func,盡量還原該函數原本功能(不影響后續處理),插入 print print;.使用 MOD._dict_f
15、unc=hook_funcMOD._dict_func=hook_func 將自定義函數掛進去;.調用函數,get 中間值。函數越多越細,接口越多,就能獲取越多的中間值。動態交互還有動態交互還有 靜態分析是在動態交互測試無法完全摸透函數邏輯無法完全摸透函數邏輯的情況下才用的方法,一般用以摸清細節。.初接觸時會覺得很復雜,摸熟了以后就很快上手了。全局代碼一般位于 _pyx_pymod_exec_MODNAME_pyx_pymod_exec_MODNAME(MODNAME 為該模塊的名字)通常其中還包括一些常量的初始化、if main、全局變量和函數的定義等。#如果不知道主要邏輯在哪個函數里一般就
16、逆這個函數 定義的函數一般以 _pyx_pw_pyx_pw_*的形式,直接在函數表里搜需要的函數即可:通過靜態分析來解題通過靜態分析來解題 靜態分析.難點難點=Cython Cython 包裝的框架代碼包裝的框架代碼+Python Python 代碼的一對多代碼的一對多+特別長的反編譯結果特別長的反編譯結果 靜態分析.關鍵點關鍵點=排除無用的框架代碼排除無用的框架代碼+(熟悉一些常見連招熟悉一些常見連招,分塊逐個擊破分塊逐個擊破)+細心細心.#連招可以通過自己編一個 Cython 擴展、比對反編譯結果和 C 代碼,來進一步熟悉。無用(不影響函數邏輯)的框架代碼包括:.Error Handle.
17、引用計數的維護.對某些 API 調用的 N 重保障 警惕函數套娃:.#高版本的 Cython 會上 wrapper,這里的 param_2 就是實際調用的參數對象通過靜態分析來解題通過靜態分析來解題通過靜態分析來解題通過靜態分析來解題-C by -C by CythonCython_pyx_t_2=b2i_pyx_t_2=b2i_pyx_t_1=_pyx_t_1=_pyx_t_2_pyx_t_2(_pyx_v_pyx_v_inp,inp,_pyx_v_pyx_v_inp_idx)inp_idx)_pyx_v_x=_pyx_t_1_pyx_v_x=_pyx_t_1x=b2i(inp,inp_id
18、x)x=b2i(inp,inp_idx)通過靜態分析來解題通過靜態分析來解題-反編譯反編譯 by by GhidraGhidrax=b2i(inp,inp_idx)x=b2i(inp,inp_idx)plVar5=b2iplVar5=b2i(Error Handle)(Error Handle)_pyx_n_s_pyx_n_s_*是通過工具處理后的符號,是通過工具處理后的符號,原反編譯結果中原反編譯結果中沒有沒有通過靜態分析來解題通過靜態分析來解題-反編譯反編譯 by by GhidraGhidrax=b2i(inp,inp_idx)x=b2i(inp,inp_idx)plVar4=b2i(p
19、aram_1,param_2)plVar4=b2i(param_1,param_2)(Error Handle)(Error Handle)(維護引用計數維護引用計數&Error Handle)Error Handle)通過靜態分析來解題通過靜態分析來解題-反編譯反編譯 by by GhidraGhidrax=b2i(inp,inp_idx)x=b2i(inp,inp_idx)_pyx_n_s_pyx_n_s_*是通過工具處理后的符號,是通過工具處理后的符號,原反編譯結果中原反編譯結果中沒有沒有通過靜態分析來解題的一些常見通過靜態分析來解題的一些常見小連招小連招 數字之間的運算通常調用 PyN
20、umber_PyNumber_*,少部分會直接用 C 的運算符 對如 int.from_bytes(.)int.from_bytes(.)之類的調用會用 GetAttrGetAttr 對字符串的初始化,通常在 _Pyx_CreateStringTabAndInitStrings_Pyx_CreateStringTabAndInitStrings 中 對數字和元組等常量的初始化,通常在 _pyx_pymod_exec_MODNAME_pyx_pymod_exec_MODNAME 中plVar13=(long*)PyNumber_Or(plVar12,local_80);local_e8=(lon
21、g*)PyNumber_And(plVar4,DAT_00118018);plVar11=(long*)PyNumber_Add(local_c8,plVar10);plVar8=(long*)PyNumber_Xor(local_d0,plVar11);plVar8=(long*)PyObject_GetAttr(&PyLong_Type,DAT_00117bd0)/&_pyx_n_s_from_bytes,_pyx_k_from_bytes,sizeof(_pyx_k_from_bytes),0,0,1,1,&_pyx_n_s_from_bytes,_pyx_k_from_bytes,si
22、zeof(_pyx_k_from_bytes),0,0,1,1,local_680=&DAT_00117bd0;/&_pyx_n_s_from_bytes pcStack_678=from_bytes;/_pyx_k_from_bytesDAT_00118018=PyLong_FromLong(0 xffff),DAT_00118018=0_DAT_00118048=PyTuple_Pack(0 x13,DAT_00117c10,DAT_00117c18,DAT_00117c68,DAT_00117c78,DAT_00117c28 ,DAT_00117cd8,DAT_00117cf8,DAT_
23、00117ce0,DAT_00117ce8,DAT_00117d00,DAT_00117d08,DAT_00117c00,DAT_00117bf0,DAT_00117c88,DAT_00117c90,DAT_00117c98,DAT_00117ca0,DAT_00117ca8,DAT_00117cb0)通過靜態分析來解題的一些常見通過靜態分析來解題的一些常見小連招小連招 forfor 或者 whilewhile 一出,大部分都是原 Python 代碼里的循環(為了優化會不調用 range range)調用函數一般流程:獲取函數 PyCodeObject,把參數放進一個數組里,然后調用 Py*C
24、allDictlocal_60=8;do /.local_60=local_60+-1;if(local_60=0)goto LAB_0010fd4d;/.while(true);plVar10=(long*)_Pyx_GetBuiltinName_Pyx_GetBuiltinName(_pyx_n_s_func3_a);plVar7=(long*)PyNumber_AndPyNumber_And(plVar5,_pyx_int_65535);plVar8=(long*)_Pyx_PyObject_GetItem_Slow_Pyx_PyObject_GetItem_Slow(param_5,l
25、ocal_a0);local_580=plVar12;local_581=plVar7;local_582=plVar8;local_d0=(long*)PyObject_VectorcallDictPyObject_VectorcallDict(plVar10,pplVar21,uVar20,0);Cython Cython 逆向逆向之之符號符號恢復恢復什么?這題居然不給符號?不能忘記的關鍵數據結構根據 Cython/CPython 源碼猜出 APICython Cython 逆向的噩夢:我的逆向的噩夢:我的符號呢?符號呢?從放水到步入正常難度(與其他類型的逆向題橫向比較)難點:.找不到函數
26、(比如最重要的 _pyx_pymod_exec_MOD_pyx_pymod_exec_MODNAME NAME 和 _pyx_pw_pyx_pw_*).一些 Cython 中重新實現的函數也無名字了 突破點:.需要熟悉特定結構體.唯一的依靠就是 Cpython 的 API 起手難,上手后難度同有符號表的尋找函數尋找函數 萬物起源:PyInit_PyInit_MODNAMEMODNAME.Cython 的 methods 多放在 _pyx_moduledef_slots_pyx_moduledef_slots 里尋找函數尋找函數 通過 _pyx_pymod_exec_MOD_pyx_pymod_
27、exec_MOD 找到各個函數的定義位置API API 符號符號恢復恢復 找字符串,搜 github,比對_Pyx_CyFunction_Init_Pyx_CyFunction_InitCython Cython 逆向逆向之之自動化自動化輔助工具輔助工具關鍵開發思路開發中的挑戰實戰效果cycythonHelper/thonHelper/https:/ 主要功能=提取文件信息+全局變量命名+跟蹤臨時變量 依據 Ghidra 的 P-code 進行插件編寫,其余反編譯軟件插件思路同理。一些開發中的挑戰.排除 Cython 框架代碼的干擾;.Const 和 RAM 的判斷:在實際代參時都為數字,但
28、P-code 中需要區分,如(const,0 x1140fd,8)(const,0 x1140fd,8);.Op 的常見順序和一般編程順序不同:開發開發思路思路 抓住 P-code 中的特定 Opcode,如 CALLCALL。通過跟蹤全局變量賦值,對變量進行命名。利用 Ghidra 中的 getDef()getDef()和 getDescendants()getDescendants()跟蹤數據流。將當前 Op 整理成嵌套數組,通過遞歸調用找到最終的值。00118080 CALL PyCode_NewWithPosOnlyArgs const:00000002 const:00000000
29、const:00000000 const:00000009 const:00000000 const:00000003 00117b40 00117b38 00117b38 00118050 CALL PyTuple_Pack const:00000009 00117c08 00117c70 00117bf0 00117cd8 00117cf8 00117cf0 00117ba0 00117c00 00117b70 00117b38 00117b38 00117b68 00117c30 const:00000030 00117b40實戰效果實戰效果Future.Future.提取特定函數的輸入
30、輸出以幫助重建函數邏輯 提取數組local_58=(long*)&_pyx_n_s_b;plVar8=(long*)PyObject_GetAttr(&PyLong_Type,_pyx_n_s_from_bytes);pplVar17=&local_58;plVar9=(long*)PyObject_VectorcallDict(plVar8,pplVar17,uVar16,0);plVar9=PyLong_Type.from_bytes(b)plVar6=(long*)PyList_New(0 x30);plVar11=_pyx_int_194;pplVar2=(long*)plVar63;*pplVar2=plVar11;plVar11=_pyx_int_39;pplVar21=plVar11;plVar11=_pyx_int_14;pplVar22=plVar11;plVar11=_pyx_int_214;pplVar23=plVar11;.plVar6=194,39,14,214,.