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;
}
Hook lua库函数时遇到的问题的更多相关文章
- [lua大坑]一个莫名其妙的lua执行时崩溃引出的堆栈大小问题
这是一个坑,天坑!如果不是我随手删除了一个本地变量,这个问题直到现在我应该也没有头绪. 首先,写了一个新的lua脚本,载入,执行.在执行的时候,出了这么一个莫名其妙的问题: EXC_BAD_ACCES ...
- lua库函数
这些函数都是Lua编程语言的一部分, 点击这里了解更多. assert(value) - 检查一个值是否为非nil, 若不是则(如果在wow.exe打开调试命令)显示对话框以及输出错误调试信息 col ...
- 【原创】lua编译时发现缺少readline库
编译lualua项目,其中用到了lua-5.1版本的源码,编译时提示缺少readline库,找不到readline/readline.h头文件等 发现系统中其实有安装readline库不过没有做链接和 ...
- 写lua时需要注意的地方
条件语句判断时,只有false和nil会导致判断为假,其他的任何值都为真. Lua 的字符串与编码无关: 它不关心字符串中具体内容. 标准 Lua 使用 64 位整数和双精度(64 位)浮点数, 但你 ...
- mac 升级EI Capitan后遇到c++转lua时遇到libclang.dylib找不到的错
升级EI Capitan后,打包lua脚本时,会报这个错: LibclangError: dlopen(libclang.dylib, 6): image not found. To provide ...
- 安装nginx环境(含lua)时遇到报错ngx_http_lua_common.h:20:20: error: luajit.h: No such file or directory的解决
下面是安装nginx+lua环境时使用的相关模块及版本,ngx_devel_kit和lua-nginx-module模块用的都是github上最新的模块.并进行了LuaJIT的安装. #Install ...
- Lua 5.1 参考手册
Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes 云风 译 www.codingno ...
- Lua 5.3 参考手册
转自:http://www.runoob.com/manual/lua53doc/manual.html 1 – 简介 Lua 是一门扩展式程序设计语言,被设计成支持通用过程式编程,并有相关数据描述设 ...
- lua 5.3 英文手册 google机器翻译版
LUA Lua 5.3参考手册作者:Roberto Ierusalimschy,Luiz Henrique de Figueiredo,Waldemar Celes 版权所有©2015-2018 Lu ...
随机推荐
- RxJava2学习笔记(3)
接上回继续,今天来学习下zip(打包)操作 一.zip操作 @Test public void zipTest() { Observable.zip(Observable.create(emitter ...
- fatal error C1859 意外的预编译头错误,只需重新运行编译器(转)
微软的建议 要解决此问题,请使用下列方法之一. http://support.microsoft.com/kb/976656/zh-cn 方法 1 禁用/analyze编译器选项,则它会被启用. 方法 ...
- python BeautifulSoup的简单使用
官网:https://www.crummy.com/software/BeautifulSoup/bs4/doc/ 参考:https://www.cnblogs.com/yupeng/p/336203 ...
- 浅谈压缩感知(二十一):压缩感知重构算法之正交匹配追踪(OMP)
主要内容: OMP的算法流程 OMP的MATLAB实现 一维信号的实验与结果 测量数M与重构成功概率关系的实验与结果 稀疏度K与重构成功概率关系的实验与结果 一.OMP的算法流程 二.OMP的MATL ...
- linux修改目录为可读写
发布到linux上遇到的问题, 上传目录没有写入权限 新建目录 mkdir /guangkuo/html/portal/images/upload 改为可读写 chmod -R a+w /guangk ...
- 从源码编译InfluxDB
操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 go语言安装参考: http://www.cnblogs.com ...
- 基于Centos搭建Maven 安装与使用
CentOS 7.2 64 位操作系统 安装 Maven Maven 简介 Apache Maven 是一个软件项目管理及自动构建工具,由 Apache 软件基金会所提供.基于项目对象模型(缩写:PO ...
- 海量数据拆分到nosql系统的一种方案
获取某用户的好友最新动态. 我们大体上来说先按照用户ID将用户的好友一致性哈希到几个mongodb集群,然后把用户的最新信息也存储到mongodb中.然后利用消息系统保持数据库中的数据和mongdb中 ...
- golang ---tcmalloc浅析
总体结构 在tcmalloc内存管理的体系之中,一共有三个层次:ThreadCache.CentralCache.PageHeap,如上图所示.分配内存和释放内存的时候都是按从前到后的顺序,在各个层次 ...
- 第三部分:Android 应用程序接口指南---第二节:UI---第三章 菜单
第3章 菜单 在许多不同类型的应用中,菜单通常是一种用户界面组件.为了提供给用户提供熟悉且一致的体验,你需要使用菜单API来展示用户动作和你Activity中的其他选项. 从安卓3.0系统(API l ...