首页
社区
课程
招聘
[求助]android平台通过dlsym来hook模块方法的原理是?
发表于: 2020-3-20 20:14 5251

[求助]android平台通过dlsym来hook模块方法的原理是?

2020-3-20 20:14
5251
新人求助啊,希望有大神能不吝帮忙解答,感谢感谢~~~

问题是这样的:本人最近对android平台的hook有一些需求,看了各种inline hook/got表hook/plt表hook的原理,对hook原理和elf结构也有了大致了解,前一段时间突然想到了一个前同事的代码里用到了一个简单技术就实现了对调用的模块里的某函数的hook,然后在android源码里也找到不少这种hook方式的使用,对其原理百思不得其解,特来这个大神聚集的地方请教一番,下面是这个hook方式的流程和代码示例,最后提出我的疑问和一些思考:

hook的流程是这样的:
1. 首先使用dlsym来获取目标函数的地址指针,对地址进行备份(放入某函数指针内)
2. 对地址指针指向的地方填入写好的跳转方法的地址
3. 模块内部调用函数时就首先调用跳转方法
4. 在跳转方法内再次调用备份的原函数的指针

代码类似这样 (为了大家方便找代码,贴了原生的代码做示例):

跳转方法:
external/e2fsprogs/lib/ss/listen.c

char **ss_rl_completion(const char *text, int start,
			int end __SS_ATTR((unused)))
{
	if ((start == 0) && current_info->rl_completion_matches)
		return (*current_info->rl_completion_matches)
			(text, cmd_generator);
	return 0;
}

hook方法:
external/e2fsprogs/lib/ss/get_readline.c

void ss_get_readline(int sci_idx)
{
	void	*handle = NULL;
...
	libpath = ss_safe_getenv("SS_READLINE_PATH");
	if (!libpath)
		libpath = DEFAULT_LIBPATH;
...
	for (cp = tmp; cp; cp = next) {
...
		if ((handle = dlopen(cp, RTLD_NOW))) {
			/* printf("Using %s for readline library\n", cp); */
			break;
		}
	}
...
	info->rl_completion_matches = (char **(*)(const char *,
				    char *(*)(const char *, int)))
		dlsym(handle, "rl_completion_matches");
	if ((t = dlsym(handle, "rl_readline_name")) != NULL)
		*t = info->subsystem_name;
	if ((completion_func =
	     dlsym(handle, "rl_attempted_completion_function")) != NULL)
		*completion_func = ss_rl_completion;
	info->readline_shutdown = ss_release_readline;
}

重点关注这个:
*completion_func = ss_rl_completion;

很显然这个不属于 inline hook / got表hook / plt表hook 这三种的任意一种。

看了linker里dlsym的实现,其实就是通过符号表获取函数首地址偏移量,然后首地址偏移量加上elf被加载进内存时的虚拟地址,组成这个函数在内存里的虚拟地址,我理解这个地址应该是在代码段的,只有只读权限。

bionic/linker/linker_soinfo.h

  ElfW(Addr) resolve_symbol_address(const ElfW(Sym)* s) const {
    if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) {
      return call_ifunc_resolver(s->st_value + load_bias);
    }

    return static_cast<ElfW(Addr)>(s->st_value + load_bias);
  }

我的疑问有二:


1. 为啥可以修改函数地址指向的地方,这个地方难道不是在代码段吗?

2. 修改了以后为啥备份的原函数地址还可以调用原函数,代码段原函数的首地址指向的地方的内容不是已经改成跳转方法的首地址了吗?


希望有大神能帮忙解答我的疑问,已经被困扰好久了,感觉要被逼疯。。。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-3-20 22:34 被andwei编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (20)
雪    币: 15933
活跃值: (7178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
多嘴回复一下。
你自己说对hook有大致了解,但是看了你的提问,感觉你对hook还没入门。你了解的inline hook是什么样子?
最后于 2020-3-21 11:18 被tDasm编辑 ,原因:
2020-3-21 11:17
1
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
难道没有人用过或者了解这种hook方式吗?Android源码里可是好多啊
2020-3-21 11:18
0
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
tDasm 多嘴回复一下。你自己说对hook有大致了解,但是看了你的提问,感觉你对hook还没入门。你了解的inline&nbsp;hook是什么样子?
@tDasm 是啊,我还没有入门呢,只是了解了一些hook原理。肯指教都是我的老师。我理解的inline hook应该是首先备份目标函数N字节的指令,准备一段指令和这个长度相同,指令主要作用是跳转至跳板函数的跳转指令,然后mprotect 修改目标函数所在页的写权限,修改目标函数开始N字节为跳转指令,达到hook的目的。

在跳板函数里call原函数的话,先进行参数准备,然后call备份的指令,然后跳转到目标函数后续的指令地址。

这帖子里提到的和这个方法想去甚远,我的理解是这种dlsym获取函数指针,并修改指针指向的值的方式就是修改代码段,既没有看到修改内存写权限,也没有看到准备跳转指令,原函数首地址内容被替换后,再call备份的函数指针应该无法执行原函数的。

跳板函数的首地址只是个地址,并不是跳转指令,作为指令覆盖了原函数头部,模块跳转到原函数执行时,pc走到原函数头的代码段时把一个地址号当指令执行,应该就跑飞了,也不可能跳转到跳板函数啊, 实在无法理解。

还望大佬指点一二
最后于 2020-3-21 12:24 被andwei编辑 ,原因:
2020-3-21 11:54
0
雪    币: 15933
活跃值: (7178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
老师教你1十1=2,别人写成1+3-3+1=2,你就不理解了?
1.如果代码段本来就可写,是不是不需要调用mprotect修改内存为可写?或者在其它位置调用了mprotect使整过代码段都可写,那么在hook某函数就不必再调用了?
2、hook如果要调用原函数才需要保存原函数入口代码n字节并加跳转到原函数n+1处执行。如果是替换原函数呢?是不是就不需要跳转到原函数了?
你是德国人?
最后于 2020-3-21 14:40 被tDasm编辑 ,原因:
2020-3-21 14:37
0
雪    币: 6573
活跃值: (3993)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
6
可以参考https://bbs.pediy.com/thread-258239.htm
2020-3-21 15:21
1
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
tDasm 老师教你1十1=2,别人写成1+3-3+1=2,你就不理解了?1.如果代码段本来就可写,是不是不需要调用mprotect修改内存为可写?或者在其它位置调用了mprotect使整过代码段都可写,那么在h ...
我可以肯定的告诉你,我说的这种hook没有调用mprotect,因为我copy了那段类似:
completion_func =dlsym(handle, "rl_attempted_completion_function")
*completion_func = ss_rl_completion;
这个逻辑的代码到我的项目里,完美hook,没有调用mprotect。并且我的hook是需要调用原函数的,备份了原函数的地址后,可以用来直接调用原函数。可能你没有用过这种方式。我去仔细研究研究版主的回复吧。
2020-3-21 17:08
0
雪    币: 15933
活跃值: (7178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
你直接把so放上来,让大家学习一下这么高明的hook方法。
凭一张嘴谁信?拿出so是最好的实证
2020-3-21 17:32
0
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
哥们儿,你觉得我是闲的没事来骗人的吗?大家都是成年人,好好说话对大家都好不是。家里没有编译环境,公司项目的结果也不能放出来。我把同事那段代码改改英文单词,避免公司机器人扫描。代码是公司sdk hook artmethod后禁止被hook的方法再被jit编译的。其实sdk里是有一套inline hook的,但是唯独这个用的是这种方式。改天我把这个模块编译一个与公司无关的so放出来。 下面是代码:
#include <dlfcn.h>

#define DEF_ART_JIT_COMPILE_M_NAME "_ZN3art3jit3Jit19jit_compile_method_E"

typedef bool (*PJitCompMethod)(void*, void* pArtMethod, void *pThread, bool osr);
static PJitCompMethod _PJitCompMethod = NULL;

bool doJitCompHook(PJitCompMethod pCompileM, PJitCompMethod* ppOldCompileM ) {
...
    void *libart = dlopen("libart.so", RTLD_LAZY | RTLD_GLOBAL);

    PJitCompMethod * ppcompile_m =  (PJitCompMethod*)dlsym(libart, DEF_ART_JIT_COMPILE_M_NAME);
...
    *ppOldCompileM = *ppcompile_m;
    *ppcompile_m = pCompileM;

    dlclose(libart);

    return true;
}

static bool hookJitCompileMethodJmp(void * pdata, void* pArtMethod, void *pThread) {
...
    if (needjit) {
        return _PJitCompMethod(pdata, pArtMethod, pThread);
    }
...
    return true;
}
    doJitCompHook((PJitCompMethod)hookJitCompileMethodJmp, &_PJitCompMethod);

最后于 2020-3-24 15:02 被andwei编辑 ,原因:
2020-3-21 18:11
0
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
LowRebSwrd 可以参考https://bbs.pediy.com/thread-258239.htm
非常感谢版主,我再写个代码调试调试。
2020-3-21 18:18
0
雪    币: 15933
活跃值: (7178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
前面还以为你hook没入门,看了你后面贴出来的代码,你c都没入门?
 *ppOldCompileM = *ppcompile_m;
    *ppcompile_m = pCompileM;
显然那个dlsym返回不是函数入口地址,而是存放函数地址的指针。而且这哪是inline hook?这相当于hook出口表。
https://bbs.pediy.com/thread-255671.htm
最后于 2020-3-21 20:33 被tDasm编辑 ,原因:
2020-3-21 20:32
0
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
tDasm 前面还以为你hook没入门,看了你后面贴出来的代码,你c都没入门?&nbsp;*ppOldCompileM&nbsp;=&nbsp;*ppcompile_m;&nbsp ...
我从没说这种方式是inline hook,是你误以为我说的是。
最后于 2020-3-21 22:30 被andwei编辑 ,原因:
2020-3-21 20:50
0
雪    币: 15933
活跃值: (7178)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
hook只有二种:
1、inline hook直接修改函数入口代码或函数内某处代码跳转到自己代码。
2、地址替换。包含入口表地址替换、出口表地址替换、结构体内地址替换等。这类hook最简单但不一定有效,不通过地址表的调用hook不到。
相当于hook出口表只是原理上一样,不等于hook出口表。
最后于 2020-3-21 22:07 被tDasm编辑 ,原因:
2020-3-21 22:07
0
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
tDasm hook只有二种:1、inline&nbsp;hook直接修改函数入口代码或函数内某处代码跳转到自己代码。2、地址替换。包含入口表地址替换、出口表地址替换、结构体内地址替换等。这类hook最简 ...
“dlsym 返回的是指向函数的指针 ” 就是这句,我又回去看了 linker 的 resolve_symbol_address,应该是走的上面 call_ifunc_resolver 那个逻辑,那个逻辑就是计算导出表的。多谢多谢~~
同事的hook工作正常,是因为那个函数被其他模块调用的,本模块内没有使用。
最后于 2020-3-21 22:34 被andwei编辑 ,原因:
2020-3-21 22:17
0
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
搞明白了,还是linker的代码没有看懂阿,结贴。这里有介绍 linker 的:656K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0L8X3u0D9L8$3N6K6i4K6u0W2j5$3!0E0i4K6u0r3K9r3g2A6P5r3W2S2L8X3N6Q4x3V1k6H3i4K6u0r3x3e0l9&6z5o6M7^5x3K6S2Q4x3X3g2Z5N6r3#2D9
最后于 2020-3-21 22:38 被andwei编辑 ,原因:
2020-3-21 22:35
1
雪    币: 2456
活跃值: (1574)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
找到符号地址并保存一份, dlsym指向的地址被替换, 导出表hook差不多
2020-3-22 12:54
0
雪    币: 149
活跃值: (213)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
typedef char **rl_completion_func_t(const char *text, int start, int end);
rl_completion_func_t * rl_attempted_completion_function;
看下他的原型就知道了!你想太多而已···
2020-4-12 16:32
0
雪    币: 7
活跃值: (268)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
楼主只是在向别人请教学习,但是这个tDasm的回复总感觉是一副高高在上的样子,还是楼主那句话 “大家都是成年人,好好说话”,
2020-4-12 23:09
1
雪    币: 330
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
bluth 楼主只是在向别人请教学习,但是这个tDasm的回复总感觉是一副高高在上的样子,还是楼主那句话 “大家都是成年人,好好说话”,
哈哈,好好说话利于交流,对大家都好。我的原则是只要没有攻击性的语言都客客气气来。
2020-4-13 14:20
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
不止
2020-9-19 21:25
0
游客
登录 | 注册 方可回帖
返回