標籤:conf 相同 void art 搜尋 list 資料 output pytho
作為一種膠水語言,Python 能夠很容易地調用 C 、 C++ 等語言,也能夠通過其他語言調用 Python 的模組。
Python 提供了 C++ 庫,使得開發人員能很方便地從 C++ 程式中調用 Python 模組。
具體操作可以參考: 官方文檔
在調用Python模組時需要如下步驟:
- 初始化Python調用環境
- 載入對應的Python模組
- 載入對應的Python函數
- 將參數轉化為Python元群組類型
- 調用Python函數並傳入參數元組
- 擷取傳回值
- 根據Python函數的定義解析傳回值
初始化
在調用Python模組時需要首先包含Python.h標頭檔,這個標頭檔一般在安裝的Python目錄中的 include檔案中,所在VS中首先需要將這個路徑加入到項目中
包含完成之後可能會抱一個錯誤:找不到 inttypes.h檔案,在個錯誤在Windows平台上很常見,如果報這個錯誤,需要去網上下載對應的inttypes.h檔案然後放入到對應的目錄中即可,我這放到VC的include目錄中
在包含這些檔案完成之後可能還會抱一個錯誤,未找到Python36_d.lib 在Python環境中確實找不到這個檔案,這個時候可以修改pyconfig.h檔案,將這個lib改為python36.lib,具體操作請參考這個連結: 78947526
還有一點要注意,下載的Python環境必須的與目標程式的類型相同,比如你在VS 中建立一個Win32項目,在引用Python環境的時候就需要引用32位版本的Python
這些準備工作做完後在調用Python前先調用Py_Initialize 函數來初始化Python環境,之後我們可以調用Py_IsInitialized來檢測Python環境是否初始化成功
下面是一個初始化Python環境的例子
BOOL Init(){ Py_Initialize(); return Py_IsInitialized();}
調用Python模組
調用Python模組可以簡單的調用Python語句也可以調用Python模組中的函數。
簡單調用Python語句
針對簡單的Python語句(就好像我們在Python的互動式環境中輸入的一條語句那樣),可以直接調用 PyRun_SimpleString 函數來執行, 這個函數需要一個Python語句的ANSI字串作為參數,返回int型的值。如果為0表示執行成功否則為失敗
void ChangePyWorkPath(LPCTSTR lpWorkPath){ TCHAR szWorkPath[MAX_PATH + 64] = _T(""); StringCchCopy(szWorkPath, MAX_PATH + 64, _T("sys.path.append(\"")); StringCchCat(szWorkPath, MAX_PATH + 64, lpWorkPath); StringCchCat(szWorkPath, MAX_PATH + 64, _T("\")")); PyRun_SimpleString("import sys"); USES_CONVERSION; int nRet = PyRun_SimpleString(T2A(szWorkPath)); if (nRet != 0) { return; }}
這個函數主要用來將傳入的路徑加入到當前Python的執行環境中,以便可以很方便的匯入我們的自訂模組
函數首先通過字串拼接的方式組織了一個 "sys.path.append(‘path‘)" 這樣的字串,其中path是我們傳進來的參數,然後調用PyRun_SimpleString執行Python的"import sys"語句來匯入sys模組,接著執行之前拼接的語句,將對應路徑加入到Python環境中
調用Python模組中的函數
調用Python模組中的函數需要執行之前說的2~7的步驟
- 載入Python模組(自訂模組)
載入Python的模組需要調用 PyImport_ImportModule 這個函數需要傳入一個模組的名稱作為參數,注意:這裡需要傳入的是模組的名稱也就是py檔案的名稱,不能帶.py尾碼。
這個函數會返回一個Python對象的指標,在C++中表示為PyObject。這裡返回模組的對象指標
- 然後調用
PyObject_GetAttrString 函數來載入對應的Python模組中的方法,這個函數需要兩個參數,第一個是之前擷取到的對應模組的指標,第二個參數是函數名稱的ANSI字串。這個函數會返回一個對應Python函數的對象指標。後面需要利用這個指標來調用Python函數
擷取到函數的指標之後我們可以調用 PyCallable_Check 來檢測一下對應的對象是否可以被調用,如果能被調用這個函數會返回true否則返回false
- 接著就是傳入參數了,Python中函數的參數以元組的方式傳入的,所以這裡需要先將要傳入的參數轉化為元組,然後調用
PyObject_CallObject 函數來執行對應的Python函數。這個函數需要兩個參數第一個是上面Python函數對象的指標,第二個參數是需要傳入Python函數中的參數組成的元組。函數會返回Python的元組對象,這個元組就是Python函數的傳回值
- 擷取到傳回值之後就是解析參數了,我們可以使用對應的函數將Python元組轉化為C++中的變數
- 最後需要調用
Py_DECREF 來解除Python對象的引用,以便Python的記憶體回收行程能正常的回收這些對象的記憶體
下面是一個傳入空參數的例子
void GetModuleInformation(IN LPCTSTR lpPyFileName, OUT LPTSTR lpVulName, OUT long& level){ USES_CONVERSION; PyObject *pModule = PyImport_ImportModule(T2A(lpPyFileName)); //載入模組 if (NULL == pModule) { g_OutputString(_T("載入模組[%s]失敗"), lpPyFileName); goto __CLEAN_UP; } PyObject *pGetInformationFunc = PyObject_GetAttrString(pModule, "getInformation"); // 載入模組中的函數 if (NULL == pGetInformationFunc || !PyCallable_Check(pGetInformationFunc)) { g_OutputString(_T("載入函數[%s]失敗"), _T("getInformation")); goto __CLEAN_UP; } PyObject *PyResult = PyObject_CallObject(pGetInformationFunc, NULL); if (NULL != PyResult) { PyObject *pVulNameObj = PyTuple_GetItem(PyResult, 0); PyObject *pVulLevelObj = PyTuple_GetItem(PyResult, 1); //擷取漏洞的名稱資訊 int nStrSize = 0; LPTSTR pVulName = PyUnicode_AsWideCharString(pVulNameObj, &nStrSize); StringCchCopy(lpVulName, MAX_PATH, pVulName); PyMem_Free(pVulName); //擷取漏洞的危險等級 level = PyLong_AsLong(pVulLevelObj); Py_DECREF(pVulNameObj); Py_DECREF(pVulLevelObj); } //解除Python對象的引用, 以便Python進行記憶體回收__CLEAN_UP: Py_DECREF(pModule); Py_DECREF(pGetInformationFunc); Py_DECREF(PyResult);}
在樣本中調用了一個叫 getInformation 的函數,這個函數的定義如下:
def getInformation(): return "測試指令碼", 1
下面是一個需要傳入參數的函數調用
BOOL CallScanMethod(IN LPPYTHON_MODULES_DATA pPyModule, IN LPCTSTR lpUrl, IN LPCTSTR lpRequestMethod, OUT LPTSTR lpHasVulUrl, int BuffSize){ USES_CONVERSION; //載入模組 PyObject* pModule = PyImport_ImportModule(T2A(pPyModule->szModuleName)); if (NULL == pModule) { g_OutputString(_T("載入模組[%s]失敗!!!"), pPyModule->szModuleName); return FALSE; } //載入模組 PyObject *pyScanMethod = PyObject_GetAttrString(pModule, "startScan"); if (NULL == pyScanMethod || !PyCallable_Check(pyScanMethod)) { Py_DECREF(pModule); g_OutputString(_T("載入函數[%s]失敗!!!"), _T("startScan")); return FALSE; } //載入參數 PyObject* pArgs = Py_BuildValue("ss", T2A(lpUrl), T2A(lpRequestMethod)); PyObject *pRes = PyObject_CallObject(pyScanMethod, pArgs); Py_DECREF(pArgs); if (NULL == pRes) { g_OutputString(_T("調用函數[%s]失敗!!!!"), _T("startScan")); return FALSE; } //如果是元組,那麼Python指令碼返回的是兩個參數,證明發現漏洞 if (PyTuple_Check(pRes)) { PyObject* pHasVul = PyTuple_GetItem(pRes, 0); long bHasVul = PyLong_AsLong(pHasVul); Py_DECREF(pHasVul); if (bHasVul != 0) { PyObject* pyUrl = PyTuple_GetItem(pRes, 1); int nSize = 0; LPWSTR pszUrl = PyUnicode_AsWideCharString(pyUrl, &nSize); Py_DECREF(pyUrl); StringCchCopy(lpHasVulUrl, BuffSize, pszUrl); PyMem_Free(pszUrl); return TRUE; } } Py_DECREF(pRes); return FALSE;}
對應的Python函數如下:
def startScan(url, method): if(method == "GET"): response = requests.get(url) else: response = requests.post(url) if response.status_code == 200: return True, url else: return False
C++資料類型與Python對象的相互轉化
Python與C++結合的一個關鍵的內容就是C++與Python資料類型的相互轉化,針對這個問題Python提供了一系列的函數。
這些函數的格式為PyXXX_AsXXX 或者PyXXX_FromXXX,一般帶有As的是將Python對象轉化為C++資料類型的,而帶有From的是將C++對象轉化為Python,Py前面的XXX表示的是Python中的資料類型。比如 PyUnicode_AsWideCharString 是將Python中的字串轉化為C++中寬字元,而 Pyunicode_FromWideChar 是將C++的字串轉化為Python中的字串。這裡需要注意一個問題就是Python3廢除了在2中的普通的字串,它將所有字串都當做Unicode了,所以在調用3的時候需要將所有字串轉化為Unicode的形式而不是像之前那樣轉化為String。具體的轉化類型請參考Python官方的說明。
上面介紹了基礎資料型別 (Elementary Data Type)的轉化,除了這些Python中也有一些容器類型的資料,比如元組,字典等等。下面主要說說元組的操作。元組算是比較重要的操作,因為在調用函數的時候需要元組傳參並且需要解析以便擷取元組中的值。
- 建立Python的元組對象
建立元組對象可以使用 PyTuple_New 來建立一個元組的對象,這個函數需要一個參數用來表示元組中對象的個數。
之後需要建立對應的Python對象,可以使用前面說的那些轉化函數來建立普通Python對象,然後調用 PyTuple_SetItem 來設定元組中資料的內容,函數需要三個參數,分別是元組對象的指標,元組中的索引和對應的資料
樣本:
cpp PyObject* args = PyTuple_New(2); // 2個參數 PyObject* arg1 = PyInt_FromLong(4); // 參數一設為4 PyObject* arg2 = PyInt_FromLong(3); // 參數二設為3 PyTuple_SetItem(args, 0, arg1); PyTuple_SetItem(args, 1, arg2);
或者如果元組中都是單一資料型別,可以直接使用 PyObject* args = Py_BuildValue(4, 3); 這種方式來建立元組
- 解析元組
Python 函數返回的是元組,在C++中需要進行對應的解析,我們可以使用 PyTuple_GetItem 來擷取元組中的資料成員,這個函數返回PyObject 的指標,之後再使用對應的轉化函數將Python對象轉化成C++資料類型即可
PyObject *pVulNameObj = PyTuple_GetItem(PyResult, 0);PyObject *pVulLevelObj = PyTuple_GetItem(PyResult, 1);//擷取漏洞的名稱資訊int nStrSize = 0;LPTSTR pVulName = PyUnicode_AsWideCharString(pVulNameObj, &nStrSize);StringCchCopy(lpVulName, MAX_PATH, pVulName);PyMem_Free(pVulName); //釋放由PyUnicode_AsWideCharString分配出來的記憶體//擷取漏洞的危險等級level = PyLong_AsLong(pVulLevelObj);//最後別忘了將Python對象解引用Py_DECREF(pVulNameObj);Py_DECREF(pVulLevelObj);Py_DECREF(PyResult);
Python中針對具體資料類型操作的函數一般是以Py開頭,後面跟上具體的資料類型的名稱,比如操作元組的PyTuple系列函數和巨集指令清單的PyList系列函數,後面如果想操作對應的資料類型只需要去官網搜尋對應的名稱即可。
這些代碼執行個體都是我之前寫的一個Demo中的代碼,Demo放到了Github上: PyScanner
C++ 調用Python3