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 ...
随机推荐
- The op amp module
- Java知识回顾 (6)正则表达式、方法、文件与异常
一.正则表达式 正则表达式定义了字符串的模式. 正则表达式可以用来搜索.编辑或处理文本. 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别. Java 正则表达式和 Perl 的是最为相似 ...
- Java中有哪些语法糖?
不要你写汇编,Java句句是糖 不能同意上面的这句话,要说为什么,首先要定义下面要讲的“语法糖”. 语法糖指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,并没有给语言添加什么新东西,但是 ...
- AMBA APB总线
前面分析了AHB总线协议.接下来分析APB总线协议. (一) APB总线接口: PCLK APB总线时钟. PRESETn APB总线复位.低有效. PADDR 地址总线. PSELx 从设备选择. ...
- mac下最简单的删除node方法是什么
sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}
- 学习Spring-Session+Redis实现session共享
1.添加依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId& ...
- REP开发技巧
这个基本包含了所有的特殊情况 wrap 换行 title显示在每页的固定位置, 第一页需要特殊处理 <DATA> <PDF_TITLE Font="5" Size ...
- 使用yocs_velocity_smoother对机器人速度进行限制
yocs_velocity_smoother是一个速度.加速度限制器,用来防止robot navigation的速度/转速过快,加速度/快减速过大.Bound incoming velocity me ...
- redis 频率限制
方式1: $redis = new Redis(); //以自然时间控制 一自然分钟内超过100次进行限制, 屏蔽多久的时间必须为计数key时间的倍数 $key = 'xxxx'.date('Y-m- ...
- [转]MyEclipse内存不足问题
1.修改eclipse.ini 在Myeclipse安装目录下G:\MyEclipse8.5\Genuitec\MyEclipse 8.5有一个myeclipse.ini配置文件,设置如下: -vma ...