在Windows平台,各种Hook技术已经被玩的天花乱坠.软件为了优先获得系统的控制权,都使用了各种Hook技术.常见的SSDT Hook/Inline Hook/IRP Hook/IDT Hook/IAT Hook等等,而Linux平台由于架构不同,Hook实现起来比较困难,但是经过研究,也出现了一些可行的Hook方案.
和Windows一样,Linux平台的程序也分为静态链接和动态链接两种.静态链接是把所有函数都打包编译到程序当中,而动态链接没有把函数编译到可执行文件当中去,而是在程序运行时动态的载入动态库.程序需要调用的时候,会查找动态链接库的位置,以便从动态库中查找函数的地址给程序使用.系统提供动态链接库可以使多个程序公用相同的代码,节省了磁盘空间,更节省了程序员的效率.同时可以不再修正程序的情况下,升级动态库里的函数就可以完成升级.然而如果动态库不是开发人员所写,而是别人所写的代码,那么程序的流程有可能会被修改.或者说如果我们想要调用别人的动态库,也可以使用这样的方法完成Hook.LD_PRELOAD的利用就这样应运而生,它可以直接影响程序运行时的动态链接.只要通过它抢先加载想要加载的动态链接库,程序在运行时就会优先加载这个动态链接库,从而覆盖正常的动态链接库.那么在Android系统下也是如此吗?下面看看代码分析.
//初始化环境变量
linker_env_init(args);
//如果执行了setuid()函数,则LD_PRELOAD无效
if (get_AT_SECURE())
{
nullify_closed_stdio();
}
debuggerd_init();
const char* LD_DEBUG = linker_env_get("LD_DEBUG");
if (LD_DEBUG != NULL)
{
gLdDebugVerbosity = atoi(LD_DEBUG);
}
const char* ldpath_env = NULL;
const char* ldpreload_env = NULL;
//如果没有program_is_setuid为假,LD_PRELOAD就生效了
if (!get_AT_SECURE())
{
ldpath_env = linker_env_get("LD_LIBRARY_PATH");
//LD_PRELOAD赋值给了ldpreload_env
ldpreload_env = linker_env_get("LD_PRELOAD");
}
//...中间部分代码省略
parse_LD_LIBRARY_PATH(ldpath_env);
parse_LD_PRELOAD(ldpreload_env);
--------------------------------------------------------------------------
#define LDPRELOAD_BUFSIZE 512
#define LDPRELOAD_MAX 8
static char gLdPreloadsBuffer[LDPRELOAD_BUFSIZE];
static void parse_path(const char* path, const char* delimiters, const char** array, char* buf, size_t buf_size, size_t max_count)
{
if (path == NULL)
{
return;
}
size_t len = strlcpy(buf, path, buf_size);
size_t i = 0;
char* buf_p = buf;
while (i < max_count && (array[i] = strsep(&buf_p, delimiters)))
{
if (*array[i] != '\0')
{
++i;
}
}
if (i > 0 && len >= buf_size && buf[buf_size - 2] != '\0')
{
array[i - 1] = NULL;
}
else
{
array[i] = NULL;
}
}
static void parse_LD_PRELOAD(const char* path)
{
parse_path(path, " :", gLdPreloadNames,gLdPreloadsBuffer, sizeof(gLdPreloadsBuffer), LDPRELOAD_MAX);
}
LD_PRELOAD中所有的库文件路径是以冒号":"分隔的,在parse_LD_PRELOAD()函数中它们被分隔并保存到了一个全局的缓冲区gLdPreloadsBuffer中,这个缓冲区被设定为512个字节,LD_PRELOAD环境变量中指定的库的个数最多为8个.看到这里也已经很清楚了,在Android系统里面LD_PRELOAD也是确实存在的.
//test.c
#include
#include
int check_func(const char* str)
{
char key[] = "checkkey";
return (!strcmp(key,str));
}
int main(int argc,char* argv[])
{
int ret = -1;
if(argc < 2)
{
printf("please input key.\n");
return ret;
}
if (!check_func(argv[1]))
{
printf("the key is invalid.\n");
return ret;
}
else
{
printf("thanks for registion.\n");
return 0;
}
}
//hook.c
#include
#include
void* get_real_addr(const char* s1,const char* s2)
{
void* lib = dlopen("libc.so",RTLD_NOW | RTLD_GLOBAL);
void* symbol = NULL;
if (lib == NULL)
{
fprintf(stderr, "dlopen failed...\n");
return NULL;
}
symbol = dlsym(lib,"strcmp");
fprintf(stderr, "the real strcmp addr : %p\n", symbol);
dlclose(lib);
return symbol;
}
int strcmp(const char* str1,const char* str2)
{
fprintf(stderr,"str1 = %s, str2 = %s\n",str1,str2);
get_real_addr(str1,str2);
//永久返回0
return 0;
}
//Android.mk
LOCAL_PATH := $(call my-dir)
# module hook
include $(CLEAR_VARS)
LOCAL_MODULE := hook
LOCAL_SRC_FILES := hook.c
include $(BUILD_SHARED_LIBRARY)
# module test
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
include $(BUILD_EXECUTABLE)
(1).静态链接,把代码都静态链接入可执行程序.
(2).通过设置执行文件的setgid / setuid标志.在有SUID权限的执行文件,系统会忽略LD_PRELOAD环境变量.
也就是说,如果你有以root方式运行的程序,最好设置上SUID权限.
(3).在程序的开始检测LD_PRELOAD
int verify_ld_preload(void)
{
int ret = 0;
char env_name[] = "ld_preload";
char *ld_preload = getenv(env_name);
if (ld_preload != NULL)
{
ret = -1;
}
return ret;
}