小心环境变量-浅谈LD_PRELOAD

//weibo: @少仲

0x0 前言

  在Windows平台,各种Hook技术已经被玩的天花乱坠.软件为了优先获得系统的控制权,都使用了各种Hook技术.常见的SSDT Hook/Inline Hook/IRP Hook/IDT Hook/IAT Hook等等,而Linux平台由于架构不同,Hook实现起来比较困难,但是经过研究,也出现了一些可行的Hook方案.


0x1 LD_PRELOAD原理

  和Windows一样,Linux平台的程序也分为静态链接和动态链接两种.静态链接是把所有函数都打包编译到程序当中,而动态链接没有把函数编译到可执行文件当中去,而是在程序运行时动态的载入动态库.程序需要调用的时候,会查找动态链接库的位置,以便从动态库中查找函数的地址给程序使用.系统提供动态链接库可以使多个程序公用相同的代码,节省了磁盘空间,更节省了程序员的效率.同时可以不再修正程序的情况下,升级动态库里的函数就可以完成升级.然而如果动态库不是开发人员所写,而是别人所写的代码,那么程序的流程有可能会被修改.或者说如果我们想要调用别人的动态库,也可以使用这样的方法完成Hook.LD_PRELOAD的利用就这样应运而生,它可以直接影响程序运行时的动态链接.只要通过它抢先加载想要加载的动态链接库,程序在运行时就会优先加载这个动态链接库,从而覆盖正常的动态链接库.那么在Android系统下也是如此吗?下面看看代码分析.


0x2 代码分析

摘自AOSP\bionic\linker\linker.cpp (节选了关键代码)

//初始化环境变量
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也是确实存在的.


0x3 编码测试

准备编写一个校验的密码的小程序,通过strcmp来比较字符串,如果正确则打印registion,如果错误就返回invalid.之后通过hook strcmp函数,输入任何字符串都会返回成功.
//test.c
#include <stdio.h>
#include <string.h>
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 <stdio.h>
#include <dlfcn.h>

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)

0x4 测试

测试环境:
MI 4
4.4.4
msm8974
Linux version 3.4.0-gbb551a9-01034-g7a5f473 (builder@taishan) (gcc version 4.7 (GCC) ) 
#1 SMP PREEMPT Tue Aug 12 21:37:08 CST 2014

将test程序和libhook.so同时push到手机当中,先正常执行test程序,如果字符串不是checkkey就会返回invalid,执行export LD_PRELOAD="/data/local/tmp/libhook.so"之后再运行test程序,输入任何字符串都会返回成功.

0x5 如何避免被Hook

(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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章