在windows程序中嵌入Lua腳本引擎–編寫自己的Lua庫

在《在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庫的聲明方式。

  1. 在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);
  1. 在lib_init.c中,將我們的庫名字和打開庫的名字綁定
……  { LUA_JITLIBNAME,luaopen_jit },  { LUA_FLLIBNAME, luaopen_fl },  { NULL,NULL }
  1. 在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;}
  1. 將lib_fl.c加入《在windows程序中嵌入Lua腳本引擎–使用VS ide編譯Luajit腳本引擎》中介紹的Lua工程。

  2. 修改《在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
  1. 在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便可以使用簡單的方法調用獲取系統版本了。

在windows程序中嵌入Lua腳本引擎–編寫自己的Lua庫

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

將把我們系統中進程信息打印出來。

在windows程序中嵌入Lua腳本引擎–編寫自己的Lua庫

介紹Luajit嵌入Win32程序的文章講完了。

最后,該工程源碼都在以下鏈接:https://www.php.cn/link/3244ef4c2aeb17c03511c93cb43caef0 密碼:pknq

? 版權聲明
THE END
喜歡就支持一下吧
點贊7 分享