0%

Riru-hide简单分析

被 Riru-hide 隐藏后的 so 会以匿名内存的形式存在。那么问题来了.
Riru-hide 是怎么做到匿名内存的,Riru-hide 还又什么隐藏手段呢?

关于 hide 的代码在 hide_utils.h 里.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef RIRU_HIDE_UTILS_H
#define RIRU_HIDE_UTILS_H

#include <cinttypes>
#include <pmparser.h>

namespace hide {

void PrepareMapsHideLibrary(); //加载libriruhide.so...

void HideFromMaps();//隐藏maps信息

void HideFromSoList();//从solist隐藏
}
#endif //RIRU_HIDE_UTILS_H

分别来看一看

PrepareMapsHideLibrary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void PrepareMapsHideLibrary() {
auto hide_lib_path = magisk::GetPathForSelfLib("libriruhide.so");

// load riruhide.so and run the hide
LOGD("dlopen libriruhide");
riru_hide_handle = DlopenExt(hide_lib_path.c_str(), 0); //加载libriruhide.so,这里DlopenExt的加载方法值得学习
if (!riru_hide_handle) {
LOGE("dlopen %s failed: %s", hide_lib_path.c_str(), dlerror());
return;
}
riru_hide_func = reinterpret_cast<riru_hide_t *>(dlsym(riru_hide_handle, "riru_hide")); //寻找riru_hide函数,之后会在HidePathsFromMaps里调用
if (!riru_hide_func) {
LOGE("dlsym failed: %s", dlerror());
dlclose(riru_hide_handle);
return;
}
}

这个函数仅加载 libriruhide.so 和填充 riru_hide_func, 无隐藏代码.

HideFromMaps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void HideFromMaps() {
auto self_path = magisk::GetPathForSelfLib("libriru.so");
std::set<std::string_view> names{self_path};
for (const auto &module : modules::Get()) {
if (module.supportHide) {
if (!module.isLoaded()) {
LOGD("%s is unloaded", module.id.data());
} else {
names.emplace(module.path);
}
} else {
LOGD("module %s does not support hide", module.id.data());
}
}
if (!names.empty()) hide::HidePathsFromMaps(names);//隐藏RiruModule
}

继续跟进 hide::HidePathsFromMaps

1
2
3
4
5
6
7
8
9
10
11
12
13
void HidePathsFromMaps(const std::set<std::string_view> &names) {
if (!riru_hide_func) return;

LOGD("do hide");
riru_hide_func(names); //调用libriruhide.so的riru_hide函数

// cleanup riruhide.so
LOGD("dlclose");
if (dlclose(riru_hide_handle) != 0) {
LOGE("dlclose failed: %s", dlerror());
return;
}
}

继续跟进 riru_hide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int riru_hide(const std::set<std::string_view> &names) {
procmaps_iterator *maps = pmparser_parse(-1);
if (maps == nullptr) {
LOGE("cannot parse the memory map");
return false;
}

char buf[PATH_MAX];
hide_struct *data = nullptr;
size_t data_count = 0;
procmaps_struct *maps_tmp;
while ((maps_tmp = pmparser_next(maps)) != nullptr) {
bool matched = false;
#ifdef DEBUG_APP
matched = strstr(maps_tmp->pathname, "libriru.so");
#endif
matched = names.count(maps_tmp->pathname);

if (!matched) continue;//匹配要隐藏的so

auto start = (uintptr_t) maps_tmp->addr_start;
auto end = (uintptr_t) maps_tmp->addr_end;
if (maps_tmp->is_r) {
if (data) {
data = (hide_struct *) realloc(data, sizeof(hide_struct) * (data_count + 1));
} else {
data = (hide_struct *) malloc(sizeof(hide_struct));
}
data[data_count].original = maps_tmp;
data_count += 1;
}
LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s", start, end, maps_tmp->perm, maps_tmp->offset,
maps_tmp->pathname);
}
//上面的代码为data填充数据,把要隐藏的so信息放进去
for (int i = 0; i < data_count; ++i) {
do_hide(&data[i]); //这里进行隐藏
}

if (data) free(data);
pmparser_free(maps);
return 0;
}

跟进 do_hide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static int do_hide(hide_struct *data) {
auto procstruct = data->original;
auto start = (uintptr_t) procstruct->addr_start;
auto end = (uintptr_t) procstruct->addr_end;
auto length = end - start;
int prot = get_prot(procstruct);

data->backup_address = (uintptr_t) FAILURE_RETURN(
mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0),
MAP_FAILED);//先申请一段匿名内存backup_address
LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s is backup to %" PRIxPTR, start, end, procstruct->perm,
procstruct->offset,
procstruct->pathname, data->backup_address);

if (!procstruct->is_r) {
LOGD("mprotect +r");
FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_READ), -1);
}
LOGD("memcpy -> backup");
memcpy((void *) data->backup_address, (void *) start, length);//把要隐藏的so内存copy到刚申请的backup_address里

// munmap original
LOGD("munmap original");
FAILURE_RETURN(munmap((void *) start, length), -1);//取消要隐藏的so的map映射

// restore
LOGD("mmap original");
FAILURE_RETURN(mmap((void *) start, length, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0),
MAP_FAILED);//再到原来so的地址上申请一段匿名内存,大小权限和原来一致
LOGD("mprotect +w");
FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_WRITE), -1);//设置权限
LOGD("memcpy -> original");
memcpy((void *) start, (void *) data->backup_address, length);//再把backup_address的内存copy到原来so的内存空间里
if (!procstruct->is_w) {
LOGD("mprotect -w");
FAILURE_RETURN(mprotect((void *) start, length, prot), -1);
}
return 0;
}

这里就可以看明白了,maps 的隐藏就是先把原来 so 给取消映射,然后在原位置重新匿名映射,再通过 backup 内存把原 so 内存 copy 过去.

HideFromSoList

这个代码的调用链是 nativeSpecializeAppProcess_post->Entry::Unload->hide::HideFromSoList.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void HideFromSoList() {
auto self_path = magisk::GetPathForSelfLib("libriru.so");
std::set<std::string_view> names_to_remove{};
if (Entry::IsSelfUnloadAllowed()) {
LOGD("don't hide self since it will be unloaded");
} else {
names_to_remove.emplace(self_path);
}
for (const auto &module : modules::Get()) {
if (module.supportHide) {
if (!module.isLoaded()) {
LOGD("%s is unloaded", module.id.data());
continue;
}
if (module.apiVersion < 24) {
LOGW("%s is too old to hide so", module.id.data());
} else {
names_to_remove.emplace(module.path);
}
} else {
LOGD("module %s does not support hide", module.id.data());
}
}

if (android_prop::GetApiLevel() >= 23 && !names_to_remove.empty()) {
RemoveFromSoList(names_to_remove); //隐藏逻辑在这里
}
}
1
2
3
static void RemoveFromSoList(const std::set<std::string_view> &names) {
hide::RemovePathsFromSolist(names);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
void RemovePathsFromSolist(const std::set<std::string_view> &names) {
if (!initialized) {
LOGW("not initialized");
return;
}
ProtectedDataGuard g;
for (const auto &soinfo : linker_get_solist()) {
const auto &real_path = soinfo->get_realpath();
if (real_path && names.count(real_path)) {
solist_remove_soinfo(soinfo); //在这里
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool solist_remove_soinfo(soinfo *si) {
soinfo *prev = nullptr, *trav;
for (trav = solist; trav != nullptr; trav = trav->next()) { //注意这个solist
if (trav == si) {
break;
}
prev = trav;
}

if (trav == nullptr) {
// si was not in solist
LOGE("name \"%s\"@%p is not in solist!", si->get_realpath(), si);
return false;
}

// prev will never be null, because the first entry in solist is
// always the static libdl_info.
prev->next(si->next()); //隐藏的逻辑在这里,这个操作就是链表的一个删除操作,那么他是修改谁的链表呢?
if (si == *sonext) {
*sonext = prev;
}

LOGD("removed soinfo: %s", si->get_realpath());

return true;
}

哈,HideFromSoList 其实就是一个链表的隐藏,那么又是哪个链表呢?
注意到 solist 就是那个全局链表

1
2
3
4
5
6
7
8
const auto initialized = []() {
SandHook::ElfImg linker("/linker");
return ProtectedDataGuard::setup(linker) &&
(solist = getStaticVariable<soinfo>(linker, "__dl__ZL6solist")) != nullptr && //看这里
(sonext = linker.getSymbAddress<soinfo**>("__dl__ZL6sonext")) != nullptr &&
(somain = getStaticVariable<soinfo>(linker, "__dl__ZL6somain")) != nullptr &&
soinfo::setup(linker);
}();

solist 其实就是 linker 的一个全局符号__dl__ZL6solist. 它是一个 soinfo 结构的链表,记录了所有 linker 加载的模块的信息.

到这里应该就明白了,HideFromSoList 去掉了 linker 里的全局 soinfo 链.

总结

Riru-hide 从两个地方进行了隐藏,分别是 maps 和 soinfo.