Hook lua庫函數時遇到的問題


最近在為distri.lua實現一個lua調試系統,有一個簡單的需求,lua導入一個文件的時候,將這個文件的文件名記錄下來,
以方便調試器在設置斷點的時候判斷是否一個合法的文件.

lua導入文件是通過luaL_loadfilex實現的,一個簡單的思路就是修改luaL_loadfilex,在luaL_loadfilex中調用一個外部定義的函數將導入的文件名傳給那個外部函數,由它記錄下來.
但這種侵入式的方案,除非在逼不得已的情況下不應該使用.

另一個思路是hook luaL_loadfilex,在運行時用另外一個函數替換luaL_loadfilex,由這個替換函數去記錄下需要的信息
然后在跳轉回原luaL_loadfilex的執行流程上.

與是我從decode中提取除了Hook.h,Hook.c稍加調整以適應Linux系統.

hook的原理很簡單:

    * 首先,使用mprotect將luaL_loadfilex所在代碼段設置為可讀/可寫/可執行,以避免在修改代碼時出現段訪問異常
    
    * 之后需要把luaL_loadfilex最前面的一段指令替換成一個跳轉指令,跳轉到替換函數中去執行.為了在替換函數執行
    完之后可以正確的回到luaL_loadfilex的正常執行路徑上,需要把luaL_loadfilex中被替換部分的指令保存下來,然后
    在后面再添加一條跳轉指令,調到luaL_loadfilex后面的執行路徑去.

被替換掉的指令布局和執行流程如下圖:

    luaL_loadfilex:
             jmp hook
             -------------
             其余指令  <-------------------------------------------------|
                                                                         |
    hook:                                                                |
            執行必要的記錄                                               |
            --保存的代碼----                                             |
            luaL_loadfilex中被替換的指令                                 |
            jmp其余指令--------------------------------------------------|                      

HookFunction實現如下:

    void* HookFunction(void* function, void* hook)
    {
    
        // Don't allow rehooking of the same function since that screws things up.
    
        assert(!GetIsHooked(function, hook));
    
        if (GetIsHooked(function, hook))
        {
            return NULL;
        }
    
        // Missing from windows.h
        //#define HEAP_CREATE_ENABLE_EXECUTE 0x00040000
    
        // Jump instruction is 5 bytes.
        const int jumpSize = 5;
    
        // Compute the instruction boundary so we don't override half an instruction.
        int boundary = GetInstructionBoundary(function, jumpSize);
        
        size_t pagesize = sysconf(_SC_PAGE_SIZE);
        unsigned char* trampoline = NULL;
        trampoline = (unsigned char*)/*aligned_alloc*/memalign(pagesize,pagesize);
    	if(mprotect(trampoline, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC)){
    		free(trampoline);
    		return NULL;
    	}
    
        // Copy the original bytes to the trampoline area and append a jump back
        // to the original function (after our jump).
    
        memcpy(trampoline, function, boundary);
        AdjustRelativeJumps(trampoline, boundary, ((unsigned char*)function) - trampoline);
    
        WriteJump(trampoline + boundary, ((unsigned char*)function) + boundary);
    
    	void *ptr = (void*)(((size_t)function/pagesize)*pagesize);
        // Give ourself write access to the region.
        if(!mprotect(ptr, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC))
        {
            // Fill the area with nops and write the jump instruction to our
            // hook function.
            memset(function, 0x90, boundary);
            WriteJump(function, hook);
    
            // Restore the original protections.
            //VirtualProtect(function, boundary, protection, &protection);
    		mprotect(ptr, pagesize, PROT_READ|PROT_EXEC);
            // Flush the cache so we know that our new code gets executed.
            //FlushInstructionCache(GetCurrentProcess(), NULL, NULL);
    
            return trampoline;
            //return 0;
        
        }    
    
    	free(trampoline);
    	return NULL;
    	
        //return -1;        
    }

本以為一切就這樣結束了,運行程序的時候,正確的進入了替換函數,但在執行完記錄操作要回到luaL_loadfilex后續執行流程的時候程序
掛了,報段訪問異常.

為啥呢,我們看下WriteJump的實現:

/**
 * Writes a relative jmp instruction.
 */
void WriteJump(void* dst, void* address)
{
    
    unsigned char* jump = (unsigned char*)(dst);

    // Setup a jump instruction.
    jump[0] = 0xE9;
    *((unsigned long*)(&jump[1])) = (unsigned long)(address) - (unsigned long)(dst) - 5;

}

使用的是E9跳轉指令+4字節的立即數做相對rip計數器的跳轉.這在32位程序下這是沒問題的,因為trampoline和luaL_loadfilex的位移差必定
在4字節的范圍內.但我程序的運行環境是64位的,這個時候程序就出問題了, trampoline和luaL_loadfilex的位移差已經超過4個字節.這就導致
[jmp其余指令]跳轉到到錯誤的地址上了.

如何解決這個問題:

*   用FF指令做跳轉,但這個方案要修改的地方就多了,除了` WriteJump`,還有`AdjustRelativeJumps`並且還會導致指令長度變長.

*   用static數據區保存luaL_loadfilex中被替換的指令,使得位移差被控制在4字節以內.

針對我的需求,我選擇了方案2,下面是修改過的HookFunction和WriteJump以及使用示例:

    /**
     * Writes a relative jmp instruction.
     */
    void WriteJump(void* dst, void* address)
    {
        
        unsigned char* jump = (unsigned char*)(dst);
    
        // Setup a jump instruction.
        jump[0] = 0xE9;
        *((unsigned int*)(&jump[1])) = (unsigned int)((unsigned long)(address) - (unsigned long)(dst) - 5);
    
    }

    void* HookFunction(void* function, void* hook,void *saveaddr,size_t saveaddr_size)
    {
    
        // Don't allow rehooking of the same function since that screws things up.
    
        assert(!GetIsHooked(function, hook));
    
        if (GetIsHooked(function, hook))
        {
            return NULL;
        }
    
        // Missing from windows.h
        //#define HEAP_CREATE_ENABLE_EXECUTE 0x00040000
    
        // Jump instruction is 5 bytes.
        const int jumpSize = 5;
    
        // Compute the instruction boundary so we don't override half an instruction.
        int boundary = GetInstructionBoundary(function, jumpSize);
        
        if(saveaddr_size < (size_t)boundary) return NULL;
        
        size_t pagesize = sysconf(_SC_PAGE_SIZE);
    	if(mprotect(saveaddr, boundary, PROT_WRITE|PROT_READ|PROT_EXEC)){
    		//free(trampoline);
    		return NULL;
    	}
    
        // Copy the original bytes to the trampoline area and append a jump back
        // to the original function (after our jump).
    
        memcpy(saveaddr, function, boundary);
        AdjustRelativeJumps(saveaddr, boundary, ((unsigned char*)function) - (unsigned char*)saveaddr);
    
        WriteJump(saveaddr + boundary, ((unsigned char*)function) + boundary);
    
    	void *ptr = (void*)(((size_t)function/pagesize)*pagesize);
        // Give ourself write access to the region.
        if(!mprotect(ptr, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC))
        {
            // Fill the area with nops and write the jump instruction to our
            // hook function.
            memset(function, 0x90, boundary);
            WriteJump(function, hook);
    
            // Restore the original protections.
            //VirtualProtect(function, boundary, protection, &protection);
    		mprotect(ptr, pagesize, PROT_READ|PROT_EXEC);
            // Flush the cache so we know that our new code gets executed.
            //FlushInstructionCache(GetCurrentProcess(), NULL, NULL);
    
            return saveaddr;
            //return 0;
        
        }    
    
    	return NULL;
    	
        //return -1;
    
    }

////使用示例

    int (*ori_luaL_loadfilex)(lua_State *L, const char *filename,const char *mode) = NULL;

    int my_luaL_loadfilex(lua_State *L, const char *filename,const char *mode){
    	printf("%s\n",filename);//記錄導入的lua文件,供調試器使用
    	return ori_luaL_loadfilex(L,filename,mode);
    }
    
    static char luaL_loadfilex_buf[4096] __attribute__((aligned(4096)));
    
    int debug_init(){
    	ori_luaL_loadfilex = HookFunction(luaL_loadfilex,my_luaL_loadfilex,luaL_loadfilex_buf,4096);
    	if(!ori_luaL_loadfilex){
    		return -1;
    	}	
    	return 0;
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM