Skip to content

Conversation

JingMatrix
Copy link
Contributor

Changes

  1. Replace dlclose of libzygisk.so by manually deleting its soinfo record and unmap its memory blocks.
  2. Cache contents of /proc/self/maps for PLT hooks of libart.so to avoid reading this file in the target process.

Detailed explanations are given as commit messages.

Why

Injection detector applications, such as Native Test, Holmes, Native Detection and JingMatrix/Demo, have found traces of ReZygisk for a long time. Current pull-request will eliminate the traces brought by solist and virtual map scanning.

Checkmarks

  • The modified functions have been tested.
  • Used the same indentation as the rest of the project.
  • Updated documentation (changelog).

Additional information

Please review this pull-request carefully to avoid my unnoticeable mistakes.

Relying on dlclose to unload libzygisk.so will block us to clean its trace in the solist.
This commit allows us to unmap libzygisk.so without using dlclose.
To call munmap, we use the function pthread_attr_setstacksize instead of pthread_attr_destroy, so that tail-call can still be applied here since it has the same signature as munmap.
1. fix typo vsdo -> vdso and allow this symbol to be not found, which is the case for the 32bit linkers on some devices
2. use soinfo_free to fully remove the soinfo record of libzygisk.so
3. set `soinfo.size = 0` to avoid the library being unmapped while removing its soinfo record
4. add more debug logs for troubleshooting
@JingMatrix JingMatrix requested a review from ThePedroo December 15, 2024 15:51
@ThePedroo ThePedroo added confirmed This issue or pull request is confirmed to be done. enhancement New feature or request labels Dec 15, 2024
@JingMatrix JingMatrix force-pushed the main branch 2 times, most recently from 5b3bb46 to b9ea635 Compare December 15, 2024 18:55
@fuengfah
Copy link

Nice work RZ teams

Copy link
Member

@ThePedroo ThePedroo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, however there are some syntax things that would be better changed

@@ -98,28 +110,51 @@ namespace SoList {
return addr == NULL ? NULL : *addr;
}

static void DropSoPath(const char* target_path) {
static bool DropSoPath(const char* target_path) {
bool path_found = false;
if (solist == NULL && !Initialize()) {
LOGE("Failed to initialize solist");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a line to separate the logging and the return.

if (iter == *sonext) {
*sonext = prev;
LOGI("dropping solist record for %s loaded at %s with size %zu", iter->get_name(), iter->get_path(), iter->get_size());
if (iter->get_size() > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A line between logging and if

}
prev = iter;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A line between the return and }


static void ResetCounters(size_t load, size_t unload) {
if (solist == NULL && !Initialize()) {
LOGE("Failed to initialize solist");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

return;
}
if (g_module_load_counter == NULL || g_module_unload_counter == NULL) {
LOGI("g_module counters not defined, skip reseting them");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

auto unloaded_modules = *g_module_unload_counter;
if (loaded_modules >= load) {
*g_module_load_counter = loaded_modules - load;
LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto (up to you)

bool path_found = SoList::DropSoPath(path);
if (!path_found || !spoof_maps) return;

LOGD("spoofing virtual maps for %s", path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should plainly remove that as it is still easy to detect either way.

@@ -1,5 +1,6 @@
#pragma once

#include <cstring>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Language server suggestion, because in

void clearApi() { memset(&api, 0, sizeof(api)); } 

memset is defined in that header

@@ -850,15 +819,21 @@ static void hook_unloader() {
ino_t art_inode = 0;
dev_t art_dev = 0;

for (auto &map : lsplt::MapInfo::Scan()) {
for (auto &map : cached_map_infos) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above the parent function, please add a comment explaining why we do cache, especially here.

void *start_addr = nullptr;
size_t block_size = 0;
for (auto &info : map) {
if (strstr(info.path.c_str(), "libzygisk.so")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (strstr(info.path.c_str(), "libzygisk.so")) {
if (!strstr(info.path.c_str(), "libzygisk.so")) continue;

Note that it is impossible to hide injecting trace of virtual memory maps from the hooked target process.
ReZygisk will only focus on removing its trace for approcess that are not hooked by modules.
Reading the file `/proc/self/maps` is detectable by the target process.
Hence, we should cache scanned virtual maps after `libart.so` is loaded for later plt hooks in the target process.
Two counters for module loading and unloading are introduced in the commit https://cs.android.com/android/_/android/platform/bionic/+/a2e83ab34845759f0999d0ec88f4cdf558c0a9f5. To remove linker traces of libzygisk.so and Zygisk modules, we should reset them properly.
@ThePedroo ThePedroo merged commit 4b7618d into PerformanC:main Dec 18, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed This issue or pull request is confirmed to be done. enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants