在《在windows程序中嵌入lua腳本引擎–建立一個簡易的“云命令”執行的系統》一文中,我提到了使用lua的ffi庫,可以讓我們像編寫c代碼一樣編寫lua程序。這對我們這些c程序員來說是一件令人興奮的事。然而,使用ffi庫編寫的程序通常會比較大,因為我們可能需要聲明一些api的原型和結構體。比如,在luajit的wiki中有一個關于使用ffi調用kernel32的例子。(轉載請注明出自breaksoftware的csdn博客)如果我們想要使用
BOOL HeapWalk(HANDLE hHeap, PROCESS_HEAP_ENTRY * lpEntry);
在Lua中,我們需要進行如下聲明:
ffi.cdef[[typedef struct _PROCESS_HEAP_ENTRY { PVOID lpData; DWORD cbData; BYTE cbOverhead; BYTE iRegionIndex; WORD wFlags; union { struct { HANDLE hMem; DWORD dwReserved[ 3 ]; } Block; struct { DWORD dwCommittedSize; DWORD dwUnCommittedSize; LPVOID lpFirstBlock; LPVOID lpLastBlock; } Region; } DUMMYUNIONNAME;} PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY, *PPROCESS_HEAP_ENTRY;BOOL HeapWalk(HANDLE hHeap, PROCESS_HEAP_ENTRY * lpEntry);]]
也就是說,我們需要告訴Lua,我們要調用的函數的名字以及函數使用的結構體的原型。
看到這里,可能會讓想使用ffi庫的朋友感到有些退縮。那么,我們如何才能更簡潔地調用這個函數呢?那就是:編寫我們自己的Lua庫”fl”。
我們可以參考Luajit中os庫的聲明方式。
- 在lualib.h中新增我們庫的名字”fl”,并聲明注冊我們庫的函數luaopen_fl
……#define LUA_FFILIBNAME"ffi"#define LUA_FLLIBNAME "fl"
……LUALIB_API int luaopen_ffi(lua_State *L);LUALIB_API int luaopen_fl(lua_State *L);
- 在lib_init.c中,將我們的庫名字和打開庫的名字綁定
…… { LUA_JITLIBNAME,luaopen_jit }, { LUA_FLLIBNAME, luaopen_fl }, { NULL,NULL }
- 在Lualib目錄下新建一個lib_fl.c文件
實現最基本的函數和結構。主要是實現注冊我們庫的luaopen_fl函數,以及函數名和函數地址綁定結構體數組luaL_Reg_fl_lib
#include "lua.h"#include "lauxlib.h"#include "lualib.h"#include "lj_lib.h"static const luaL_Reg fl_lib[] = { { NULL, NULL }};LUALIB_API int luaopen_fl(lua_State *L){ luaL_register(L, LUA_FLLIBNAME, fl_lib); return 1;}
-
將lib_fl.c加入《在windows程序中嵌入Lua腳本引擎–使用VS ide編譯Luajit腳本引擎》中介紹的Lua工程。
-
修改《在Windows程序中嵌入Lua腳本引擎–使用VS IDE編譯Luajit腳本引擎》中介紹的Buildvm工程的生成后事件。在事件中,將lib_fl.c加入ALL_LIB中
@set ALL_LIB=lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c lib_fl.c
- 在ljamalg.c中新增#include “lib_fl.c”
這樣我們便新增了一個名為fl的庫,我們可以
fl = require "fl"fl.……
來調用它。
現在我們要擴展我的庫:
A. 在fl庫中新增一個獲取系統版本信息的函數
LJLIB_CF(fl_GetSystemVersion){ OSVERSIONINFOA osver; ZeroMemory(&osver, sizeof(OSVERSIONINFOA)); osver.dwOSVersionInfoSize = sizeof(osver); if ( GetVersionExA(&osver) ) { lua_pushnumber(L, osver.dwMajorVersion); lua_pushnumber(L, osver.dwMinorVersion); lua_pushnumber(L, osver.dwBuildNumber); lua_pushnumber(L, osver.dwPlatformId); lua_pushstring(L, osver.szCSDVersion); return 5; } else { return 0; }}
并在fl_lib數組中新增名字和該函數的地址的綁定
static const luaL_Reg fl_lib[] = { { "GetSystemVersion",lj_cf_fl_GetSystemVersion }, { NULL, NULL }};
這樣我們編譯出來的Luajit便可以使用簡單的方法調用獲取系統版本了。
B. 在fl庫中新增一個獲取系統中所有進程的函數
為了讓我們的這個例子盡可能復雜,我不準備使用快照的方法去獲取進程信息。而是使用Windows未公開的函數NtQuerySystemInformation。我在之前的《使用APIHOOK實現進程隱藏》中介紹過該方法。
#include <windows.h>#define MAXLOOPCOUNT 5 #define MAXPROcssNUM 1024#define STATUS_SUCCESS 0x00000000#define SystemProcessInformation 5#define STATUS_SUCCESS 0x00000000#define STATUS_INFO_LENGTH_MISMATCH ((ULONG)0xC0000004L)typedef DWORD (WINAPI * Fun_NtQuerySystemInformation)( DWORD, PVOID, DWORD, PDWORD );typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer;} UNICODE_STRING;typedef UNICODE_STRING *PUNICODE_STRING;typedef const UNICODE_STRING *PCUNICODE_STRING;typedef struct _SYSTEM_PROCESS_INFORMATION{ DWORD dwNextEntryOffset; // 下段結構對象的偏移 DWORD dwNumberOfThreads; // 線程數 LARGE_INTEGER qSpareLi1; LARGE_INTEGER qSpareLi2; LARGE_INTEGER qSpareLi3; LARGE_INTEGER qCreateTime; // 創建時間 LARGE_INTEGER qUserTime; // 用戶態時間 LARGE_INTEGER qKernelTime; // 內核態時間 UNICODE_STRING ImageName; // 文件名(非路徑) int nBasePriority; // 基本優先級 DWORD dwProcessId; // 進程ID DWORD dwInheritedFromUniqueProcessId; // 父進程ID DWORD dwHandleCount; // 句柄數 DWORD dwSessionId; ULONG dwSpareUl3; SIZE_T tPeakVirtualSize; SIZE_T tVirtualSize; DWORD dwPageFaultCount; DWORD dwPeakWorkingSetSize; DWORD dwWorkingSetSize; SIZE_T tQuotaPeakPagedPoolUsage; SIZE_T tQuotaPagedPoolUsage; SIZE_T tQuotaPeakNonPagedPoolUsage; SIZE_T tQuotaNonPagedPoolUsage; SIZE_T tPagefileUsage; SIZE_T tPeakPagefileUsage; SIZE_T tPrivatePageCount; LARGE_INTEGER qReadOperationCount; LARGE_INTEGER qWriteOperationCount; LARGE_INTEGER qOtherOperationCount; LARGE_INTEGER qReadTransferCount; LARGE_INTEGER qWriteTransferCount; LARGE_INTEGER qOtherTransferCount;}SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;BOOL GetNtQuerySystemInfoBuffer( IN DWORD SystemInformationClass, void** lpBuffer ){ BOOL bSuccess = FALSE; HMODULE hNtDll = NULL; Fun_NtQuerySystemInformation NtQuerySystemInformation = NULL; ULONG cbBuffer = 0x0001; ULONG ulNeedBytes = cbBuffer; int nloopcount = 0; ULONG nstatus = 0; void* pBuffer = NULL; *lpBuffer = NULL; hNtDll = GetModuleHandle( L"ntdll.dll" ); do { if( NULL == hNtDll ) { // 加載ntdll失敗,返回FALSE break; } NtQuerySystemInformation = ( Fun_NtQuerySystemInformation ) GetProcaddress( hNtDll, "NtQuerySystemInformation" ); if ( NULL == NtQuerySystemInformation ) { // 獲取導出函數NtQuerySystemInformation失敗,返回FALSE break; } // 預分配的空間大小,先分配一個小空間,理論上使用這個空間去獲取信息是失敗的 cbBuffer = 0x0001; ulNeedBytes = cbBuffer; // 分配內存用于保存進程信息,只分配不釋放,由外部釋放 *lpBuffer = malloc( cbBuffer ); nloopcount = 0; do { // 為了防止無限循環,做個最大循環限制 if ( nloopcount > MAXLOOPCOUNT ) { break; } nloopcount++; if( NULL == *lpBuffer ) { // 分配內存失敗,返回FALSE break; } // 理論上,第一次執行這個函數會失敗的,因為分配的空間太小 nstatus = NtQuerySystemInformation( SystemInformationClass, *lpBuffer, cbBuffer, &ulNeedBytes ); if ( STATUS_INFO_LENGTH_MISMATCH == nstatus ) { // 理論上,第一次執行NtQuerySystemInformation后會進入這里進行內存的再次擴容 cbBuffer = ulNeedBytes; // 重新分配內存用于保存進程信息,只分配不釋放,由外部釋放 pBuffer = realloc( *lpBuffer, cbBuffer ); if (pBuffer != NULL) *lpBuffer = pBuffer; continue; } else if ( STATUS_SUCCESS == nstatus ) { // 成功 bSuccess = TRUE; break; } else { // 非內存大小分配不夠導致的錯誤 bSuccess = FALSE; break; } } while(1); } while( 0 ); if ( NULL != hNtDll ) { FreeLibrary( hNtDll ); hNtDll = NULL; } if ( FALSE == bSuccess ) { // 如果獲取信息失敗,則釋放分配的內存 if ( NULL != *lpBuffer ) { free( *lpBuffer ); *lpBuffer = NULL; } } return bSuccess;}
看到如上結構體,要是在Lua中用ffi去聲明,豈不是很崩潰!我們再看下填充數據的輔助函數
BOOL GetProcessFullInfo(lua_State *L){ BOOL bSuccess = FALSE; PSYSTEM_PROCESS_INFORMATION pInfo = NULL; int nloopcount = 0; do { LPVOID pBuffer = NULL; if ( FALSE == GetNtQuerySystemInfoBuffer( SystemProcessInformation, &pBuffer) ) { // 獲取失敗,直接返回,不用釋放pBuffer,因為GetZwQuerySystemInfoBuffer內部就釋放了 break; } if ( NULL == pBuffer ) { break; } pInfo = ( PSYSTEM_PROCESS_INFORMATION ) pBuffer; nloopcount = 0; lua_newtable( L ); while( NULL != pInfo ) { // 為了防止無限循環,做個最大進程數限制 if ( nloopcount > MAXPROCSSNUM ) { break; } nloopcount++; if( 0 == pInfo->dwNextEntryOffset ) { // 找到末尾最后一個了,就退出循環 break; } else { lua_pushinteger( L, pInfo->dwProcessId); ////////////////////////////////////////////////////////////////////////// lua_newtable( L ); lua_pushinteger( L, pInfo->dwInheritedFromUniqueProcessId ); lua_pushlstring( L, (char*)pInfo->ImageName.Buffer, pInfo->ImageName.Length ); lua_settable( L, -3 ); ////////////////////////////////////////////////////////////////////////// lua_settable( L, -3 ); // 利用偏移,找到下一個結構對象 pInfo = ( PSYSTEM_PROCESS_INFORMATION)( ( (PUCHAR) pInfo ) + pInfo->dwNextEntryOffset ); } } if ( NULL != pBuffer ) { free( pBuffer ); pBuffer = NULL; } bSuccess = TRUE; } while( 0 ); return bSuccess;}
該函數生成一個如同
struct PINFO{DWORD dwPPID; // 父進程IDwstring wstrProcessName; // 本進程名字};struct PINFOEX{DWORD dwPID; // 本進程IDPINFO Info;}list<pinfoex> PINFOLIST;</pinfoex>
的結果。
如果我們執行如此簡短的Lua腳本
fl = require "fl"allprocessinfo = fl.GetAllProcess()for PID,item in pairs(allprocessinfo) dofor PPID,PNAME in pairs(item) do print(PPID,PNAME)endend
將把我們系統中進程信息打印出來。
介紹Luajit嵌入Win32程序的文章講完了。
最后,該工程源碼都在以下鏈接:https://www.php.cn/link/3244ef4c2aeb17c03511c93cb43caef0 密碼:pknq