0%

C++ Hook

C++ Hook

LD_PRELOAD 预加载hook so

通过LD_PRELOAD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if( strcmp(argv[1], "password") )
{
printf("Incorrect password\n");
}
else
{
printf("Correct password\n");
}
return 0;
}
1
gcc -o main main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

typedef int(*Fun)(const char*, const char*);

int strcmp(const char* s1, const char* s2)
{
static void* handle = NULL;
static Fun org_strcmp = NULL;

if(!handle)
{ //解析得到真实的strcmp函数
handle = dlopen("libc.so.6", RTLD_LAZY);
org_strcmp = (Fun)dlsym(handle, "strcmp");
}
//做我们想做的
printf("Hacked by way of ld_preload\n\n\n");
//完成真实地功能
return org_strcmp(s1, s2);
}
1
gcc -fPIC -shared -o libmyhook.so my_hook.c -ldl
1
LD_PRELOAD=./libmyhook.so ./main password 

LD_PRELOAD

LD_PRELOAD,是个环境变量,用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序为LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib>/usr/lib。程序中我们经常要调用一些外部库的函数,以rand为例,如果我们有个自定义的rand函数,把它编译成动态库后,通过LD_PRELOAD加载,当程序中调用rand函数时,调用的其实是我们自定义的函数。

dlsym参数

RTLD_DEFAULT是在当前库中查找函数,而RTLD_NEXT则是在当前库之后查找第一次出现的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

typedef int(*Fun)(const char*, const char*);

int strcmp(const char* s1, const char* s2)
{
static Fun org_strcmp = NULL;

org_strcmp = (Fun)dlsym(RTLD_NEXT, "strcmp");
//做我们想做的
printf("Hacked by way of ld_preload\n\n\n");
//完成真实地功能
return org_strcmp(s1, s2);
}

RTLD_NEXT找不到

1
#define _GNU_SOURCE

dlopen参数

RTLD_LAZY:在dlopen返回前,对于动态库中存在的未定义的变量(如外部变量extern,也可以是函数)不执行解析,就是不解析这个变量的地址。
RTLD_NOW:与上面不同,他需要在dlopen返回前,解析出每个未定义变量的地址,如果解析不出来,在dlopen会返回NULL,错误为:   : undefined symbol: xxxx…….
RTLD_GLOBAL:它的含义是使得库中的解析的定义变量在随后的其它的链接库中变得可以使用。

Android Hook

ZygotePreload 在app创建前预加载一些库(java 或native),比如使用 LD_PRELOAD

1
2
3
4
5
6
7
8
9
10
11
android:zygotePreloadName="io.github.zauther.demo.ZygotePreload"

@Keep
@RequiresApi(api = Build.VERSION_CODES.Q)
public class ZygotePreload implements android.app.ZygotePreload{
@Override
public void doPreload(@NonNull ApplicationInfo appInfo) {
System.out.println("==TEST==");
Log.i("==TEST==","ZygotePreload:"+appInfo.dataDir);
}
}

通过符号表Hook对应的方法

定义函数指针 lookup

lookup需要跟实际调用方法参数保持一致

1
void* (*lookup)(void*, char const*, unsigned int);

找到so对应位置

  • fdlopen 打开so库
  • fdlsym 找到符号表位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
std::string soPath = "/system/";
if (get_api_level() > 29) {
soPath = "/apex/com.android.art/";
}else if (get_api_level() == 29) {
soPath = "/apex/com.android.runtime/";
}

void *handle = fdlopen((soPath + "lib/libart.so").c_str(), RTLD_LAZY);
if (handle == nullptr) {
handle = fdlopen((soPath + "lib64/libart.so").c_str(), RTLD_LAZY);
if (handle == nullptr) {
return false;
}
lookup = (void* (*)(void* prt, char const*, unsigned int))fdlsym(handle, "_ZN3art10ClassTable6LookupEPKcm");
if (lookup == nullptr) {
return false;
}
} else {
lookup = (void* (*)(void* prt, char const*, unsigned int))fdlsym(handle, "_ZN3art10ClassTable6LookupEPKcj");
if (lookup == nullptr) {
return false;
}
}
return true;

函数调用

1
(*lookup)((void *)classTableAddr, descriptor.c_str(), descriptor_hash);

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (jsize i = 0; i < env->GetArrayLength(classNames); i++) {
jstring className = (jstring)env->GetObjectArrayElement(classNames, i);
const char* name = env->GetStringUTFChars(className, nullptr);
if (name == nullptr) {
continue;
}
env->DeleteLocalRef(className);
std::string descriptor(DotToDescriptor(name));
const size_t descriptor_hash = ComputeModifiedUtf8Hash(descriptor.c_str());
void* addr = (*lookup)((void *)classTableAddr, descriptor.c_str(), descriptor_hash);
if (addr == nullptr) {
continue;
}
vector.push_back(name);
}