反外挂的一些简单思路

对于Linux/Android环境下的一些反外挂简单思路

外挂技术中, 最常用到的Hooking, 代码注入 等技术
通过ptrace函数读取到目标进程的内存信息, 注入dll, 劫持系统函数

针对这些手段, 有一些比较常见的防御手段

  • 模块完整性保护 : 定时扫描进程中使用的so库, 如果出现白名单之外的so库, 那么就说明遭到了代码注入
  • 调试标记符号读取 : 读取/proc/pid/status文件, 文件中的TracerPid如果不为0, 说明进程正在被调试
  • 函数指令内容检测 : 进程初始化时, 读取一部分系统关键函数的指令, 计算并保存指令的CRC32值, 之后定时扫描这些系统函数, CRC32校验失败说明这些函数的指令遭到了修改
  • hook 系统函数 : 进程初始化时, hook一部分系统函数, 修改其内容, 监控这些函数调用

模块完整性保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取当前进程使用的所有so
char maps_file[32];
sprintf(maps_file, "/proc/%d/maps", getpid());
FILE* fp = fopen(maps_file, "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
char* path = strstr(line, ".so");
if (path != NULL) {
char* end = strstr(path, " ");
*end = '\0';
printf("%s\n", path);
}
}
fclose(fp);

调试标记符号读取

1
2
3
4
5
6
7
8
9
10
// 读取/proc/pid/status文件, 检查TracerPid项
int pid = getpid();
std::ifstream status_file("/proc/" + std::to_string(pid) + "/status");
std::string line;
while (getline(status_file, line)) {
if (line.find("TracerPid:") == 0) {
int tracer_pid = stoi(line.substr(line.find("\t") + 1));
return tracer_pid != 0;
}
}

函数指令内容检测

1
2
3
4
5
6
7
// 从动态库中加载函数, 通过dladdr函数信息, 然后计算crc32
void * handle = dlopen(so, RTLD_LAZY);
void * addr = dlsym(handle, method);
Dl_info dlinfo;
dladdr(addr, &dlinfo);
size_t size = (unsigned int)((char*)dlinfo.dli_saddr - (char*)dlinfo.dli_fbase);
_crc32(dlinfo.dli_fbase, size);

hook
github上有很多开源的hook框架, 例如xHook, frida等等, 这里不做详细说明