一
背景
二
模拟战争环境
对手模拟:
对抗模拟:
这个其实是因为Android的一个严重漏洞导致的,就是Android11以下,哪怕没有权限也可以通过netlinker直接获取到你得网卡信息 , 详细代码实现可以参考我之前写的文章:这个代码在github有开源https://bbs.kanxue.com/thread-271698.htm。 (不过我只有10以下的手机的话,可以直接往内核打补丁,然后重新编译内核刷入,也是可以的,不过麻烦点,不推荐) 不需要任何授权,而且还可以读一些net文件直接通过cat的方式获取 ,而且11以下,还有可以使用老的SD卡读写权限,直接往SD卡写入。 新版本只允许你往/sdcard/Android/data/包名里面去写入SD卡相关数据,这样在Apk卸载的时候直接就会一起删除。有很多大厂的指纹非常恶心。 会往SD卡里面写入一些文件种子,如果不进行隔离,很难改的全。
客户端架构:
设备指纹分类:
系统服务相关设备指纹相关
其他ApkSettingsProvider提供相关
内核ID相关
Init进程设备指纹相关
import android.media.MediaDrm;
import java.util.UUID;
try {
UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
MediaDrm wvDrm = new MediaDrm(wideVineUuid);
byte[] wideVineId =
wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
Log.i("DRM ID", bytesToHex(wideVineId));
wvDrm.release();
} catch (UnsupportedSchemeException e) {
e.printStackTrace();
}
#include <media/NdkMediaDrm.h>
const uint8_t uuid[] =
{0xed,0xef,0x8b,0xa9,0x79,0xd6,0x4a,0xce,0xa3,0xc8,0x27,0xdc,0xd5,0x1d,0x21,0xed
};
AMediaDrm *mediaDrm = AMediaDrm_createByUUID(uuid);
AMediaDrmByteArray aMediaDrmByteArray;
AMediaDrm_getPropertyByteArray(mediaDrm, PROPERTY_DEVICE_UNIQUE_ID,
&aMediaDrmByteArray);
AMediaDrm_release(mediaDrm);
C:UsersASUS>adb shell
cmi:/ $ su
cmi:/ # ps -ef | grep -service.widevine | grep android.hardware.drm
media 1069 1 0 19:31:15 ? 00:00:00 [email protected]
Apk环境相关
设备指纹对抗:
前置准备 :
因为在可以用Magisk模块在指定进程注入lsplant,实现Java Hook, 比如修改system_server里面的去hook一些Java方法 很方便,自己实现的话逻辑也可控 ,方便Hook除了目标Apk以外的其他进程。
Magisk模块基础:
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
在 Android 上,所有的应用进程都是从一个特殊的守护进程 "Zygote" 分叉(fork)出来的。
对于每一个新的应用进程,Zygote 会分叉一个新进程并进行 "(specialization)" 操作。
这个专门化操作强制实施 Android 安全沙盒,确保第三方应用代码只有在被限制在沙盒中之后才加载。
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
在 Android 上,还有一个特殊的进程叫做 "system_server"。
这个单一进程承载了大部分的系统服务,控制着 Android 操作系统和应用程序之间的互动方式。
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Zygisk 框架提供了一种方法,允许开发者构建模块并在 system_server
和任何应用进程的specialization之前和之后运行自定义代码。
这使开发者能够注入代码并改变 system_server 和应用进程的行为。
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
请注意,模块只会在 Zygote fork 子进程之后加载。
这意味着你的所有代码都是在应用/系统服务器进程中运行,而不是在 Zygote 守护进程中运行!
/* Copyright 2022-2023 John "topjohnwu" Wu
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
// This is the public API for Zygisk modules.
// DO NOT MODIFY ANY CODE IN THIS HEADER.
#pragma once
#include <jni.h>
#include <ostream>
#include <sstream>
#include <string>
#define ZYGISK_API_VERSION 4
/*
***************
* Introduction
***************
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
在 Android 上,所有的应用进程都是从一个特殊的守护进程 "Zygote" 分叉(fork)出来的。
对于每一个新的应用进程,Zygote 会分叉一个新进程并进行 "(specialization)" 操作。
这个专门化操作强制实施 Android 安全沙盒,确保第三方应用代码只有在被限制在沙盒中之后才加载。
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
在 Android 上,还有一个特殊的进程叫做 "system_server"。
这个单一进程承载了大部分的系统服务,控制着 Android 操作系统和应用程序之间的互动方式。
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Zygisk 框架提供了一种方法,允许开发者构建模块并在 system_server
和任何应用进程的specialization之前和之后运行自定义代码。
这使开发者能够注入代码并改变 system_server 和应用进程的行为。
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
请注意,模块只会在 Zygote fork 子进程之后加载。
这意味着你的所有代码都是在应用/系统服务器进程中运行,而不是在 Zygote 守护进程中运行!
*********************
* Development Guide
*********************
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) {
return orig_logger_entry_max(env);
}
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
JNINativeMethod methods[] = {
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
};
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
}
private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
-----------------------------------------------------------------------------------------
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
never runs in a true superuser environment.
由于你的模块类的代码在 pre[XXX]Specialize 中以 Zygote 的特权运行,
或者在 post[XXX]Specialize 中在目标进程的沙盒中运行,你的类中的代码从不在一个真正的超级用户环境中运行。
If your module require access to superuser permissions, you can create and register
a root companion handler function. This function runs in a separate root companion
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
your target process and the root companion process.
如果你的模块需要访问超级用户权限,你可以创建并注册一个根伴随处理函数(root companion handler function)。
这个函数在一个单独的根伴随守护进程中运行,并提供了一个 Unix socket,允许你在目标进程和根伴随进程之间进行
IPC(进程间通信)。
Example code:
static void example_handler(int socket) { ... }
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This method is called as soon as the module is loaded into the target process. A Zygisk API handle will be passed as an argument.
// 一旦模块被加载到目标进程中,就会调用此方法。一个 Zygisk API 句柄会作为参数传递。
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This method is called before the app process is specialized.
// At this point, the process just got forked from zygote, but no app specific specialization
// is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote.
// 在应用进程专业化之前调用,此时进程刚从 zygote 分叉出来,但还未应用任何特定于应用的专业化设置。
// 这意味着进程还没有任何沙箱限制,并且仍然以 zygote 相同的权限运行。
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This method is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs with the same privilege of the app's own code.
// 在应用进程专业化之后调用,此时进程已经为该应用启用了所有沙盒限制。
// 这意味着这个方法与应用自己的代码以相同的权限运行。
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This method is called before the system server process is specialized.
// 在系统服务器进程专业化之前调用此方法。
// See preAppSpecialize(args) for more info.
// 有关更多信息,请参考 preAppSpecialize(args)。
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This method is called after the system server process is specialized.
// At this point, the process runs with the privilege of system_server.
// 在系统服务器进程专业化之后调用。
// 此时,进程以 system_server 的权限运行。
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
};
struct AppSpecializeArgs {
// Required arguments.
// These arguments are guaranteed to exist on all Android versions.
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jobjectArray &rlimits;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing
jintArray *const fds_to_ignore;
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete;
std::string toString(JNIEnv *env) const {
std::ostringstream stream;
printJString(env, stream, "nice_name", nice_name);
printJString(env, stream, "app_data_dir", app_data_dir);
printJString(env, stream, "se_info", se_info);
printJString(env, stream, "instruction_set", instruction_set);
stream << "uid: " << uid << " gid: " << gid << " runtime_flags: " << runtime_flags
<< " mount_external: " << mount_external;
printJIntArray(env, stream, "gids", gids);
printJBoolean(env, stream, "is_child_zygote", is_child_zygote);
printJBoolean(env, stream, "is_top_app", is_top_app);
printJBoolean(env, stream, "mount_data_dirs", mount_data_dirs);
printJBoolean(env, stream, "mount_storage_dirs", mount_storage_dirs);
printJObjectArrayPointer(env, stream, "pkg_data_info_list", pkg_data_info_list);
printJObjectArrayPointer(env, stream, "whitelisted_data_info_list",
whitelisted_data_info_list);
return stream.str();
}
private:
[[maybe_unused]]
void printJString(JNIEnv *env, std::ostringstream &stream, const std::string &name,
jstring value) const {
if (value != nullptr) {
const char *chars = env->GetStringUTFChars(value, nullptr);
stream << " " << name << ": " << chars << "n";
env->ReleaseStringUTFChars(value, chars);
} else {
stream << " " << name << ": null" << "n";
}
}
[[maybe_unused]]
void printJIntArray(JNIEnv *env, std::ostringstream &stream, const std::string &name,
jintArray value) const {
if (value != nullptr) {
jsize length = env->GetArrayLength(value);
jint *elements = env->GetIntArrayElements(value, nullptr);
stream << " " << name << ": [";
for (jsize i = 0; i < length; ++i) {
stream << elements[i];
if (i < length - 1) {
stream << ", ";
}
}
stream << "]" << "n";
env->ReleaseIntArrayElements(value, elements, JNI_ABORT);
} else {
stream << " " << name << ": null" << "n";
}
}
[[maybe_unused]]
void printJBoolean(JNIEnv *env, std::ostringstream &stream,
const std::string &name, jboolean *value) const {
if (value != nullptr) {
stream << " " << name << ": " << ((*value) == JNI_TRUE ? "true" : "flase") << "n";
} else {
stream << " " << name << ": null" << "n";
}
}
[[maybe_unused]]
void
printJObjectArrayPointer(JNIEnv *env, std::ostringstream &stream, const std::string &name,
jobjectArray *arrayPtr) const {
if (arrayPtr && *arrayPtr) {
jobjectArray array = *arrayPtr;
jsize length = env->GetArrayLength(array);
stream << " " << name << ": [";
for (jsize i = 0; i < length; ++i) {
jstring str = (jstring) env->GetObjectArrayElement(array, i);
const char *chars = env->GetStringUTFChars(str, nullptr);
stream << chars;
env->ReleaseStringUTFChars(str, chars);
if (i < length - 1) {
stream << ", ";
}
}
stream << "]" << "n";
} else {
stream << " " << name << ": null" << "n";
}
}
};
struct ServerSpecializeArgs {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs() = delete;
};
namespace internal {
struct api_table;
template<class T>
void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option)
// 这些值被用在 Api::setOption(Option) 方法中
enum Option : int {
// Force Magisk's denylist unmount routines to run on this process.
// 强制在此进程上运行 Magisk 的拒绝列表unmount routines。
//
// Setting this option only makes sense in preAppSpecialize.
// 只有在 preAppSpecialize 中设置此选项才有意义。
// The actual unmounting happens during app process specialization.
// 实际的卸载过程发生在应用进程specialization期间。
//
// Set this option to force all Magisk and modules' files to be unmounted from the
// mount namespace of the process, regardless of the denylist enforcement status.
// 设置此选项以强制从进程的挂载命名空间中卸载所有 Magisk 和模块的文件,无论拒绝列表的执行状态如何。
// 这个主要是卸载magisk整个系统文件
FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// 设置此选项时,你的模块库将在 post[XXX]Specialize 之后被 dlclose。
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
// 注意,dlclose 你的模块后,你所有的代码都将从内存中取消映射。
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
// 在进程中挂钩任何函数后,你必须不启用此选项。
// 这个是卸载so文件
DLCLOSE_MODULE_LIBRARY = 1,
};
// Bit masks of the return value of Api::getFlags()
// Api::getFlags() 返回值的位掩码
enum StateFlag : uint32_t {
// The user has granted root access to the current process
// 用户已授予当前进程根访问权限
PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist
// 当前进程被添加到拒绝列表中
PROCESS_ON_DENYLIST = (1u << 1),
};
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards.
// 所有API方法post[XXX]Specialize后停止工作,因为Zygisk将在之后从专业化过程中卸载。
struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC.
// 获取和root守护进程通讯的句柄,可以直接通过这个句柄进行ipc通讯 。
// 连接到root companion进程并获取 Unix socket进行 IPC。
//
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
// 由于 SELinux 限制,此 API 仅在 pre[XXX]Specialize 方法中有效。
//
// The pre[XXX]Specialize methods run with the same privilege of zygote.
// pre[XXX]Specialize 方法以与 zygote 相同的权限运行。
// If you would like to do some operations with superuser permissions, register a handler
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
// 如果你想以超级用户权限执行一些操作,请注册一个处理函数,
// 在根进程中以 REGISTER_ZYGISK_COMPANION(func) 调用。
// Another good use case for a companion process is that if you want to share some resources
// across multiple processes, hold the resources in the companion process and pass it over.
// companion进程的另一个好用途是,如果你想在多个进程间共享一些资源,
// 可以在companion进程中保持这些资源并传递过去。
//
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
// root伴随进程是 ABI 意识到的;
// 也就是说,当从 32 位进程调用此方法时,你将连接到一个 32 位的伴随进程,对于 64 位也是如此。
//
// Returns a file descriptor to a socket that is connected to the socket passed to your
// module's companion request handler. Returns -1 if the connection attempt failed.
// 返回一个文件描述符到一个套接字,该套接字连接到传递给模块的伴随请求处理器的套接字。
// 如果连接尝试失败,则返回 -1。
int connectCompanion();
// Get the file descriptor of the root folder of the current module.
// 获取当前模块的根文件夹的文件描述符。
//
// This API only works in the pre[XXX]Specialize methods.
// 此 API 仅在 pre[XXX]Specialize 方法中工作。
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
// or in the root companion process (assuming that you sent the fd over the socket).
// 只有在 pre[XXX]Specialize 方法或根伴随进程中(假设你通过套接字发送了 fd)才能访问返回的目录。
// Both restrictions are due to SELinux and UID.
// 这两个限制都是由于 SELinux 和 UID。
//
// Returns -1 if errors occurred.
// 如果发生错误,则返回 -1。
int getModuleDir();
// Set various options for your module.
// 为你的模块设置各种选项。
// Please note that this method accepts one single option at a time.
// 请注意,此方法一次只接受一个选项。
// Check zygisk::Option for the full list of options available.
// 查看 zygisk::Option 以获取可用选项的完整列表。
void setOption(Option opt);
// Get information about the current process.
// 获取有关当前进程的信息。
// Returns bitwise-or'd zygisk::StateFlag values.
// 返回按位或的 zygisk::StateFlag 值。
uint32_t getFlags();
// Exempt the provided file descriptor from being automatically closed.
// 免除提供的文件描述符被自动关闭。
//
// This API only make sense in preAppSpecialize; calling this method in any other situation
// is either a no-op (returns true) or an error (returns false).
// 此 API 仅在 preAppSpecialize 中有意义;
// 在任何其他情况下调用此方法要么是无操作(返回 true),要么是错误(返回 false)。
//
// When false is returned, the provided file descriptor will eventually be closed by zygote.
// 当返回 false 时,提供的文件描述符最终将被 zygote 关闭。
bool exemptFd(int fd);
// Hook JNI native methods for a class
// 为一个类挂钩 JNI 原生方法
//
// Lookup all registered JNI native methods and replace it with your own methods.
// 查找所有注册的 JNI 原生方法并用你自己的方法替换它。
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
// 原始函数指针将被保存在每个 JNINativeMethod 的 fnPtr 中。
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
// will be set to nullptr.
// 如果没有找到匹配的类、方法名或签名,那么特定的 JNINativeMethod.fnPtr 将被设置为 nullptr。
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
int numMethods);
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
// hook在内存中加载的 ELF 文件的 PLT(程序链接表)中得函数。
//
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
// 解析 /proc/[PID]/maps 将给你一个进程的内存映射。例如:
//
// <address> <perms> <offset> <dev> <inode> <pathname>
// 地址 权限 偏移量 设备 节点 路径名
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
// (更多详情请访问:https://man7.org/linux/man-pages/man5/proc.5.html)
//
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
// `dev` 和 `inode` 对唯一标识了被映射到内存中的文件。
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
// 对于匹配的在内存中加载的 ELF 文件,将函数 `symbol` 替换为 `newFunc`。
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
// 如果 `oldFunc` 不是 nullptr,原始的函数指针将被保存到 `oldFunc`。
void
pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
// Commit all the hooks that was previously registered.
// 提交之前注册的所有钩子。
// Returns false if an error occurred.
// 如果发生错误,返回 false。
bool pltHookCommit();
private:
internal::api_table *tbl;
template<class T>
friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module
#define REGISTER_ZYGISK_MODULE(clazz)
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) {
zygisk::internal::entry_impl<clazz>(table, env);
}
// Register a root companion request handler function for your module
// 为模块注册root伴随请求处理程序函数
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process.
// 该函数在超级用户守护进程中运行,并处理来自目标进程中运行的模块的根伴随请求。
// The function has to accept an integer value,
// which is a Unix domain socket that is connected to the target process.
// 函数必须接受一个整数值,该整数值是连接到目标进程的Unix socket。
// See Api::connectCompanion() for more info.
//
// NOTE:
// the function can run concurrently on multiple threads.
// Be aware of race conditions if you have globally shared resources.
// 该函数可以在多个线程上同时运行。如果您拥有全球共享的资源,请注意种族状况。
#define REGISTER_ZYGISK_COMPANION(func)
void zygisk_companion_entry(int client) { func(client); }
/*********************************************************
* The following is internal ABI implementation detail.
* You do not have to understand what it is doing.
*********************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *impl;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
module_abi(ModuleBase *
module) : api_version(ZYGISK_API_VERSION), impl(module) {
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
}
};
struct api_table {
// Base
void *impl;
bool (*registerModule)(api_table *, module_abi *);
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
bool (*exemptFd)(int);
bool (*pltHookCommit)();
int (*connectCompanion)(void * /* impl */);
void (*setOption)(void * /* impl */, Option);
int (*getModuleDir)(void * /* impl */);
uint32_t (*getFlags)(void * /* impl */);
};
template<class T>
void entry_impl(api_table *table, JNIEnv *env) {
static Api api;
api.tbl = table;
static T module;
ModuleBase *m = &module;
static module_abi abi(m);
if (!table->registerModule(table, &abi)) return;
m->onLoad(&api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
}
inline int Api::getModuleDir() {
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
}
inline void Api::setOption(Option opt) {
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
}
inline uint32_t Api::getFlags() {
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
}
inline bool Api::exemptFd(int fd) {
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
}
inline void
Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
int numMethods) {
if (tbl->hookJniNativeMethods)
tbl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc,
void **oldFunc) {
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
}
inline bool Api::pltHookCommit() {
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
}
} // namespace zygisk
extern "C" {
[[gnu::visibility("default"), maybe_unused]]
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default"), maybe_unused]]
void zygisk_companion_entry(int);
} // extern "C"
Magisk模块如何实现一个Xposed ?
1、Magisk模块加载java代码
2、系统服务初始化
Object origServiceManager = ServiceManager.getIServiceManager.callStatic();
//在SystemServer还没有添加系统服务的时候进行动态代理
ServiceManager.sServiceManager.setStaticValue(Reflection.on("android.os.IServiceManager")
.proxy((proxy, method, args) -> {
if ("addService".equals(method.getName())) {
String serviceName = (String) args[0];
IBinder binder = (IBinder) args[1];
//CLog.w("[" + serviceName + "] -> [" + binder + "]");
if (ServerConfig.TARGET_BINDER_SERVICE_NAME.equals(serviceName)) {
// Replace the clipboard service so apps can acquire binder
args[1] = new BinderServiceProxy((Binder) args[1],
ServerConfig.TARGET_BINDER_SERVICE_DESCRIPTOR, rms);
clipboardServiceReplaced = true;
} else if (Context.ACTIVITY_SERVICE.equals(serviceName)) {
RuntimeManagerService.initContext();
}
if (clipboardServiceReplaced)
//set orig ServiceManager
ServiceManager.sServiceManager.setStaticValue(origServiceManager);
}
try {
return method.invoke(origServiceManager, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}));
3、共享内存的实现
检测也很好检测,直接用内存漫游去内存里面扣DexFile File 的个数,很容易检测到内存加载了多少个Dex
else if (code == ServerConfig.DEX_TRANSACTION_CODE) {
//CLog.i("RuntimeManagerService receive DEX_TRANSACTION_CODE "+.getCallAppName());
try {
//unzip jar
unzip(RuntimeManagerService.RUNTIME_JAR_PATH,
RuntimeManagerService.RUNTIME_TEMP_DEX_PATH);
//get dex
File[] files = new File(RuntimeManagerService.RUNTIME_TEMP_DEX_PATH).listFiles();
if (files == null || files.length == 0) {
CLog.i("DEX_TRANSACTION_CODE files size == 0 ");
return false;
}
@SuppressWarnings("all")
List<File> dexFiles = Stream.of(files).
filter(file -> file.getName().endsWith(".dex")).collect(Collectors.toList());
if (dexFiles.isEmpty()) {
CLog.i("DEX_TRANSACTION_CODE dexFiles.size()==0 ");
return false;
}
for(File file:dexFiles){
if(!file.exists()){
CLog.d("DEX_TRANSACTION_CODE dex file not find "+file);
return false;
}
}
//CLog.i("DEX_TRANSACTION_CODE dex info " + dexFiles);
ArrayList<SharedMemory> shm = RuntimeManagerService.getPreloadDex(dexFiles);
if (shm == null || shm.isEmpty()) return false;
reply.writeNoException();
//dex size
reply.writeInt(shm.size());
for (int i = 0; i < shm.size(); i++) {
// assume that write only a fd
SharedMemory sharedMemory = shm.get(i);
sharedMemory.writeToParcel(reply, 0);
reply.writeLong(sharedMemory.getSize());
}
//clean temp dir
try {
FileUtils.deleteDirectory(
new File(RuntimeManagerService.RUNTIME_TEMP_DEX_PATH)
);
}catch (Throwable e) {
MagiskEngine.popen("rm -rf "+RuntimeManagerService.RUNTIME_TEMP_DEX_PATH);
}
} catch (Throwable e) {
CLog.e("DEX_TRANSACTION_CODE handler error " + e, e);
}
//CLog.i("RuntimeManagerService receive DEX_TRANSACTION_CODE return");
return true;
}
SharedMemory sharedMemory = shm.get(i);
sharedMemory.writeToParcel(reply, 0);
jobject DexLoader::FromDexFd(JNIEnv *env,
const std::vector<std::tuple<int, size_t>> fd_list,const std::string& process_name ) {
if (fd_list.size() == 0) {
LOGE("DexLoader::FromDexFd fd size<=0")
return nullptr;
}
LOGI("DexLoader::FromDexFd start load dex %s ", process_name.c_str())
jobjectArray byteBuffArray =
env->NewObjectArray(fd_list.size(),env->FindClass("java/nio/ByteBuffer"), NULL);
for(int i=0;i<fd_list.size();i++){
std::tuple<int, size_t> fd_info = fd_list.at(i);
auto fd = std::get<0>(fd_info);
auto size = std::get<1>(fd_info);
auto *addr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
LOGI("DexLoader::FromDexFd mmap start -> %p [%s] ",addr, printMappedRegion(addr).c_str())
//auto *addr = mmap(nullptr, size, PROT_READ, MAP_ANONYMOUS | MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
LOGE("DexLoader::FromDexFd mmap error %s ", strerror(errno))
return nullptr;
}
//get byte buff
ScopedLocalRef<jobject> buffer(env,env->NewDirectByteBuffer(addr, (long) size));
env->SetObjectArrayElement(byteBuffArray,i,buffer.get());
//LOGI("DexLoader::FromDexFd mmap success %lu %p - %p package name -> [%s] ", size,addr,(void*)((char*)addr+size),process_name.c_str())
}
if(InMemoryDexClassLoader == nullptr){
InMemoryDexClassLoader = WellKnownClasses::CacheClass(env,
"dalvik/system/InMemoryDexClassLoader");
}
if(InMemoryDexClassLoader_init == nullptr){
//more dex
InMemoryDexClassLoader_init = WellKnownClasses::CacheMethod(env,
InMemoryDexClassLoader,"<init>","([Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",false);
}
WellKnownClasses::Init(env);
ScopedLocalRef<jobject> system_class_loader(env, env->CallStaticObjectMethod(
WellKnownClasses::java_lang_ClassLoader,
WellKnownClasses::java_lang_ClassLoader_getSystemClassLoader));
jobject loader = env->NewObject(InMemoryDexClassLoader, InMemoryDexClassLoader_init,
byteBuffArray, system_class_loader.get());
if (JNIHelper::ExceptionCheck(env)) {
return nullptr;
}
return loader;
}
4、客户端如何规避Class检测?
private static SharedMemory readDex(InputStream in) throws IOException, ErrnoException {
var memory = SharedMemory.create(null, in.available());
var byteBuffer = memory.mapReadWrite();
Channels.newChannel(in).read(byteBuffer);
SharedMemory.unmap(byteBuffer);
if (BuildConfig.isObfuscationDex) {
//obfuscate dex
SharedMemory newMemory = ObfuscationManager.obfuscateDex(memory);
if (BuildConfig.isSavefuscationDex) {
saveObfuscationDex(newMemory);
}
if (memory != newMemory) {
memory.close();
memory = newMemory;
}
}
memory.setProtect(OsConstants.PROT_READ);
return memory;
}
public synchronized static ArrayList<SharedMemory> getPreloadDex(List<File> dexFiles) {
if (preloadDex == null) {
preloadDex = new ArrayList<>();
for (File dex_file : dexFiles) {
if (!dex_file.exists()) {
CLog.e("RuntimeManagerService getPreloadDex file not find " + dex_file);
continue;
}
try (var is = new FileInputStream(dex_file)) {
preloadDex.add(readDex(is));
} catch (Throwable e) {
CLog.e("RuntimeManagerService preload dex", e);
return null;
}
}
}
return preloadDex;
}
三
设备指纹对抗
设备指纹相关的对抗(system_service)
Class<?> SettingsProviderClazz = RuntimeManagerService.findclass(
"com.android.providers.settings.SettingsProvider");
if (SettingsProviderClazz == null) {
CLog.e("ServiceBaseInfoProcessor mockAndroidId ContentProviderProxyClazz == null");
return;
}
//public Bundle call(String method, String name, Bundle args) {
hook_set.add(RposedHelpers.findAndHookMethod(SettingsProviderClazz,
"call", String.class, String.class, Bundle.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
//GET_secure/android_id/Bundle[mParcelledData.dataSize=48]
var thisOjb = (ContentProvider) param.thisObject;
if (thisOjb == null) return;
//if(!FPServiceProcessManager.isMatchCallPackageInfo(thisOjb.getCallingPackage())) return;
String key = (String) param.args[1];
if (StringUtils.isEmpty(key)) {
return;
}
//args bundle
//Bundle bundle = (Bundle) param.args[2];
//result
Bundle result = (Bundle) param.getResult();
if (result != null) {
String orig_value = result.getString("value");
String mock_value = getMockValueDistribution(orig_value, key,thisOjb.getCallingPackage());
if (mock_value != null && !mock_value.equals(orig_value)) {
Bundle temp_result = new Bundle();
temp_result.putString("value", mock_value);
param.setResult(temp_result);
CLog.i("[" + key + "] system_service orig value [" + orig_value + "] mock value -> ["
+ mock_value + "] call package -> " + thisOjb.getCallingPackage());
}
// CLog.i("[" + key + "] system_service orig value [" + orig_value + "] mock value -> ["
// + mock_value + "] call package -> " + thisOjb.getCallingPackage());
}
}
}));
} catch (Throwable e) {
CLog.e("ServiceBaseInfoProcessor mockAndroidId error " + e, e);
}
private void HookgetPackageInfo(IPackageManager server_package) {
// Method[] declaredMethods = server_package.getClass().getDeclaredMethods();
// for(var method: declaredMethods){
// CLog.e("IPackageManager method info "+method);
// }
RC_MethodHook handler_list = new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
try {
Object result = param.getResult();
if (result == null) return;
if (result instanceof List<?> list) {
list.removeIf(item -> isHandler(param,item));
} else if (result instanceof ParceledListSlice<?> list) {
list.getList().removeIf(item -> isHandler(param,item));
}
} catch (Throwable e) {
CLog.e("ServicePackageProcessor handler_list error ",e);
}
}
};
RC_MethodHook handler_single = new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
try {
Object result = param.getResult();
if (result == null) return;
if (isHandler(param,result)) {
param.setResult(null);
}
} catch (Exception e) {
CLog.e("ServicePackageProcessor handler_single error ",e);
}
}
};
//java.util.List com.android.server.pm.PackageManagerService.getAllPackages()
try {
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getAllPackages", handler_list));
} catch (Exception ignored) {
}
try {
//android.content.pm.ParceledListSlice com.android.server.pm.PackageManagerService.getInstalledPackages(int,int)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getInstalledPackages", handler_list));
} catch (Exception ignored) {
}
try {
//android.content.pm.ParceledListSlice
//com.android.server.pm.PackageManagerService.getInstalledApplications(int,int)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getInstalledApplications", handler_list
));
} catch (Exception ignored) {
}
try {
//public final ParceledListSlice<PackageInfo> getPackagesHoldingPermissions()
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getPackagesHoldingPermissions", handler_list
));
} catch (Exception ignored) {
}
try {
//List<InstrumentationInfo> queryInstrumentationInternal(String targetPackage,int)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "queryInstrumentationInternal", handler_list
));
} catch (Exception ignored) {
}
try {
//public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags);
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "queryIntentActivities", handler_list
));
} catch (Exception ignored) {
}
try {
//public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
// @Nullable Intent[] specifics, @NonNull Intent intent, int flags);
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "queryIntentActivityOptions", handler_list
));
} catch (Exception ignored) {
}
try {
//public List<ResolveInfo> queryIntentServices(Intent intent, ResolveInfoFlags flags)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "queryIntentServices", handler_list
));
} catch (Exception ignored) {
}
try {
//public List<ResolveInfo> queryIntentContentProviders(Intent intent, ResolveInfoFlags flags)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "queryIntentContentProviders", handler_list
));
} catch (Exception ignored) {
}
try {
// ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentReceivers(
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "queryIntentReceivers", handler_list
));
} catch (Exception ignored) {
}
try {
//PackageInfo com.android.server.pm.PackageManagerService.getPackageInfo(java.lang.String,int,int)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getPackageInfo", handler_single));
} catch (Exception ignored) {
}
try {
//int[] com.android.server.pm.PackageManagerService.getPackageGids(java.lang.String,int,int)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getPackageGids", handler_single));
} catch (Exception ignored) {
}
try {
//ApplicationInfo com.android.server.pm.PackageManagerService.getApplicationInfo(java.lang.String,int,int)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getApplicationInfo", handler_single));
} catch (Exception ignored) {
}
try {
//InstallSourceInfo
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getInstallSourceInfo", handler_single));
} catch (Exception ignored) {
}
try {
//public abstract @Nullable Intent getLaunchIntentForPackage(@NonNull String packageName);
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getLaunchIntentForPackage", handler_single));
} catch (Exception ignored) {
}
try {
//public abstract @Nullable Intent getLeanbackLaunchIntentForPackage(@NonNull String packageName);
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getLeanbackLaunchIntentForPackage", handler_single));
} catch (Exception ignored) {
}
try {
//public ActivityInfo getActivityInfo(ComponentName className, int flags)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getActivityInfo", handler_single));
} catch (Exception ignored) {
}
try {
//public ResolveInfo resolveActivity(Intent intent, ResolveInfoFlags flags)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "resolveActivity", handler_single));
} catch (Exception ignored) {
}
try {
//public ResolveInfo resolveActivityAsUser(Intent intent, ResolveInfoFlags flags, int userId)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "resolveActivityAsUser", handler_single));
} catch (Exception ignored) {
}
try {
//public int getPackageUid(String packageName, int flags)
hook_set.addAll(RposedBridge.hookAllMethods(
server_package.getClass(), "getPackageUid", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object result = param.getResult();
if (result == null) return;
if (result instanceof Integer) {
if (param.args[0] instanceof String package_name) {
if (isBlackApp(param,package_name)) {
param.setResult(-1);
}
}
}
}
}));
} catch (Exception ignored) {
}
}
其他Apk相关Hook :
public static void mockOaid(IRuntimeService service, Context context) {
if(isInited) return;
//小米安全中心里面的类
Class<?> IdProviderClazz = RposedHelpers.findClass(
"com.miui.idprovider.IdProvider", context.getClassLoader());
if (IdProviderClazz == null) {
CLog.e("ServiceProcessOthersApks mockOaid IdProvider clazz == null");
return;
}
//CLog.e("ServiceProcessOthersApks mockOaid get class success !");
//public Cursor query(Uri uri, String[] strArr, String str, String[] strArr2, String str2) {
RposedHelpers.findAndHookMethod(IdProviderClazz, "query",
Uri.class,
String[].class, String.class,
String[].class, String.class, new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object arg = param.args[0];
if (arg == null) {
return;
}
Object result = param.getResult();
if (result == null) return;
Cursor ret = (Cursor) result;
if (param.thisObject instanceof ContentProvider object) {
Uri uri = (Uri) arg;
if (object.getCallingPackage() == null) {
return;
}
String callingPackage = object.getCallingPackage();
//CLog.i("ServiceProcessOthersApks callingPackage "+callingPackage);
//只处理目标Apk,减少请求服务端逻辑
if (callingPackage.equals(BuildConfig.tagPackageName)) {
if (uri.toString().equals(AUTHORITY + PATH_OAID)) {
handlerId(param, service, ret, PATH_OAID);
} else if (uri.toString().equals(AUTHORITY + PATH_AAID)) {
handlerId(param, service, ret, PATH_AAID);
} else if (uri.toString().equals(AUTHORITY + PATH_VAID)) {
handlerId(param, service, ret, PATH_VAID);
} else {
CLog.i("not match uri " + uri + " " + AUTHORITY + PATH_OAID);
}
}
}
}
});
isInited = true;
}
内核ID相关Hook:
常用内核ID | ||
|
||
|
||
|
||
|
||
|
||
|
houji:/ # ls -l /proc/19178/fd
total 0
lrwx------ 1 root root 64 2024-05-24 22:46 0 -> /dev/null
lrwx------ 1 root root 64 2024-05-24 22:46 1 -> /dev/null
lrwx------ 1 root root 64 2024-05-24 22:46 79 -> /dev/ashmem5bc1633e-ea26-444e-b8a6-d7f607b9dd37
lr-x------ 1 root root 64 2024-05-24 22:46 8 -> /apex/com.android.art/javalib/core-oj.jar
lrwx------ 1 root root 64 2024-05-24 22:46 80 -> socket:[605258]
这块还有一个坑,就是你为什么不用自定义ROM去做? 因为国内环境对原生ROM检测能力较强,意义不大,改动成本也太大,不适合做插件化,随开随关。
{
.procname = "boot_id",
.data = &sysctl_bootid,
.maxlen = 16,
.mode = 0444,
.proc_handler = proc_do_uuid,
},
char *sysctl_bootid = 0;
void initHackBootId(){
sysctl_bootid = (char *)kallsyms_lookup_name("sysctl_bootid");
//save orig boot_id
memset(ori_sysctl_bootid, 0, 0x10);
memcpy(ori_sysctl_bootid, sysctl_bootid, 0x10);
snprintf(ori_ashmem_path, ASHMEM_FULL_NAME_LEN, "%s%pU", ASHMEM_NAME_PREFIX, ori_sysctl_bootid);
if(sysctl_bootid == NULL){
pr_info("get sysctl_bootid sym sysctl_bootid == NULL n");
}
pr_info("runtime_kpm: kernel sysctl_bootid addr:n %llx %s",sysctl_bootid, sysctl_bootid);
}
这个Boot_id修改以后还有个坑 ,修改以后任何APk 都打不开了 ,原因是因为服务端因为已经加载了 ,保存的是原始的boot_id 。 然后我们往服务端发送具体的改机指令,服务端开始通过Syscall往内核写入mock boot_id ,因为我们需要在客户端启动之前完成mock指纹的工作 客户端刚刚启动 ,这时候客户端读取服务端资源的时候,是通过共享内存的方式去写入的 ,客户端拿到的也是一个fd文件句柄 ,客户端尝试去打开这个文件句柄的时候,发现找不到这个boot_id导致任何Apk都打不开,这块解决办法也很简单 ,可以使用mknod进行backup即可解决。
最早的时候我实现内核对抗的时候采用也是自定义内核和编译内核模块的方式,但是高版本刷入不进去,刷完手机就死机。 之前用的版本一直都是Android 10 ,就拿Boot_Id来说 ,这块其实他就是一个sysctl函数 ,这个函数是方便内核和应用层交互的 。 改内核也很简单 ,可以直接在内核里面添加一个sysctl函数,直接对应用层暴露一个命令,直接写入即可 。 static int hack_bootid(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos) {
size_t len;
long ret;
if (!write) {
pr_err("hack_bootid: Attempt to read-only");
return -EFAULT;
}
if (write) {
len = min_t(size_t, *lenp, sizeof(sysctl_bootid));
pr_info("hack_bootid: Attempt to write. Requested len=%zu", len);
if (len) {
memset(sysctl_bootid, 0, sizeof(sysctl_bootid));
ret = copy_from_user(sysctl_bootid, buffer, len); // 直接从用户空间拷贝数据
if (ret) {
pr_err("hack_bootid: Failed to copy data from user space");
return -EFAULT;
}
pr_info("hack_bootid: Successfully updated sysctl_bootid ret = %d ",ret);
}
}
pr_info("hack_bootid: Current bootid -> [%.*s]", (int)sizeof(sysctl_bootid), sysctl_bootid);
return 0;
}内核里面注册对应的sysctl表, 应用层可以直接 {
.procname = "hack_boot_id",
.data = &sysctl_bootid,
.maxlen = 16,
.mode = 0666,
.proc_handler = hack_bootid,
},修改的话直接一条命令行 ,也可以实现。 sysctl -w kernel.random.hack_boot_id="1234567890123456" 不过现在有了Apatch以后就抛弃了。
Init进程设备指纹相关:
在Android8以上,安卓出了个分区隔离 ,system分区里面的不允许读取vendor里面的内容 ,vendor也不允许读取system里面 ,这样各种应用层在Hal层进行修改 。所以就没办法注入目标进程以后通过/system/lib64/libandroid_runtime.so调用_ZN7android14AndroidRuntime7mJavaVME虚拟机的创建 。
_ZN5wvdrm8hardware3drm4V1_28widevine11WVDrmPlugin20CdmIdentifierBuilder17getDeviceUniqueIdEPNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEE
const char* _libinject_log_tag = "Zhenxi";
int _libinject_log = 1;
void libinject_log(const char* log_tag) {
_libinject_log_tag = log_tag;
_libinject_log = log_tag == NULL ? 0 : 1;
}
pid_t _pid;
void *_dlopen;
void *_dlerror;
void *_calloc;
void *_free;
typedef void (*remote_stop_t)();
remote_stop_t remote_stop_ptr = NULL;
// ptrace wrapper with some error checking.
static long trace(const char* debug, int request, void *addr = NULL, size_t data = 0) {
errno = 0;
long ret = 0;
for (int i = 0; i < 10; i++) {
ret = ptrace(request, _pid, (caddr_t) addr, (void *) data);
if (ret == -1 && (errno == EBUSY || errno == EFAULT || errno == ESRCH)) {
char eb[16];
char rb[16];
const char* e = NULL;
const char* r = NULL;
switch (errno) {
case ESRCH: e = "ESRCH"; break;
default: snprintf(eb, sizeof(eb), "%d", errno); e = eb;
}
switch (request) {
case PTRACE_PEEKTEXT: r = "PTRACE_PEEKTEXT"; break;
case PTRACE_PEEKDATA: r = "PTRACE_PEEKDATA"; break;
case PTRACE_POKETEXT: r = "PTRACE_POKETEXT"; break;
case PTRACE_POKEDATA: r = "PTRACE_POKEDATA"; break;
case PTRACE_CONT: r = "PTRACE_CONT"; break;
case PTRACE_KILL: r = "PTRACE_KILL"; break;
case PTRACE_SINGLESTEP: r = "PTRACE_SINGLESTEP"; break;
#if defined(PTRACE_GETREGS)
case PTRACE_GETREGS: r = "PTRACE_GETREGS"; break;
#endif
#if defined(PTRACE_SETREGS)
case PTRACE_SETREGS: r = "PTRACE_SETREGS"; break;
#endif
#if defined(PTRACE_GETFPREGS)
case PTRACE_GETFPREGS: r = "PTRACE_GETFPREGS"; break;
#endif
#if defined(PTRACE_SETFPREGS)
case PTRACE_SETFPREGS: r = "PTRACE_SETFPREGS"; break;
#endif
case PTRACE_ATTACH: r = "PTRACE_ATTACH"; break;
case PTRACE_DETACH: r = "PTRACE_DETACH"; break;
case PTRACE_SYSCALL: r = "PTRACE_SYSCALL"; break;
case PTRACE_SETOPTIONS: r = "PTRACE_SETOPTIONS"; break;
case PTRACE_GETEVENTMSG: r = "PTRACE_GETEVENTMSG"; break;
case PTRACE_GETSIGINFO: r = "PTRACE_GETSIGINFO"; break;
case PTRACE_SETSIGINFO: r = "PTRACE_SETSIGINFO"; break;
#if defined(PTRACE_GETREGSET)
case PTRACE_GETREGSET: r = "PTRACE_GETREGSET"; break;
#endif
#if defined(PTRACE_SETREGSET)
case PTRACE_SETREGSET: r = "PTRACE_SETREGSET"; break;
#endif
default: snprintf(rb, sizeof(rb), "%d", request); r = rb;
}
INJECTLOG("ptrace [%s] error [%s] on request [%s]", debug, e, r);
}
if (ret == -1 && (errno == ESRCH)) {
INJECTLOG("ptrace remote_stop/retry");
if (remote_stop_ptr != NULL) {
remote_stop_ptr();
}
} else {
break;
}
}
return ret;
}
/*
* This method will open /proc/<pid>/maps and search for the specified
* library base address.
*/
static uintptr_t findLibrary(const char *library, pid_t pid) {
char filename[0xFF] = { 0 }, buffer[1024] = { 0 };
FILE *fp = NULL;
uintptr_t address = 0;
bool isFind = false;
sprintf(filename, "/proc/%d/maps", pid == -1 ? _pid : pid);
fp = fopen(filename, "rt");
if (fp == NULL) {
INJECTLOGE("findLibrary fopen error %s %s ",library,filename);
goto done;
}
while (fgets(buffer, sizeof(buffer), fp)) {
if (strstr(buffer, library)) {
address = (uintptr_t) strtoul(buffer, NULL, 16);
isFind = true;
goto done;
} else{
//INJECTLOG("printf map item %s ",buffer);
}
}
done:
// if(!isFind){
// INJECTLOG("findLibrary not find [%s] in %s ",library,filename);
// }
if (fp) {
fclose(fp);
}
// if(address == 0){
// INJECTLOG("findLibrary [%s] return 0 ,not find addr ",library);
// }
return address;
}
/*
* Compute the delta of the local and the remote modules and apply it to the local address of the symbol ...
* 计算本地和远程模块的增量,并将其应用于符号的本地地址。。。
* BOOM, remote symbol address!
* BOOM,远程符号地址!
*/
static void* remote_findFunction(const char* library, void* local_addr) {
if(local_addr == nullptr){
INJECTLOGE("remote_findFunction %s location base %p",library,(void*)local_addr);
return nullptr;
}
uintptr_t remote_handle = findLibrary( library, -1 );
if(remote_handle == 0){
INJECTLOGE("remote_findFunction %s remote base == 0 ",library);
return nullptr;
}
uintptr_t local_handle = findLibrary( library, getpid() );
if(local_handle == 0){
//INJECTLOG("remote_findFunction local_handle == 0 %s loc %p %d %s ",library,local_addr,getpid(),getprogname());
Dl_info info;
if(dladdr(local_addr,&info)){
local_handle = (uintptr_t)info.dli_fbase;
} else{
INJECTLOGE("remote_findFunction local_handle backup == 0 ");
return nullptr;
}
}
uintptr_t offset = (uintptr_t) local_addr - local_handle;
uintptr_t remote_addr = remote_handle + offset;
return (void*)remote_addr;
}
static uint64_t ms() {
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
return (spec.tv_sec * 1000) + (spec.tv_nsec / 1.0e6);
}
/*
* Make sure the remote process is stopped, or we get ESRCH errors
*/
static void remote_stop() {
// INJECTLOG( "remote_stop" );
kill( _pid, SIGSTOP );
int status;
int ret;
uint64_t start = ms();
while ( (ret = waitpid( _pid, &status, WUNTRACED || WNOHANG )) != -1 ) {
if (ret == _pid) {
if (WIFSIGNALED(status)) {
trace ( "remote_stop", PTRACE_CONT, NULL, WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
break;
} else if (WIFEXITED(status)) {
break;
}
} else if (ms() - start > 128) {
// assume stopped before remote_stop() was called, 128ms is long
break;
}
usleep(1);
}
// INJECTLOG( "/remote_stop" );
}
/*
* Read 'blen' bytes from the remote process at 'addr' address.
*/
static bool remote_read(const char* debug, size_t addr, unsigned char *buf, size_t blen){
remote_stop();
size_t i = 0;
long ret = 0;
for( i = 0; i < blen; i += sizeof(size_t) ){
ret = trace( debug, PTRACE_PEEKTEXT, (void *)(addr + i) );
if( ret == -1 ) {
return false;
}
memcpy( &buf[i], &ret, sizeof(ret) );
}
return true;
}
/*
* Write 'blen' bytes to the remote process at 'addr' address.
*/
static bool remote_write(const char* debug, size_t addr, unsigned char *buf, size_t blen) {
remote_stop();
size_t i = 0;
long ret;
// make sure the buffer is word aligned
char *ptr = (char *) malloc(blen + blen % sizeof(size_t));
memcpy(ptr, buf, blen);
for (i = 0; i < blen; i += sizeof(size_t)) {
ret = trace( debug, PTRACE_POKETEXT, (void *) (addr + i), *(size_t *) &ptr[i] );
if (ret == -1) {
free(ptr);
return false;
}
}
free(ptr);
return true;
}
// Get remote registers
static void trace_getregs(const char* debug, struct pt_regs * regs) {
#if defined (__aarch64__) || defined(__x86_64__)
uintptr_t regset = NT_PRSTATUS;
struct iovec ioVec;
ioVec.iov_base = regs;
ioVec.iov_len = sizeof(*regs);
trace( debug, PTRACE_GETREGSET, (void*)regset, (size_t)&ioVec );
#else
trace( debug, PTRACE_GETREGS, 0, (size_t)regs );
#endif
}
// Set remote registers
static void trace_setregs(const char* debug, struct pt_regs * regs) {
#if defined (__aarch64__) || defined(__x86_64__)
uintptr_t regset = NT_PRSTATUS;
struct iovec ioVec;
ioVec.iov_base = regs;
ioVec.iov_len = sizeof(*regs);
trace( debug, PTRACE_SETREGSET, (void*)regset, (size_t)&ioVec );
#else
trace( debug, PTRACE_SETREGS, 0, (size_t)regs );
#endif
}
/*
* Remotely call the remote function given its address, the number of
* arguments and the arguments themselves.
* 给定远程函数的地址、参数数量和参数本身,远程调用该函数。
*/
static uintptr_t remote_call(void *function, int nargs, ...) {
#if defined(__arm__) || defined(__aarch64__) || defined(__i386__) || defined(__x86_64__)
//暂停目标进程
remote_stop();
struct pt_regs regs, rbackup;
// get registers and backup them
trace_getregs( "backup", ®s );
// 备份原始寄存器
memcpy( &rbackup, ®s, sizeof(struct pt_regs) );
// start copying parameters
va_list vl;
va_start(vl,nargs);
// push parameters into registers and stacks, setup registers to perform the call
#if defined(__arm__) || defined(__aarch64__)
// fill R0-Rx with the first 4 (32-bit) or 8 (64-bit) parameters
// 用前4个(32位)或8个(64位)参数填充R0 Rx
// arm里面寄存器是R0开始
for ( int i = 0; ( i < nargs ) && ( i < PARAMS_IN_REGS ); ++i ) {
regs.uregs[i] = va_arg( vl, uintptr_t );
}
// push remaining parameters onto stack
if (nargs > PARAMS_IN_REGS) {
regs.ARM_sp -= sizeof(uintptr_t) * (nargs - PARAMS_IN_REGS);
uintptr_t stack = regs.ARM_sp;
for ( int i = PARAMS_IN_REGS; i < nargs; ++i ) {
uintptr_t arg = va_arg( vl, uintptr_t );
remote_write( "params", (size_t)stack, (uint8_t *)&arg, sizeof(uintptr_t) );
stack += sizeof(uintptr_t);
}
}
// return address to catch
regs.ARM_lr = 0;
// function address to call
regs.ARM_pc = (uintptr_t)function;
// setup the current processor status register
// 设置当前处理器状态寄存器
if ( regs.ARM_pc & 1 ) {
// thumb
regs.ARM_pc &= (~1u);
regs.ARM_cpsr |= CPSR_T_MASK;
} else {
// arm
regs.ARM_cpsr &= ~CPSR_T_MASK;
}
#elif defined(__i386__)
// push all params onto stack
regs.esp -= sizeof(uintptr_t) * nargs;
uintptr_t stack = regs.esp;
for( int i = 0; i < nargs; ++i ) {
uintptr_t arg = va_arg( vl, uintptr_t );
remote_write( "params", (size_t)stack, (uint8_t *)&arg, sizeof(uintptr_t) );
stack += sizeof(uintptr_t);
}
// return address to catch
uintptr_t tmp_addr = 0;
regs.esp -= sizeof(uintptr_t);
remote_write( "return", (size_t)regs.esp, (uint8_t *)&tmp_addr, sizeof(uintptr_t) );
// function address to call
regs.eip = (uintptr_t)function;
#elif defined(__x86_64__)
// align, rsp - 8 must be a multiple of 16 at function entry point
{
uintptr_t space = sizeof(uintptr_t);
if (nargs > 6) space += sizeof(uintptr_t) * (nargs - 6);
while (((regs.rsp - space - 8) & 0xF) != 0) regs.rsp--;
}
// fill [RDI, RSI, RDX, RCX, R8, R9] with the first 6 parameters
for ( int i = 0; ( i < nargs ) && ( i < 6 ); ++i ) {
uintptr_t arg = va_arg( vl, uintptr_t );
switch (i) {
case 0: regs.rdi = arg; break;
case 1: regs.rsi = arg; break;
case 2: regs.rdx = arg; break;
case 3: regs.rcx = arg; break;
case 4: regs.r8 = arg; break;
case 5: regs.r9 = arg; break;
}
}
// push remaining parameters onto stack
if (nargs > 6) {
regs.rsp -= sizeof(uintptr_t) * (nargs - 6);
uintptr_t stack = regs.rsp;
for( int i = 6; i < nargs; ++i ) {
uintptr_t arg = va_arg( vl, uintptr_t );
remote_write( "params", (size_t)stack, (uint8_t *)&arg, sizeof(uintptr_t) );
stack += sizeof(uintptr_t);
}
}
// return address to catch
uintptr_t tmp_addr = 0;
regs.rsp -= sizeof(uintptr_t);
remote_write( "return", (size_t)regs.rsp, (uint8_t *)&tmp_addr, sizeof(uintptr_t) );
// function address to call
regs.rip = (uintptr_t)function;
// may be needed
regs.rax = 0;
regs.orig_rax = 0;
#endif
// end of parameters
va_end(vl);
// do the call
trace_setregs( "call", ®s );
trace( "call", PTRACE_CONT );
// catch the SIGSEGV caused by the 0 return address
// 捕获由0返回地址引起的SIGSEGV
int status;
while ( waitpid( _pid, &status, WUNTRACED ) == _pid ) {
if ( WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSEGV) ) {
break;
}
trace( "waitpid", PTRACE_CONT );
}
// get registers again for return value
// 再次获取返回值的寄存器
trace_getregs( "return", ®s );
// restore original registers state
// 恢复原始寄存器状态
trace_setregs( "restore", &rbackup );
// continue execution
// 继续执行
trace( "continue", PTRACE_CONT );
#if defined(__arm__) || defined(__aarch64__)
return regs.ARM_r0;
#elif defined(__i386__)
return regs.eax;
#elif defined(__x86_64__)
return regs.rax;
#endif
return 0;
#else
#error ARCHITECTURE NOT SUPPORTED
#endif
}
// Allocate memory in remote process
static uintptr_t remote_calloc(size_t nmemb, size_t size) {
return remote_call(_calloc, 2, nmemb, size);
}
// Free remotely allocated memory.
static void remote_free(uintptr_t p) {
remote_call(_free, 1, p);
}
// Copy a given string into the remote process memory.
static uintptr_t remote_string(const char *s) {
uintptr_t mem = remote_calloc(strlen(s) + 1, 1);
remote_write( "string", mem, (unsigned char *) s, strlen(s) + 1);
return mem;
}
// Remotely force the target process to dlopen a library.
static uintptr_t remote_dlopen(const char *libname) {
uintptr_t pmem = remote_string(libname);
uintptr_t plib = remote_call(_dlopen, 2, pmem, 2);
remote_free(pmem);
return plib;
}
// Get remote dlerror
static void remote_dlerror(char* error, int size) {
uintptr_t e = remote_call(_dlerror, 0);
remote_read("dlerror", e, (unsigned char*)error, size - 1);
}
// Find pid for process
pid_t find_pid_of(const char* process) {
int id;
pid_t pid = -1;
DIR* dir;
FILE *fp;
char filename[32];
char cmdline[256];
struct dirent * entry;
if (process == NULL)
return -1;
dir = opendir("/proc");
if (dir == NULL)
return -1;
while ((entry = readdir(dir)) != NULL) {
id = atoi(entry->d_name);
if (id != 0) {
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if (fp) {
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (strcmp(process, cmdline) == 0) {
/* process found */
//if(id > pid){
////相同进程名时, 使用进程ID最大的进程(最近启动的进程), 避免在VMOS里查找到主机的进程
// pid = id;
//}
pid = id;
break;
}
}
}
}
closedir(dir);
return pid;
}
//作用为解除zygote进程的阻塞状态
void * connect_to_zygote(void *arg) {
int s, len;
struct sockaddr_un remote;
INJECTLOG("[+] wait 2s...");
sleep(2);
/***
* zygote进程启动后会进入一个死循环, 用来接收AMS的请求连接,
* 当没有应用启动时, zygote进程一直处于阻塞状态。
* 为防止wait无法返回,因此主动发起一个zygote的连接,解除zygote阻塞状态。
*/
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {
remote.sun_family = AF_UNIX;
#if defined(__arm__)
strcpy(remote.sun_path, "/dev/socket/zygote");
#elif defined(__aarch64__)
strcpy(remote.sun_path, "/dev/socket/zygote64");
#endif
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
INJECTLOG("[+] start to connect zygote socket");
connect(s, (struct sockaddr *) &remote, len);
INJECTLOG("[+] close socket");
close(s);
}
return NULL;
}
// Load library in process pid, resolves JavaVM and passes it and param to loaded library, returns 0 on success
// 在进程pid中加载库,解析JavaVM并将其和param传递给加载的库,成功时返回0
int injectvm(pid_t pid, char* library, const char* type,const char* param) {
remote_stop_ptr = remote_stop;
int ret = 1;
_pid = pid;
// attach to target process
if ( trace( "attach", PTRACE_ATTACH ) != -1) {
// stop entire process, including non-main threads
kill( _pid, SIGSTOP);
// wait until we're stopped
remote_stop();
/* First thing first, we need to search these functions into the target
* process address space.
*/
/* We can resolve the references to LIBC easily, but dl* is tricky. On older Android
* versions, libdl.so is commonly not loaded by the linker, and our dl* functions
* come directly from the linker.
*
* On newer Android versions, libdl.so is directly loaded and dl* come from there.
*
* On even newer Android versions, the linker/libc/libdl have moved from /system to /bionic
*/
//判断VMOS环境 isVMOS!=0 表示为VMOS环境
int isVMOS = findLibrary(PATH_LIBC_VMOS, _pid);
int isVMOSPRO = findLibrary(PATH_LIBC_VMOS_PRO, _pid);
int isVMOSPRO2 = findLibrary(PATH_LIBC_VMOS_PRO_2, _pid);
int isTWOYI = findLibrary(PATH_LIBC_TWOYI, _pid);
int isVPHONE = findLibrary(PATH_LIBC_VPHONE, _pid);
const char* libc = isVMOS!=0 ? PATH_LIBC_VMOS : (isVMOSPRO!=0 ? PATH_LIBC_VMOS_PRO :(isVMOSPRO2!=0 ? PATH_LIBC_VMOS_PRO_2 : ( isTWOYI!=0 ? PATH_LIBC_TWOYI : ( isVPHONE!=0 ? PATH_LIBC_VPHONE : (access( PATH_LIBC_BIONIC, R_OK ) == 0 ? PATH_LIBC_BIONIC : PATH_LIBC)))));
const char* libdl = isVMOS!=0 ? PATH_LIBDL_VMOS : (isVMOSPRO!=0 ? PATH_LIBDL_VMOS_PRO :(isVMOSPRO2!=0 ? PATH_LIBDL_VMOS_PRO_2 : ( isTWOYI!=0 ? PATH_LIBDL_TWOYI :( isVPHONE!=0 ? PATH_LIBDL_VPHONE :(access( PATH_LIBDL_BIONIC, R_OK ) == 0 ? PATH_LIBDL_BIONIC : PATH_LIBDL)))));
const char* linker = isVMOS!=0 ? PATH_LINKER_VMOS :(isVMOSPRO!=0 ? PATH_LINKER_VMOS_PRO :(isVMOSPRO2!=0 ? PATH_LINKER_VMOS_PRO_2 : ( isTWOYI!=0 ? PATH_LINKER_TWOYI :( isVPHONE!=0 ? PATH_LINKER_VPHONE :(access( PATH_LINKER_BIONIC, R_OK ) == 0 ? PATH_LINKER_BIONIC : PATH_LINKER)))));
//INJECTLOG( "libc:%s", libc );
//INJECTLOG( "libdl:%s", libdl );
//INJECTLOG( "linker:%s", linker );
//获取远端dlopen的地址
_calloc = remote_findFunction( libc, (void *) calloc );
_free = remote_findFunction( libc, (void *) free );
if ((findLibrary( libdl, -1 ) != 0) && (findLibrary( libdl, _pid ) != 0)) {
//ret = 100+isVPHONE;
void* handle = dlopen( libdl, RTLD_LAZY );
_dlopen = remote_findFunction( libdl, dlsym( handle, "dlopen" ) );
_dlerror = remote_findFunction( libdl, dlsym( handle, "dlerror" ) );
dlclose( handle );
} else {
//ret = 200+isVPHONE;
_dlopen = remote_findFunction( linker, (void *) dlopen );
_dlerror = remote_findFunction( linker, (void *) dlerror );
}
// Resolve android::AndroidRuntime::mJavaVM,
// this is tricky from the payload because
// of linker namespaces (you can't load the lib, dlsym doesn't work right, and the location
// of the variable in memory is different between Android versions), but no such issue
// exists from this injector.
//获取java vm,如果是vendor下面的拿runtime可能失败
//因为runtime是在system分区下面
void* runtime = dlopen( PATH_LIBANDROID_RUNTIME, RTLD_NOW );
void* javavm = dlsym( runtime, "_ZN7android14AndroidRuntime7mJavaVME" );
void* _javavm = remote_findFunction( PATH_LIBANDROID_RUNTIME, javavm );
dlclose(runtime);
INJECTLOG( "calloc:%p free:%p dlopen:%p dlerror:%p javavm:%p", _calloc, _free, _dlopen, _dlerror, _javavm );
INJECTLOG( "inject elf path %s ", library );
// once we have the addresses, we can proceed to inject
if ( remote_dlopen(library) != 0 ) {
INJECTLOG( "remote dlopen [%s] success ! ",library);
void* payload = dlopen( library, RTLD_LAZY );
if(payload == nullptr){
INJECTLOG( "remote dlopen error %s ", library);
}
void* LocDoRunSym = dlsym(payload, "doRun" );
if(LocDoRunSym == nullptr){
INJECTLOG( "not find doRun sym " );
}
Dl_info info;
dladdr(LocDoRunSym,&info);
INJECTLOG("loc doRun info [%s] fbase -> %p offset %p ",
info.dli_fname,info.dli_fbase,(void*)((size_t)LocDoRunSym-(size_t)info.dli_fbase)
);
void* remoteDoRunSym = remote_findFunction(library, LocDoRunSym);
if (LocDoRunSym != NULL && remoteDoRunSym != NULL) {
//INJECTLOG("start call doRun -> %p -> [%s]", remoteDoRunSym, param);
uintptr_t pdata = remote_string(param);
uintptr_t ptype = remote_string(type);
//remote_call(remoteDoRunSym, 2, _javavm, pmem);
//return 0 is success ~!
auto call_dorun_ret = remote_call(remoteDoRunSym, 2,ptype, pdata);
//auto call_dorun_ret = remote_call(remoteDoRunSym, 0);
remote_free(pdata);
remote_free(ptype);
//INJECTLOG("doRun call finish %lu %p",call_dorun_ret,(void*)call_dorun_ret);
ret = call_dorun_ret;
}
} else {
char error[1024] = { 0 };
remote_dlerror(error, 1024);
INJECTLOG( "remote_dlopen failed: %s", error );
}
// detach from target process
remote_stop();
trace( "detach", PTRACE_DETACH );
// let all threads in the target process continue
kill( _pid, SIGCONT );
INJECTLOG( "<<<<<<<< ptrace finish >>>>>>>> ");
} else {
INJECTLOG( "Failed to attach to process %d", _pid);
}
return ret;
}
下面说的远端函数指的是被注入的进程的函数地址。
1、所以我们需要得到目标进程dlopen,malloc (用于分配字符串)的地址。
/*
* Compute the delta of the local and the remote modules and apply it to the local address of the symbol ...
* 计算本地和远程模块的增量,并将其应用于符号的本地地址。。。
* BOOM, remote symbol address!
* BOOM,远程符号地址!
*/
static void* remote_findFunction(const char* library, void* local_addr) {
if(local_addr == nullptr){
INJECTLOGE("remote_findFunction %s location base %p",library,(void*)local_addr);
return nullptr;
}
uintptr_t remote_handle = findLibrary( library, -1 );
if(remote_handle == 0){
INJECTLOGE("remote_findFunction %s remote base == 0 ",library);
return nullptr;
}
uintptr_t local_handle = findLibrary( library, getpid() );
if(local_handle == 0){
//INJECTLOG("remote_findFunction local_handle == 0 %s loc %p %d %s ",library,local_addr,getpid(),getprogname());
Dl_info info;
if(dladdr(local_addr,&info)){
local_handle = (uintptr_t)info.dli_fbase;
} else{
INJECTLOGE("remote_findFunction local_handle backup == 0 ");
return nullptr;
}
}
uintptr_t offset = (uintptr_t) local_addr - local_handle;
uintptr_t remote_addr = remote_handle + offset;
return (void*)remote_addr;
}
2、远端进程打开我们自己的SO文件
3、Hack DRM
extern "C" {
__attribute__ ((visibility ("default")))
int
doRun(char *type, char *data) {
LOG(ERROR) << "start doRun !!! data ->" << data;
if (strcpy(type, "hack_drm")) {
#define SYM_THE_PATH "/vendor/lib64/libwvhidl.so"
LOG(ERROR) << "rep mock drm [" << fake_wvdrm_hash << "] orig [" << data << "]";
fake_wvdrm_hash = data;
if (!DrmIsHooked) {
//1.2&1.3&1.4
auto sym = getSymCompat(SYM_THE_PATH,
"_ZN5wvdrm8hardware3drm4V1_28widevine11WVDrmPlugin20CdmIdentifierBuilder17getDeviceUniqueIdEPNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEE");
if (sym == nullptr) {
sym = getSymCompat(SYM_THE_PATH,
"_ZN5wvdrm8hardware3drm4V1_38widevine11WVDrmPlugin20CdmIdentifierBuilder17getDeviceUniqueIdEPNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEE");
}
if (sym == nullptr) {
sym = getSymCompat(SYM_THE_PATH,
"_ZN5wvdrm8hardware3drm4V1_48widevine11WVDrmPlugin20CdmIdentifierBuilder17getDeviceUniqueIdEPNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEE");
}
if (sym == nullptr) {
LOG(ERROR) << "!!!!!!!!!! mock drm error ,not find sym !!!!!!!! ";
return 1;
}
auto ret = HookUtils::Hooker(sym, (void *) new_getDeviceUniqueId,
(void **) &orig_getDeviceUniqueId);
if (ret) {
DrmIsHooked = true;
LOG(ERROR) << "hack drm is success ! " << ret;
} else {
LOG(ERROR) << "hack drm is error ! ";
}
} else {
LOG(INFO) << "realy is hack drm " << fake_wvdrm_hash;
}
}
return 0;
}
}
Getprop指纹修改:
zeus:/ $ id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),1078(ext_data_rw),1079(ext_obb_rw),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid),3012(readtracefs) context=u:r:shell:s0
zeus:/ $ getprop | grep oem
[ro.fota.oem]: [Xiaomi]
[ro.ril.oem.imei]: [864489057004666]
[ro.ril.oem.imei1]: [864489057004666]
[ro.ril.oem.imei2]: [864489057004666]
[ro.ril.oem.meid]: [99001831350666]
[ro.ril.oem.psno]: [37331/21ZW02666]
[ro.ril.oem.sno]: [789752F14564695]
[sys.oem_unlock_allowed]: [0]
[persist.sys.boot.reason.history]: [reboot,userrequested,1716561955]
magisk resetprop ro.debuggable 1
我们直接把一台正常的手机,伪装成关闭selinux权限的 ,然后去故意暴露一个我修改过的ro.ril.oem.imei等,让目标App去读取 。 用这种方式差不点没给我笑死,竟然真的把一台已经封掉的手机给复活了。基本可以断定,很多大厂客户端采集的数据也没有确认 ,特别是 这种隐藏数据 。 他也是尝试去拿,而我们也尝试去给你伪造的 。这种imei这种数据在大厂的风控设备指纹里面比重很大,完全不经过确认是真是假,就被列入了指纹。
四
设备指纹采集反思&总结
1、我们可以自己去NDK里面把libc.so的动态库编译到我们自己的so里面 。而不走系统的libc。
2、我们应该尽可能的不要使用c++ 里面的string
3、单字段多形式获取
4、基础环境检测
但是这块也有弊端,logcat这种属于大数据 ,通过什么地方上报是个问题,如果是同步上报过去,肯定会很耗时 ,所以这块 可能还需要去设计,在制定场景下去通过socket这种发包的方式取上报拦截。
五
一机多号实现
SD卡里面还有/sdcard/Android/data/包名这些都是隶属于/sdcard下面的,但是他们两个的挂载位置是完全不一样的,所属的selinux权限也不一样 。 /sdcard 这种属于 Fuse(文件系统用户空间),但是/sdcard/Android/data/是属于F2FS(闪存友好文件系统)的 。
mount --bind 源文件目录 目标文件目录
六
对抗总结
问题1:
这块其实很简单,直接不让他读或者在内核写个模块把这个文件特征抹掉就好了,内核你都能随便修改了,整个手机规则的缔造者。 啥文件还不能修改呢。
问题2:
这个问题其实很简单,注入归注入,修改归修改,注入了什么也没修改 内存是无痕迹的 。 我们只需要在magisk模块加载完毕以后调用 api_->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); 把magisk模块so移除掉即可 。可能移除掉以后目标的Application还没开始执行调用就已经关闭了。 我们在目标进程没有修改任何代码,所以不存在函数跳转地址 ,我也没有inlinehook当前进程的任何so文件 。 内存里面也就不会再内存里面存在任何执行的段 ,并且任何SO文件的CRC也不发生变化。
问题3:
其实这块有一个很恶心的事情,就是shamiko+magisk修改包名 ,可以过掉国内99.999%的App root检测 。 shamiko会用chroot给你一个新的环境 ,是任何root痕迹都没有的 ,root检测其实很难的 ,解锁的手机也没办法直接封号 。 (之前老版本的shamiko可能有一些漏洞可以钻,但是新版本里面我找了很久也没找到好的洞 。) 因为现在市面上解锁的手机占比还是很大的 。解锁了也不一定代表作弊了。
问题4:
办法有很多 ,可以直接用自动点击的软件配合OCR文字识别即可 。看什么样的数据,如果是大文本数据可以直接通过抓包或者路由代理等直接解密即可 。 最次办法直接客户端注入lsplant直接hook对应函数,拿到数据 。 这块lsplant hook crc也不一定发生变化的,因为我之前把他inlinehook那块注释掉,发现hook还是生效的,他那个inlinehook主要是为了解决 一些被oat了短指令的函数 。 这块还有一个新的技术点可以用内核里面的硬件断点去实现inlinehook 。也是无痕的hook方式一种,不过好像hook的方法数量有限。 这里感谢B哥提供的硬段思路,B哥太强了!
问题5:
可以把手机放在全国各地,连接上USB能实现自动点击即可 , 流量使用物联卡的流量即可。 解决聚集性策略。这块需要逐步发展,手机数量是在之前满足的情况下,在进行扩增。
七
结尾
看雪ID:珍惜Any
https://bbs.kanxue.com/user-home-819934.htm
# 往期推荐
2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge
3、银狐样本分析
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):自动化采集Android系统级设备指纹对抗&如何四两拨千斤?