2025 腾讯游戏安全大赛 mobile 决赛 wp
输麻了,挂哥乱杀的比赛。。。
外挂功能分析
cheat
cheat
程序开局执行 am start -n com.ACE2025.Game/com.epicgames.ue4.SplashActivity
打开游戏,并在3s后进入函数主逻辑

0x000000000241BF0 -> 0x000000000247504:(我之后所表示的地址,默认基址都为 0x5555555000)
这里的函数主要是判断外挂的控件是否被勾选或触发。

我们点击初始化辅助后,
cheat
程序就会进入Initialize_the_plugin
,来进行自瞄
与透视
前的初始化工作。
比如说获取libUE4.so
的基地址、获取GName
绝对地址、获取并保存第一人称角色Actor地址
,获取PlayerCameraManager
对象地址、视觉转化矩阵
。
透视功能的实现
程序在 000000555579C530 => 00000055557978D4
的位置获取了手机屏幕的宽和高。
之后的关键逻辑在 000000555579923C
处的函数实现
首先这里检测了是否开启了自瞄,如果开启了自瞄,那么程序将会调用 draw_yellow_circle => 0x0000055557C90B0 =>0x0000055557C6D34
来绘制黄色圆圈。
就是这个黄色框:

这里获取了摄像机的世界坐标以及rotation
这里获取第三人称的世界坐标
从这里开始一直到1300多行的 foot_r
,这些都是在读取第三人称身体部位的坐标,包括:头、脊椎、左上臂、右上臂、左前臂、右前臂、左手、右手、左大腿、右大腿、左脚、右脚等等。

实际上是为了绘制人物骨骼做准备,如下图
从 1367到1536的代码,是为了把这些世界坐标转化为手机平面坐标。
这里是自瞄位置的坐标转化,mb_selected___position
的值为 0、1、2,分别代表瞄准胸部、头部、臀部。
再往后就是绘制方式的选择
box_selected=0、1、2
分别代表绘制矩形方阵
、四角方针
、立方方阵
。
再之后就是选择是否绘制人物射线、人物骨骼了,人物骨骼
的绘制代码很多,毕竟需要绘制非常多的部位。
可以发现这里射线的起点给的是固定的屏幕值,transformer_and_draw
函数主要功能就是对屏幕上的两个点之间进行线段绘制
。
这就是外挂上透视的各个功能实现原理。
自瞄的功能实现
首先看这里:0x0000055557A331C
在外挂程序打开并且还未开启辅助的时候,就会执行这里的函数
其功能大致如下:
- 在
/dev/input
目录下获取了触摸屏的驱动设备(名称为fts
),并启用设备抓取。 - 之后创建了一个新的驱动设备,设备名称为随机的8字节字符串。并配置好设备事件。用来模拟触摸屏、按键等等。
- 在辅助关闭的时候,恢复设备抓取,删除新的驱动设备。

之后,由新创建的驱动设备来代替实现 fts
的功能。实际上,cheat
程序也是通过操纵新驱动设备的读写来实现拖动触摸屏自瞄的。我们继续分析。
0000005555798810
处的函数
这里判断是否在黄圈内。如果在黄圈内,则继续往下执行。
这里获取视觉转化矩阵。
随后把敌人要射击的身体部位坐标转化为屏幕坐标,进入 self_aim_func
函数来进行处理自瞄。
随后 self_aim_func=>0x0000055557A3268=>0x0000055557A21DC
进入了新的虚拟设备驱动处理函数。
通过向驱动设备写入数据来拖动屏幕
实现自瞄射击。
ACEInject
这是一个Zygisk
模块。关键逻辑在 libGame.so
文件中。
主逻辑在 0x00000000000111C
处的函数。
可以看到,程序获取了libUE4.so
文件的坐标,并且在 libUE4.sO + 0x6711AC4
位置处修改了会被代码
修改后:
之后程序进入了反调试的死循环里:

1、/proc/self/stat 反调试,读进程状态,判断进程是否被调试。
2、端口检测,检测 27043与27044端口有没有被使用
3、/proc/self/status 反调试,读取TracerPid,如果TracerPid 不为0,则进程正在被调试
4、check_gmain_frida,遍历进程的 /proc/self/task/ 目录,检查每个线程的 comm 文件中是否有与 gmain 或 pool-frida 相关的字符串,来找frida调试的痕迹。
5、check_frida,通过读取 /proc/self/maps来判断是否存在frida。
6、frida hook时的特征检测,
(1)、检查指定地址 a1 是否来自 libc.so 或 libdl.so。Frida 注入的钩子通常会用到这些库,尤其是 dlsym、dlopen 和 fopen 等函数。因此,如果目标地址的符号来自这两个库,函数会判断为可能的调试行为。
(2)、0x580000、0xD61F0、实际上是 检查 `xx 00 00 58 xx x0 1f d6`这种inline hook 的特征码
如果程序检测到反调试,则会立马终止外挂程序。
ACEInject 外挂检测
crc内存校验
新建线程,不停的循环检测即可.这里提前计算出了代码段的crc32值,之后线程只有循环检测即可。检测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [crc check], zgisk hack!
void* check_magisk1_crc_verify(void* arg){
uint32 last_crc = 0xe9838d80; // 0xa10ec2ab
while (1) {
sleep(4);
uint64 libUE4_base = get_base_so("libUE4.so");
log("libUE4_base: %llx", libUE4_base);
uint64 text_start = libUE4_base + 0x000000005AE7000;
uint64 text_end = libUE4_base + 0x00000000A4EAC50;
uint64 text_size = text_end - text_start;
unsigned int current_crc = cal_mem_crc32(text_start, text_size);
log("current crc: %x", current_crc);
if (last_crc !=0){
if(last_crc != current_crc){
wlog("[crc check], zgisk hack!");
log("[crc check], zgisk hack!");
}
}
}
return NULL;
}
段权限检查
写个循环不断检查段的权限,因为zgisk程序会修改代码段属性。
测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [seg permission check], zgisk hack!
void *check_magisk2_check_segment__permission(void* arg){
uint64 libUE4_base = get_base_so("libUE4.so");
uint64 invalid_seg = libUE4_base + 0x000000006711AC4;
log("invalid_seg: %llx", invalid_seg);
// 查询invalid_seg地址的权限
while (1){
FILE *fp = fopen("/proc/self/maps", "r");
if (fp == NULL) {
log("Failed to open /proc/self/maps");
return NULL;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, "libUE4.so")) {
unsigned long start, end;
sscanf(line, "%lx-%lx", &start, &end);
if (invalid_seg >= start && invalid_seg < end) {
// 该地址在libUE4.so的映射范围内
// 检查权限
if (strstr(line, "rwxp")) {
wlog("[seg permission check], zgisk hack!");
log("[seg permission check], zgisk hack!");
}
}
}
}
fclose(fp);
sleep(5);
}
return NULL;
}
inotify 监控
inotify
可以监控/proc/self/stat
、/proc/self/status
, /proc/self/maps
等等,我这里给出监控 /proc/self/maps
的例子:
测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [inotify maps check], find cheat hack!
void* check_maps(void * arg){
int fd, wd_mem;
char buffer[BUF_LEN];
char mem_file[128] = "/proc/self/maps";
sleep(3);
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init");
return ;
}
// 监视 /proc/<pid>/maps 和 /proc/<pid>/mem 文件的打开和修改操作
wd_mem = inotify_add_watch(fd, mem_file, IN_ACCESS | IN_MODIFY | IN_OPEN | IN_CLOSE_WRITE);
if (wd_mem == -1) {
log("inotify_add_watch error3");
return NULL;
}
log("Monitoring /proc/self/mem for access and modifications...\n");
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error ");
return NULL;
}
// 处理读取到的事件
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_ACCESS) {
log("File %s was accessed.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
if (event->mask & IN_MODIFY) {
log("File %s was modified.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
if (event->mask & IN_OPEN) {
log("File %s was opened.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
if (event->mask & IN_CLOSE_WRITE) {
log("File %s was closed after write.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
// log("just circle");
i += sizeof(struct inotify_event) + event->len;
}
}
// 关闭 inotify 实例
close(fd);
}
cheat 外挂检测
mincore检测
设置陷阱内存页。经过寻找与调试,找到了 so_base + 0xAF75B08;
测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [mincore check], find cheat hack!
uint64 mincore_one(){
uint64 so_base = get_base_so("libUE4.so");
uint64 detect_addr1 = so_base + 0xAF75B08;
log("detect_addr1: %llx", detect_addr1);
return detect_addr1;
}
// mincore 钓鱼吧 / /proc/self/pagemap
void* check_mincore(void* arg){
sleep(3);
// 页面地址替换,先睡3s,让系统进行完所有的初始化,我们再写。
int num_pages = 1;
uint64 mincore_check_list[num_pages];
mincore_check_list[0] = mincore_one();
uint64 mincore_mem_addr[num_pages];
size_t PS = sysconf(_SC_PAGESIZE); //获取系统的页面大小
for(int i = 0; i < num_pages; ++i) {
mincore_mem_addr[i] = (uint64)mmap(nullptr, PS, PROT_READ | PROT_WRITE,
MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if ((void*)mincore_check_list[i] == MAP_FAILED) {
log("Failed to allocate memory for mincore check list");
return NULL;
}
}
for(int i = 0; i < num_pages; ++i) {
*(uint64 *)mincore_check_list[i] = mincore_mem_addr[i];
log("mincore_check_list[%d]: %llx = mincore_mem_addr[x]: %llx", i, mincore_check_list[i], mincore_mem_addr[i]);
}
// 循环检测
while(1){
sleep(3);
for(int i = 0; i < num_pages; ++i) {
unsigned char vec[2];
int res = mincore((void*)mincore_mem_addr[i], PS, vec);
if (res == -1) {
log("mincore failed");
return NULL;;
}
log("mincore check: %d", (vec[0] & 1));
if ((vec[0] & 1) == 1) {
wlog("[mincore check], find cheat hack! ");
log("[mincore check], find cheat hack!");
return NULL; ;
}
}
}
}
inotify 监控 /proc/self/maps
同ACEInject
检测中的 inotify
监控方法
void* check_maps(void * arg){
int fd, wd_mem;
char buffer[BUF_LEN];
char mem_file[128] = "/proc/self/maps";
sleep(3);
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init");
return ;
}
// 监视 /proc/<pid>/maps 和 /proc/<pid>/mem 文件的打开和修改操作
wd_mem = inotify_add_watch(fd, mem_file, IN_ACCESS | IN_MODIFY | IN_OPEN | IN_CLOSE_WRITE);
if (wd_mem == -1) {
log("inotify_add_watch error3");
return NULL;
}
log("Monitoring /proc/self/mem for access and modifications...\n");
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error ");
return NULL;
}
// 处理读取到的事件
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_ACCESS) {
log("File %s was accessed.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
if (event->mask & IN_MODIFY) {
log("File %s was modified.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
if (event->mask & IN_OPEN) {
log("File %s was opened.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
if (event->mask & IN_CLOSE_WRITE) {
log("File %s was closed after write.\n", event->name);
wlog("[inotify maps check], find cheat hack!");
}
// log("just circle");
i += sizeof(struct inotify_event) + event->len;
}
}
// 关闭 inotify 实例
close(fd);
}
inotify监控 /dev/input
事实上,通过监控 /dev/uinput
也能达到同样效果
需要在 Permissive
模式下才能执行,即需要提前开启setenforce 0
测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [inotify input check], find cheat xxx!
// 监控驱动设备的删除与打开,
void *check_detect_device(void* arg){
int fd, wd;
char buffer[BUF_LEN];
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init error");
return NULL ;
}
// UI_DEV_CREATE
// 监视 /dev/input 目录下的文件
wd = inotify_add_watch(fd, "/dev/input", IN_OPEN | IN_CREATE | IN_DELETE);
if (wd == -1) {
log("inotify_add_watch error: %s\n", strerror(errno));
return NULL ;
}
log("Monitoring /dev/uinput for access and modification events...\n");
// 持续监听事件
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error !!");
return NULL ;
}
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_OPEN) {
log("File %s was opened.\n", event->name);
}
if (event->mask & IN_CREATE) {
log("File %s was created.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify input check], find cheat open! %s create.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_DELETE) {
log("File %s was deleted.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify input check], find cheat close! %s delete.",event->name);
wlog(str);
free(str);
}
i += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
return NULL ;
}
检测虚拟设备的名称与数目
在.so文件刚被加载的时候获取虚拟设备的数目与名称,之后每隔一段时间,检测虚拟驱动设备的数目与名称,当虚拟设备的数目增加且名称为 8 字节长度的时候,判定为外挂程序。
需要root
权限
测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [input devices check], find cheat open! xxx
void res_device_name() {
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input");
return;
}
struct dirent *entry;
char device_name[MAX_NAME_LEN];
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
log("Device name: %s", device_name);
res_device_name_list[res_device_count] = (char *)malloc(MAX_NAME_LEN);
strncpy(res_device_name_list[res_device_count], device_name, MAX_NAME_LEN - 1);
res_device_name_list[res_device_count][MAX_NAME_LEN - 1] = '\0'; // 确保字符串以 null 结尾
res_device_count++;
}
}
log("res_device_count: %d", res_device_count);
closedir(dir);
}
void * check_input_devices(void* arg) {
while(1){
sleep(3);
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input: %s", strerror(errno));
log("error code: %s", strerror(errno));
return NULL;
}
struct dirent *entry;
char device_name[MAX_NAME_LEN];
int device_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
int tag = 0;
for(int i = 0;i<res_device_count;i++){
if (strcmp(device_name, res_device_name_list[i]) == 0) {
tag = 1;
}
}
if(tag == 0){
if(strlen(device_name) == 8){
char *str = (char *)malloc(128);
sprintf(str, "[input devices check], find cheat open! %s.",device_name);
wlog(str);
free(str);
}
}
device_count++;
}
}
closedir(dir);
}
}
检测虚拟设备抓取
利用的是这个原理,cheat
在启动过程中对某一虚拟设备进行了设备抓取。
测到外挂后,向/data/ldata/com.ACE2025.Game/log.txt
文件中写入 [grab check], find cheat open!
那我们只需要每隔一段时间检测设备抓取状态,即可检测出cheat
程序。需要root
权限
void check_EVIOCGRAB(){
DIR *dir;
struct dirent *entry;
int handle;
int result;
// 打开 /dev/input 目录
dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input directory");
return;
}
// 遍历目录中的所有文件
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[256];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
handle = open(device_path, O_RDWR);
if (handle == -1) {
log("Failed to open device");
continue; // 继续处理下一个设备
}
// 尝试启用设备抓取
result = ioctl(handle, EVIOCGRAB, 1);
if (result == -1) {
log("Failed to grab device");
char *str = (char *)malloc(128);
sprintf(str, "[grab check], find cheat open! %s.",device_path);
free(str);
wlog(str);
} else {
log("Successfully grabbed device");
// 禁用抓取(释放设备)
ioctl(handle, EVIOCGRAB, 0);
}
// 关闭设备文件
close(handle);
}
}
// 关闭目录
closedir(dir);
}
代码编写
非root下
非root
模式下有cec内存校验
,段权限检测
, mincore检测
, inotify检测 maps
,inotify监测 /dev/input(如果开了Permissive模式的话)
可用。对应展示视频no_root.mp4
#include <android/log.h>
#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <dirent.h>
#include <unistd.h>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <string>
#include <list>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <getopt.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/inotify.h>
#include <fcntl.h>
#include <linux/input.h>
#define MAX_NAME_LEN 256
#define TAG "tlsn"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
#define log(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
typedef unsigned char uint8;
typedef unsigned short int uint16;
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef signed char int8;
typedef signed short int int16;
typedef signed int int32;
typedef signed long long int64;
typedef uintptr_t kaddr;
#define BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
void *check_magisk(void* arg);
uint64 GWorld = 0xAFAC398;
uint64 GNAME = 0xADF07C0;
uint64 GUObjectArray = 0xAE34A98;
void init_main_func();
char* get_current_time() ;
__attribute__((section(".init_array")))
void (*my_init_array[])() = { init_main_func };
char *res_device_name_list[20];
int res_device_count =0;
const char* logpath = "/data/data/com.ACE2025.Game/log.txt";
FILE *logfp = NULL;
void init_log_file(){
logfp = fopen(logpath, "w+");
if (logfp == NULL) {
LOGE("Failed to open log file: %s", logpath);
return;
}
}
void wlog(const char *str){
if (logfp == NULL) {
LOGE("Log file not opened");
return;
}
char *now_time= get_current_time();
fprintf(logfp, "[+] %s\t%s\n",now_time ,str);
fflush(logfp);
free(now_time);
}
void close_log_file(){
if (logfp != NULL) {
fclose(logfp);
logfp = NULL;
}
}
char* get_current_time() {
time_t raw_time;
struct tm * time_info;
char *time_buffer = (char*)malloc(80);
time(&raw_time); // 获取当前时间(秒)
time_info = localtime(&raw_time); // 转换为本地时间
// 将时间格式化为字符串
strftime(time_buffer, 80, "%Y-%m-%d %H:%M:%S", time_info);
return time_buffer;
}
uint64 so_base = 0;
uint64 get_base_so(const char* so_name){
if(so_base != 0){
return so_base;
}else{
FILE *fp = fopen("/proc/self/maps", "r");
if (fp == NULL) {
log("Failed to open /proc/self/maps");
return -1;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, so_name)) {
unsigned long start, end;
sscanf(line, "%lx-%lx", &start, &end);
fclose(fp);
return start;
}
}
fclose(fp);
}
return 0;
}
static const unsigned int crc32_table[] =
{
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
unsigned int xcrc32 (const unsigned char *buf, int len, unsigned int init)
{
unsigned int crc = init;
while (len--)
{
crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ *buf) & 255];
buf++;
}
return crc;
}
uint32 cal_mem_crc32(uint64 addr, uint32 size) {
unsigned char* mem_data = (unsigned char *)malloc(size + 10);
memcpy(mem_data, (void*)addr, size);
if (mem_data == NULL) {
LOGE("Failed to allocate memory for CRC calculation");
return -1;
}
unsigned int init_value = 0xFFFFFFFF;
unsigned int crc_value = xcrc32(mem_data, size, init_value);
free(mem_data);
return crc_value;
}
void* check_magisk1_crc_verify(void* arg){
uint32 last_crc = 0xe9838d80; // 0xa10ec2ab
while (1) {
sleep(4);
uint64 libUE4_base = get_base_so("libUE4.so");
log("libUE4_base: %llx", libUE4_base);
uint64 text_start = libUE4_base + 0x000000005AE7000;
uint64 text_end = libUE4_base + 0x00000000A4EAC50;
uint64 text_size = text_end - text_start;
unsigned int current_crc = cal_mem_crc32(text_start, text_size);
log("current crc: %x", current_crc);
if (last_crc !=0){
if(last_crc != current_crc){
wlog("[crc check], zgisk hack!");
log("[crc check], zgisk hack!");
}
}
}
return NULL;
}
void *check_magisk2_check_segment__permission(void* arg){
uint64 libUE4_base = get_base_so("libUE4.so");
uint64 invalid_seg = libUE4_base + 0x000000006711AC4;
log("invalid_seg: %llx", invalid_seg);
// 查询invalid_seg地址的权限
while (1){
FILE *fp = fopen("/proc/self/maps", "r");
if (fp == NULL) {
log("Failed to open /proc/self/maps");
return NULL;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, "libUE4.so")) {
unsigned long start, end;
sscanf(line, "%lx-%lx", &start, &end);
if (invalid_seg >= start && invalid_seg < end) {
// 该地址在libUE4.so的映射范围内
// 检查权限
if (strstr(line, "rwxp")) {
wlog("[seg permission check], zgisk hack!");
log("[seg permission check], zgisk hack!");
}
}
}
}
fclose(fp);
sleep(5);
}
return NULL;
}
void test_log_file(){
wlog("start check ...\n");
}
uint64 mincore_one(){
uint64 so_base = get_base_so("libUE4.so");
uint64 detect_addr1 = so_base + 0xAF75B08;
log("detect_addr1: %llx", detect_addr1);
return detect_addr1;
}
// mincore 钓鱼吧
void* check_mincore(void* arg){
sleep(3);
// 页面地址替换,先睡3s,让系统进行完所有的初始化,我们再写。
int num_pages = 1;
uint64 mincore_check_list[num_pages];
mincore_check_list[0] = mincore_one();
uint64 mincore_mem_addr[num_pages];
size_t PS = sysconf(_SC_PAGESIZE); //获取系统的页面大小
for(int i = 0; i < num_pages; ++i) {
mincore_mem_addr[i] = (uint64)mmap(nullptr, PS, PROT_READ | PROT_WRITE,
MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if ((void*)mincore_check_list[i] == MAP_FAILED) {
log("Failed to allocate memory for mincore check list");
return NULL;
}
}
for(int i = 0; i < num_pages; ++i) {
*(uint64 *)mincore_check_list[i] = mincore_mem_addr[i];
log("mincore_check_list[%d]: %llx = mincore_mem_addr[x]: %llx", i, mincore_check_list[i], mincore_mem_addr[i]);
}
// 循环检测
while(1){
sleep(3);
for(int i = 0; i < num_pages; ++i) {
unsigned char vec[2];
int res = mincore((void*)mincore_mem_addr[i], PS, vec);
if (res == -1) {
log("mincore failed");
return NULL;;
}
log("mincore check: %d", (vec[0] & 1));
if ((vec[0] & 1) == 1) {
wlog("[mincore check], find cheat hack! ");
log("[mincore check], find cheat hack!");
return NULL; ;
}
}
}
}
// 能监听 /proc/self/maps,但监听不了 /proc/self/mem?
void* check_maps(void * arg){
int fd, wd_mem;
char buffer[BUF_LEN];
char mem_file[128] = "/proc/self/maps";
sleep(3);
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init");
return NULL;
}
// 监视 /proc/<pid>/maps 和 /proc/<pid>/mem 文件的打开和修改操作
wd_mem = inotify_add_watch(fd, mem_file, IN_ACCESS );
if (wd_mem == -1) {
log("inotify_add_watch error3");
return NULL;
}
log("Monitoring /proc/self/mem for access and modifications...\n");
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error ");
return NULL;
}
// 处理读取到的事件
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_ACCESS) {
log("/proc/self/maps/ %s was accessed. mb cheat / zgisk\n", event->name);
wlog("[inotify /proc/self/maps/ check], find cheat hack!");
}
// log("just circle");
i += sizeof(struct inotify_event) + event->len;
}
}
// 关闭 inotify 实例
close(fd);
}
// 检测设备抓取。通过 ioctl(handle, EVIOCGRAB, 1) 启用抓取,捕获返回的错误码。需要root权限
// 需要 root权限
void* check_EVIOCGRAB(void* arg){
while(1){
DIR *dir;
struct dirent *entry;
int handle;
int result;
sleep(3);
// 打开 /dev/input 目录
dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input directory , mb u need root ");
return NULL;
}
// 遍历目录中的所有文件
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[256];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
handle = open(device_path, O_RDWR);
if (handle == -1) {
log("Failed to open device, mb u need root");
continue; // 继续处理下一个设备
}
// 尝试启用设备抓取
result = ioctl(handle, EVIOCGRAB, 1);
if (result == -1) {
log("Failed to grab device");
char *str = (char *)malloc(128);
sprintf(str, "[grab check], find cheat open! %s.",device_path);
free(str);
wlog(str);
} else {
log("Successfully grabbed device");
// 禁用抓取(释放设备)
ioctl(handle, EVIOCGRAB, 0);
}
// 关闭设备文件
close(handle);
}
}
// 关闭目录
closedir(dir);
}
}
void get_device_name(const char *device, char *name) {
log("name: %s", name);
int fd = open(device, O_RDONLY);
if (fd == -1) {
log("Failed to open device");
return;
}
// 获取设备名称
if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) == -1) {
log("Failed to get device name");
}
close(fd);
}
// 保存设备名称与数目
void res_device_name() {
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input");
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
char device_name[MAX_NAME_LEN]={0};
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
log("Device name: %s", device_name);
strcpy(res_device_name_list[res_device_count], device_name);
res_device_count++;
}
}
log("res_device_count: %d", res_device_count);
closedir(dir);
}
void * check_input_devices(void* arg) {
while(1){
sleep(3);
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input: %s , mb u need root", strerror(errno));
return NULL;
}
struct dirent *entry;
char device_name[MAX_NAME_LEN];
int device_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
int tag = 0;
for(int i = 0;i<res_device_count;i++){
if (strcmp(device_name, res_device_name_list[i]) == 0) {
tag = 1;
}
}
if(tag == 0){
if(strlen(device_name) == 8){
char *str = (char *)malloc(128);
sprintf(str, "[input devices check], find cheat open! %s.",device_name);
wlog(str);
free(str);
}
}
device_count++;
}
}
closedir(dir);
}
}
// 监控驱动设备的删除与打开,
void *check_detect_device(void* arg){
int fd, wd;
char buffer[BUF_LEN];
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init error");
return NULL ;
}
// UI_DEV_CREATE
// 监视 /dev/input 目录下的文件
wd = inotify_add_watch(fd, "/dev/input", IN_CREATE | IN_DELETE);
if (wd == -1) {
log("inotify_add_watch error: %s\n, mb u need root", strerror(errno));
return NULL ;
}
log("Monitoring /dev/uinput for access and modification events...\n");
// 持续监听事件
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error !!");
return NULL ;
}
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_CREATE) {
log("File %s was created.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify input check], find cheat open! %s create.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_DELETE) {
log("File %s was deleted.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify input check], find cheat close! %s delete.",event->name);
wlog(str);
free(str);
}
i += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
return NULL ;
}
// 需要 root 权限
void check_uinput(){
int fd, wd;
char buffer[BUF_LEN];
sleep(3);
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init");
return ;
}
// 监视 /dev/input 目录下的文件
wd = inotify_add_watch(fd, "/dev/uinput", IN_OPEN | IN_CREATE | IN_DELETE);
if (wd == -1) {
log("inotify_add_watch error1");
log("inotify_add_watch error: %s\n", strerror(errno));
return ;
}
log("Monitoring /dev/uinput for access and modification events...\n");
// 持续监听事件
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error !!");
return ;
}
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_OPEN) {
log("File %s was opened.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify uinput check], find cheat open! %s open.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_CREATE) {
log("File %s was create \n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify uinput check], find cheat create! %s open.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_DELETE) {
log("File %s was deleted.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify uinput check], find cheat delete! %s delete.",event->name);
wlog(str);
free(str);
}
i += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
}
// void *check_cheat(void* arg){
// check_mincore();
// check_detect_device();
// // check_maps();
// // check_EVIOCGRAB();
// // check_uinput();
// return NULL;
// }
void init_main_func() {
get_base_so("libUE4.so");
init_log_file();
test_log_file();
log("Load libAnswer.so");
pthread_t thread_id;
// 1、crc 内存校验
if (pthread_create(&thread_id, NULL, check_magisk1_crc_verify, NULL) != 0) {
log("Failed to create thread");
return ;
}
// 2、段权限检测
pthread_t thread_id2;
if (pthread_create(&thread_id2, NULL, check_magisk2_check_segment__permission, NULL) != 0) {
log("Failed to create thread");
return ;
}
// 3、inotify 监控,当然如果要架空 /proc/self/maps的话,就与 段权限检测冲突了。
// pthread_t thread_id3;
// if (pthread_create(&thread_id3, NULL, check_maps, NULL) != 0) {
// log("Failed to create thread3");
// return ;
// }
// 4、mincore检测
pthread_t thread_id4;
if (pthread_create(&thread_id4, NULL, check_mincore, NULL) != 0) {
log("Failed to create thread2");
return ;
}
// 5、inotify监控 /dev/input
pthread_t thread_id5;
if (pthread_create(&thread_id5, NULL, check_detect_device, NULL) != 0) {
log("Failed to create thread2");
return ;
}
// 6、检测虚拟设备的名称与数目
// res_device_name();
// pthread_t thread_id6;
// if (pthread_create(&thread_id6, NULL, check_input_devices, NULL) != 0) {
// log("Failed to create thread2");
// return ;
// }
// // 7、检测虚拟设备的抓取
// pthread_t thread_id7;
// if (pthread_create(&thread_id7, NULL, check_EVIOCGRAB, NULL) != 0) {
// log("Failed to create thread2");
// return ;
// }
}
// D:/tlsn/compiler/Android_studio/SDK/ndk/27.0.12077973/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android24-clang.cmd ./libAnswer.cpp -fPIC -shared -g -o ./libAnswer.so -llog
root下
另创建了个文件:need_root.cpp
,需要在 root权限下执行的函数,全部放在了这里,并在libAnsver.cpp
中通过 popen("su -c xxx","r")
进行执行与交互。
libAnsver.cpp
:
#include <android/log.h>
#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <dirent.h>
#include <unistd.h>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <string>
#include <list>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <getopt.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/inotify.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <linux/input.h>
#define MAX_NAME_LEN 256
#define TAG "tlsn"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
#define log(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
typedef unsigned char uint8;
typedef unsigned short int uint16;
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef signed char int8;
typedef signed short int int16;
typedef signed int int32;
typedef signed long long int64;
typedef uintptr_t kaddr;
#define BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
void *check_magisk(void* arg);
uint64 GWorld = 0xAFAC398;
uint64 GNAME = 0xADF07C0;
uint64 GUObjectArray = 0xAE34A98;
void init_main_func();
char* get_current_time() ;
__attribute__((section(".init_array")))
void (*my_init_array[])() = { init_main_func };
char *res_device_name_list[20];
int res_device_count =0;
const char* logpath = "/data/data/com.ACE2025.Game/log.txt";
FILE *logfp = NULL;
void init_log_file(){
logfp = fopen(logpath, "w+");
if (logfp == NULL) {
LOGE("Failed to open log file: %s", logpath);
return;
}
}
void wlog(const char *str){
if (logfp == NULL) {
LOGE("Log file not opened");
return;
}
char *now_time= get_current_time();
fprintf(logfp, "[+] %s\t%s\n",now_time ,str);
fflush(logfp);
free(now_time);
}
void close_log_file(){
if (logfp != NULL) {
fclose(logfp);
logfp = NULL;
}
}
char* get_current_time() {
time_t raw_time;
struct tm * time_info;
char *time_buffer = (char*)malloc(80);
time(&raw_time); // 获取当前时间(秒)
time_info = localtime(&raw_time); // 转换为本地时间
// 将时间格式化为字符串
strftime(time_buffer, 80, "%Y-%m-%d %H:%M:%S", time_info);
return time_buffer;
}
uint64 so_base = 0;
uint64 get_base_so(const char* so_name){
if(so_base != 0){
return so_base;
}else{
FILE *fp = fopen("/proc/self/maps", "r");
if (fp == NULL) {
log("Failed to open /proc/self/maps");
return -1;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, so_name)) {
unsigned long start, end;
sscanf(line, "%lx-%lx", &start, &end);
fclose(fp);
return start;
}
}
fclose(fp);
}
return 0;
}
static const unsigned int crc32_table[] =
{
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
unsigned int xcrc32 (const unsigned char *buf, int len, unsigned int init)
{
unsigned int crc = init;
while (len--)
{
crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ *buf) & 255];
buf++;
}
return crc;
}
uint32 cal_mem_crc32(uint64 addr, uint32 size) {
unsigned char* mem_data = (unsigned char *)malloc(size + 10);
memcpy(mem_data, (void*)addr, size);
if (mem_data == NULL) {
LOGE("Failed to allocate memory for CRC calculation");
return -1;
}
unsigned int init_value = 0xFFFFFFFF;
unsigned int crc_value = xcrc32(mem_data, size, init_value);
free(mem_data);
return crc_value;
}
void* check_magisk1_crc_verify(void* arg){
uint32 last_crc = 0xe9838d80; // 0xa10ec2ab
while (1) {
sleep(4);
uint64 libUE4_base = get_base_so("libUE4.so");
log("libUE4_base: %llx", libUE4_base);
uint64 text_start = libUE4_base + 0x000000005AE7000;
uint64 text_end = libUE4_base + 0x00000000A4EAC50;
uint64 text_size = text_end - text_start;
unsigned int current_crc = cal_mem_crc32(text_start, text_size);
log("current crc: %x", current_crc);
if (last_crc !=0){
if(last_crc != current_crc){
wlog("[crc check], zgisk hack!");
log("[crc check], zgisk hack!");
}
}
}
return NULL;
}
void *check_magisk2_check_segment__permission(void* arg){
uint64 libUE4_base = get_base_so("libUE4.so");
uint64 invalid_seg = libUE4_base + 0x000000006711AC4;
log("invalid_seg: %llx", invalid_seg);
// 查询invalid_seg地址的权限
while (1){
FILE *fp = fopen("/proc/self/maps", "r");
if (fp == NULL) {
log("Failed to open /proc/self/maps");
return NULL;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
if (strstr(line, "libUE4.so")) {
unsigned long start, end;
sscanf(line, "%lx-%lx", &start, &end);
if (invalid_seg >= start && invalid_seg < end) {
// 该地址在libUE4.so的映射范围内
// 检查权限
if (strstr(line, "rwxp")) {
wlog("[seg permission check], zgisk hack!");
log("[seg permission check], zgisk hack!");
}
}
}
}
fclose(fp);
sleep(5);
}
return NULL;
}
void test_log_file(){
wlog("start check ...\n");
}
uint64 mincore_one(){
uint64 so_base = get_base_so("libUE4.so");
uint64 detect_addr1 = so_base + 0xAF75B08;
log("detect_addr1: %llx", detect_addr1);
return detect_addr1;
}
// mincore 钓鱼吧
void* check_mincore(void* arg){
sleep(3);
// 页面地址替换,先睡3s,让系统进行完所有的初始化,我们再写。
int num_pages = 1;
uint64 mincore_check_list[num_pages];
mincore_check_list[0] = mincore_one();
uint64 mincore_mem_addr[num_pages];
size_t PS = sysconf(_SC_PAGESIZE); //获取系统的页面大小
for(int i = 0; i < num_pages; ++i) {
mincore_mem_addr[i] = (uint64)mmap(nullptr, PS, PROT_READ | PROT_WRITE,
MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
if ((void*)mincore_check_list[i] == MAP_FAILED) {
log("Failed to allocate memory for mincore check list");
return NULL;
}
}
for(int i = 0; i < num_pages; ++i) {
*(uint64 *)mincore_check_list[i] = mincore_mem_addr[i];
log("mincore_check_list[%d]: %llx = mincore_mem_addr[x]: %llx", i, mincore_check_list[i], mincore_mem_addr[i]);
}
// 循环检测
while(1){
sleep(3);
for(int i = 0; i < num_pages; ++i) {
unsigned char vec[2];
int res = mincore((void*)mincore_mem_addr[i], PS, vec);
if (res == -1) {
log("mincore failed");
return NULL;;
}
log("mincore check: %d", (vec[0] & 1));
if ((vec[0] & 1) == 1) {
wlog("[mincore check], find cheat hack! ");
log("[mincore check], find cheat hack!");
return NULL; ;
}
}
}
}
// 能监听 /proc/self/maps,但监听不了 /proc/self/mem?
void* check_maps(void * arg){
int fd, wd_mem;
char buffer[BUF_LEN];
char mem_file[128] = "/proc/self/maps";
sleep(3);
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init");
return NULL;
}
// 监视 /proc/<pid>/maps 和 /proc/<pid>/mem 文件的打开和修改操作
wd_mem = inotify_add_watch(fd, mem_file, IN_ACCESS );
if (wd_mem == -1) {
log("inotify_add_watch error3");
return NULL;
}
log("Monitoring /proc/self/mem for access and modifications...\n");
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error ");
return NULL;
}
// 处理读取到的事件
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_ACCESS) {
log("/proc/self/maps/ %s was accessed. mb cheat / zgisk\n", event->name);
wlog("[inotify /proc/self/maps/ check], find cheat hack!");
}
// log("just circle");
i += sizeof(struct inotify_event) + event->len;
}
}
// 关闭 inotify 实例
close(fd);
}
// 检测设备抓取。通过 ioctl(handle, EVIOCGRAB, 1) 启用抓取,捕获返回的错误码。需要root权限
// 需要 root权限
void* check_EVIOCGRAB(void* arg){
while(1){
DIR *dir;
struct dirent *entry;
int handle;
int result;
sleep(3);
// 打开 /dev/input 目录
dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input directory , mb u need root ");
return NULL;
}
// 遍历目录中的所有文件
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[256];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
handle = open(device_path, O_RDWR);
if (handle == -1) {
log("Failed to open device, mb u need root");
continue; // 继续处理下一个设备
}
// 尝试启用设备抓取
result = ioctl(handle, EVIOCGRAB, 1);
if (result == -1) {
log("Failed to grab device");
char *str = (char *)malloc(128);
sprintf(str, "[grab check], find cheat open! %s.",device_path);
free(str);
wlog(str);
} else {
log("Successfully grabbed device");
// 禁用抓取(释放设备)
ioctl(handle, EVIOCGRAB, 0);
}
// 关闭设备文件
close(handle);
}
}
// 关闭目录
closedir(dir);
}
}
void get_device_name(const char *device, char *name) {
int fd = open(device, O_RDONLY);
if (fd == -1) {
log("Failed to open device");
return;
}
// 获取设备名称
if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) == -1) {
log("Failed to get device name: %s", strerror(errno));
}
close(fd);
}
// 保存设备名称与数目
void res_device_name() {
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
printf("Failed to open /dev/input\n");
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
char device_name[MAX_NAME_LEN]={0};
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
printf("Device name: %s\n", device_name);
strcpy((char*)&res_device_name_list[res_device_count], device_name);
res_device_count++;
}
}
printf("res_device_count: %d\n", res_device_count);
closedir(dir);
}
void * check_input_devices(void* arg) {
while(1){
sleep(3);
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
log("Failed to open /dev/input: %s , mb u need root", strerror(errno));
return NULL;
}
struct dirent *entry;
char device_name[MAX_NAME_LEN];
int device_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
int tag = 0;
for(int i = 0;i<res_device_count;i++){
if (strcmp(device_name, res_device_name_list[i]) == 0) {
tag = 1;
}
}
if(tag == 0){
if(strlen(device_name) == 8){
char *str = (char *)malloc(128);
sprintf(str, "[input devices check], find cheat open! %s.",device_name);
wlog(str);
free(str);
}
}
device_count++;
}
}
closedir(dir);
}
}
// 监控驱动设备的删除与打开,
void *check_detect_device(void* arg){
int fd, wd;
char buffer[BUF_LEN];
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init error");
return NULL ;
}
// UI_DEV_CREATE
// 监视 /dev/input 目录下的文件
wd = inotify_add_watch(fd, "/dev/input", IN_CREATE | IN_DELETE);
if (wd == -1) {
log("inotify_add_watch error: %s\n, mb u need root", strerror(errno));
return NULL ;
}
log("Monitoring /dev/input for access and modification events...\n");
// 持续监听事件
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error !!");
return NULL ;
}
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_CREATE) {
log("File %s was created.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify input check], find cheat open! %s create.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_DELETE) {
log("File %s was deleted.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify input check], find cheat close! %s delete.",event->name);
wlog(str);
free(str);
}
i += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
return NULL ;
}
// 需要 root 权限
void check_uinput(){
int fd, wd;
char buffer[BUF_LEN];
sleep(3);
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
log("inotify_init");
return ;
}
// 监视 /dev/input 目录下的文件
wd = inotify_add_watch(fd, "/dev/uinput", IN_OPEN | IN_CREATE | IN_DELETE);
if (wd == -1) {
log("inotify_add_watch error1");
log("inotify_add_watch error: %s\n", strerror(errno));
return ;
}
log("Monitoring /dev/uinput for access and modification events...\n");
// 持续监听事件
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
log("read error !!");
return ;
}
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_OPEN) {
log("File %s was opened.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify uinput check], find cheat open! %s open.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_CREATE) {
log("File %s was create \n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify uinput check], find cheat create! %s open.",event->name);
wlog(str);
free(str);
}
if (event->mask & IN_DELETE) {
log("File %s was deleted.\n", event->name);
char *str = (char *)malloc(128);
sprintf(str, "[inotify uinput check], find cheat delete! %s delete.",event->name);
wlog(str);
free(str);
}
i += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
}
// void *check_cheat(void* arg){
// check_mincore();
// check_detect_device();
// // check_maps();
// // check_EVIOCGRAB();
// // check_uinput();
// return NULL;
// }
void get_file_path(char *dirpath) {
Dl_info dl_info;
if (dladdr((void*)get_file_path, &dl_info)) {
// 获取文件路径
char path[PATH_MAX] = {0};
strcpy(path, dl_info.dli_fname);
// 过滤得到目录
char *pppath = strrchr(path, '/');
if (pppath != NULL) {
*pppath = '\0'; // 将最后一个斜杠替换为字符串结束符
}
// 赋给dirpath
strcpy(dirpath, path);
// 打印文件路径
printf("File path: %s\n", dirpath);
} else {
printf("Failed to get library path.\n");
}
}
void* root_exec(void*arg){
while (1)
{
sleep(3);
char *dirpath = (char*)malloc(PATH_MAX);
char * nee_root_path = (char*)malloc(PATH_MAX);
get_file_path(dirpath);
strcpy(nee_root_path, dirpath);
strcat(nee_root_path, "/need_root");
log("need_root_path: %s\n", nee_root_path);
free(dirpath);
char command[256] = {0};
snprintf(command, sizeof(command), "su -c %s", nee_root_path);
free(nee_root_path);
FILE* fp = popen(command, "r");
char output[1024];
if (fp == NULL) {
log("popen failed");
return NULL;
}
// Read the output of the command line by line
while (fgets(output, sizeof(output), fp) != NULL) {
if(strstr(output,"[input devices check]") != NULL){
wlog(output);
log(output);
}else if (strstr(output,"[grab check]") != NULL){
wlog(output);
log(output);
}else if (strstr(output,"[inotify input check]") != NULL){
wlog(output);
log(output);
}
}
// Close the pipe
fclose(fp);
}
return NULL;
}
void init_main_func() {
get_base_so("libUE4.so");
init_log_file();
test_log_file();
log("Load libAnswer.so");
pthread_t thread_id;
// 1、crc 内存校验
if (pthread_create(&thread_id, NULL, check_magisk1_crc_verify, NULL) != 0) {
log("Failed to create thread");
return ;
}
// 2、段权限检测
pthread_t thread_id2;
if (pthread_create(&thread_id2, NULL, check_magisk2_check_segment__permission, NULL) != 0) {
log("Failed to create thread");
return ;
}
// 3、inotify 监控,当然如果要架空 /proc/self/maps的话,就与 段权限检测冲突了。
// pthread_t thread_id3;
// if (pthread_create(&thread_id3, NULL, check_maps, NULL) != 0) {
// log("Failed to create thread3");
// return ;
// }
// 4、mincore检测
pthread_t thread_id4;
if (pthread_create(&thread_id4, NULL, check_mincore, NULL) != 0) {
log("Failed to create thread2");
return ;
}
// // 5、inotify监控 /dev/input
// pthread_t thread_id5;
// if (pthread_create(&thread_id5, NULL, check_detect_device, NULL) != 0) {
// log("Failed to create thread2");
// return ;
// }
// // 6、检测虚拟设备的名称与数目
// // res_device_name();
// pthread_t thread_id6;
// if (pthread_create(&thread_id6, NULL, check_input_devices, NULL) != 0) {
// log("Failed to create thread2");
// return ;
// }
// // // 7、检测虚拟设备的抓取
// pthread_t thread_id7;
// if (pthread_create(&thread_id7, NULL, check_EVIOCGRAB, NULL) != 0) {
// log("Failed to create thread2");
// return ;
// }
// 使用root权限执行下面命令:
pthread_t thread_id8;
if(pthread_create(&thread_id8, NULL, root_exec, NULL) != 0) {
log("Failed to create thread2");
return ;
}
}
// adb shell pm path com.ACE2025.Game
// D:/tlsn/compiler/Android_studio/SDK/ndk/27.0.12077973/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android24-clang.cmd ./libAnswer.cpp -fPIC -shared -g -o ./libAnswer.so -llog
need_root.cpp
#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <dirent.h>
#include <unistd.h>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <string>
#include <list>
#include <vector>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <getopt.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/inotify.h>
#include <fcntl.h>
#include <linux/input.h>
#define MAX_NAME_LEN 256
#define TAG "tlsn"
typedef unsigned char uint8;
typedef unsigned short int uint16;
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef signed char int8;
typedef signed short int int16;
typedef signed int int32;
typedef signed long long int64;
typedef uintptr_t kaddr;
#define BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
char *res_device_name_list[20];
int res_device_count =0;
const char* printfpath = "/data/data/com.ACE2025.Game/printf.txt";
void get_device_name(const char *device, char *name) {
int fd = open(device, O_RDONLY);
if (fd == -1) {
printf("Failed to open device\n");
return;
}
// 获取设备名称
if (ioctl(fd, EVIOCGNAME(MAX_NAME_LEN), name) == -1) {
printf("Failed to get device name: %s\n", strerror(errno));
}
close(fd);
}
void * check_input_devices(void* arg) {
while(1){
sleep(3);
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
printf("Failed to open /dev/input: %s , mb u need root\n", strerror(errno));
return NULL;
}
struct dirent *entry;
char device_name[MAX_NAME_LEN];
int device_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
int tag = 0;
for(int i = 0;i<res_device_count;i++){
// printf("device_name: %s cmp %s \n", device_name, (const char*)res_device_name_list[i]);
if (strcmp(device_name, (const char*)res_device_name_list[i]) == 0) {
tag = 1;
}
}
if(tag == 0){
if(strlen(device_name) == 8){
char *str = (char *)malloc(128);
fflush(stdout);
sprintf(str, "[input devices check], find cheat open! %s.",device_name);
printf("%s\n", str);
fflush(stdout);
free(str);
}
}
device_count++;
}
}
closedir(dir);
}
}
void res_device_name() {
DIR *dir = opendir("/dev/input");
if (dir == NULL) {
printf("Failed to open /dev/input\n");
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
char device_name[MAX_NAME_LEN]={0};
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[PATH_MAX];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
// 获取设备名称
get_device_name(device_path, device_name);
res_device_name_list[res_device_count] = (char*)malloc(MAX_NAME_LEN);
strcpy(res_device_name_list[res_device_count], device_name);
// printf("Device name: %s\n", res_device_name_list[res_device_count]);
res_device_count++;
}
}
// printf("res_device_count: %d\n", res_device_count);
closedir(dir);
}
void* check_EVIOCGRAB(void* arg){
while(1){
DIR *dir;
struct dirent *entry;
int handle;
int result;
sleep(3);
// 打开 /dev/input 目录
dir = opendir("/dev/input");
if (dir == NULL) {
printf("Failed to open /dev/input directory , mb u need root ");
return NULL;
}
// 遍历目录中的所有文件
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
char device_path[256];
snprintf(device_path, sizeof(device_path), "/dev/input/%s", entry->d_name);
handle = open(device_path, O_RDWR);
if (handle == -1) {
// printf("Failed to open device, mb u need root");
continue; // 继续处理下一个设备
}
// 尝试启用设备抓取
result = ioctl(handle, EVIOCGRAB, 1);
if (result == -1) {
// printf("Failed to grab device");
char *str = (char *)malloc(128);
fflush(stdout);
sprintf(str, "[grab check], find cheat open! %s.",device_path);
free(str);
printf("%s\n",str);
fflush(stdout);
} else {
// printf("Successfully grabbed device");
// 禁用抓取(释放设备)
ioctl(handle, EVIOCGRAB, 0);
}
// 关闭设备文件
close(handle);
}
}
// 关闭目录
closedir(dir);
}
}
void *check_detect_device(void* arg){
int fd, wd;
char buffer[BUF_LEN];
// 创建 inotify 实例
fd = inotify_init();
if (fd == -1) {
printf("inotify_init error");
return NULL ;
}
// UI_DEV_CREATE
// 监视 /dev/input 目录下的文件
wd = inotify_add_watch(fd, "/dev/input", IN_CREATE | IN_DELETE);
if (wd == -1) {
printf("inotify_add_watch error: %s\n, mb u need root", strerror(errno));
return NULL ;
}
printf("Monitoring /dev/input for access and modification events...\n");
// 持续监听事件
while (1) {
int length = read(fd, buffer, BUF_LEN);
if (length == -1) {
printf("read error !!");
return NULL ;
}
int i = 0;
while (i < length) {
struct inotify_event *event = (struct inotify_event *) &buffer[i];
if (event->mask & IN_CREATE) {
// printf("File %s was created.\n", event->name);
char *str = (char *)malloc(128);
fflush(stdout);
sprintf(str, "[inotify input check], find cheat open! %s create.",event->name);
printf("%s\n",str);
fflush(stdout);
free(str);
}
if (event->mask & IN_DELETE) {
// printf("File %s was deleted.\n", event->name);
char *str = (char *)malloc(128);
fflush(stdout);
sprintf(str, "[inotify input check], find cheat close! %s delete.",event->name);
printf("%s\n",str);
fflush(stdout);
free(str);
}
i += sizeof(struct inotify_event) + event->len;
}
}
close(fd);
return NULL ;
}
int main(){
res_device_name();
pthread_t thread_id5;
if (pthread_create(&thread_id5, NULL, check_detect_device, NULL) != 0) {
printf("Failed to create thread2\n");
return 0;
}
pthread_t thread_id6;
if (pthread_create(&thread_id6, NULL, check_input_devices, NULL) != 0) {
printf("Failed to create thread2\n");
return 0;
}
pthread_t thread_id7;
if (pthread_create(&thread_id7, NULL, check_EVIOCGRAB, NULL) != 0) {
printf("Failed to create thread2\n");
return 0;
}
if(pthread_join(thread_id5, NULL) != 0) {
printf("Failed to join thread\n");
return 0;
}
if (pthread_join(thread_id6, NULL) != 0) {
printf("Failed to join thread\n");
return 0;
}
if (pthread_join(thread_id7, NULL) != 0) {
printf("Failed to join thread\n");
return 0;
}
}
// D:/tlsn/compiler/Android_studio/SDK/ndk/27.0.12077973/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android24-clang.cmd ./input_device.cpp -g -o ./input_device