|
|
本帖最后由 正己 于 2024-5-11 19:16 编辑

一、课程目标
1.了解常见frida检测
二、工具
1.教程Demo(更新)
2.jadx-gui
3.VS Code
4.jeb
三、课程内容
1.检测文件名、端口名、双进程保护、失效的检测点
1.检测/data/local/tmp路径下的是否有frida特征文件,server端改名,例如:fr
2.指定端口转发
- ./fs1 -l 0.0.0.0:6666
- adb forward tcp:6666 tcp:6666
- frida -H 127.0.0.1:6666 wuaipojie -l hook.js
复制代码
3.spawn启动过双进程保护
- frida -U -f 进程名 -l hook.js
复制代码
PS:学会看注入报错的日志,比如说当app主动附加自身进程时,这时候再注入就会提示`run frida as root`(以spawn的方式启动进程即可)
4.借助脚本定位检测frida的so
- function hook_dlopen() {
- Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
- {
- onEnter: function (args) {
- var pathptr = args[0];
- if (pathptr !== undefined && pathptr != null) {
- var path = ptr(pathptr).readCString();
- console.log("load " + path);
- }
- }
- }
- );
- }
复制代码
5.随着firda的版本迭代,以前诸多检测点以失效
(1.)例如`检测D-Bus`
D-Bus是一种进程间通信(IPC)和远程过程调用(RPC)机制,最初是为Linux开发的,目的是用一个统一的协议替代现有的和竞争的IPC解决方案。
- bool check_dbus() {
- // 定义一个socket地址结构体变量sa
- struct sockaddr_in sa;
- // 创建一个socket文件描述符
- int sock;
- // 定义一个字符数组res,用于存储接收到的数据
- char res[7];
- // 循环遍历所有可能的端口号,从0到65535
- for(int i = 0; i <= 65535; i++) {
- // 创建一个新的socket连接
- sock = socket(AF_INET, SOCK_STREAM, 0);
- // 设置socket地址结构体的端口号
- sa.sin_port = htons(i);
- // 尝试连接到当前端口
- if (connect(sock, (struct sockaddr*)&sa, sizeof(sa)) != -1) {
- // 如果连接成功,记录日志信息,表示发现了一个开放的端口
- __android_log_print(ANDROID_LOG_VERBOSE, "ZJ595", "FRIDA DETECTION [1]: Open Port: %d", i);
- // 初始化res数组,清零
- memset(res, 0, 7);
- // 向socket发送一个空字节
- send(sock, "\x00", 1, 0); // 注意这里的NULL被替换为0
- // 发送AUTH请求
- send(sock, "AUTH\r\n", 6, 0);
- // 等待100微秒
- usleep(100);
- // 尝试接收响应
- if (recv(sock, res, 6, MSG_DONTWAIT) != -1) {
- // 如果接收到响应,检查响应内容是否为"REJECT"
- if (strcmp(res, "REJECT") == 0) {
- // 如果是,关闭socket并返回true,表示检测到了Frida服务器
- close(sock);
- return true; // Frida server detected
- }
- }
- }
- // 如果当前端口连接失败或没有检测到Frida服务器,关闭socket
- close(sock);
- }
- // 如果遍历完所有端口都没有检测到Frida服务器,返回false
- return false; // No Frida server detected
- }
复制代码
(2)`检测fd`
/proc/pid/fd 目录的作用在于提供了一种方便的方式来查看进程的文件描述符信息,这对于调试和监控进程非常有用。通过查看文件描述符信息,可以了解进程打开了哪些文件、网络连接等,帮助开发者和系统管理员进行问题排查和分析工作。
- bool check_fd() {
- DIR *dir = NULL;
- struct dirent *entry;
- char link_name[100];
- char buf[100];
- bool ret = false;
- if ((dir = opendir("/proc/self/fd/")) == NULL) {
- LOGI(" %s - %d error:%s", __FILE__, __LINE__, strerror(errno));
- } else {
- entry = readdir(dir);
- while (entry) {
- switch (entry->d_type) {
- case DT_LNK:
- sprintf(link_name, "%s/%s", "/proc/self/fd/", entry->d_name);
- readlink(link_name, buf, sizeof(buf));
- if (strstr(buf, "frida") || strstr(buf, "gum-js-loop") ||
- strstr(buf, "gmain") ||
- strstr(buf, "-gadget") || strstr(buf, "linjector")) {
- LOGI("check_fd -> find frida:%s", buf);
- ret = true;
- }
- break;
- default:
- break;
- }
- entry = readdir(dir);
- }
- }
- closedir(dir);
- return ret;
- }
复制代码
(3)`检测文件`
众所周知frida我们一般都会放在data/local/tmp目录下,旧版fridaserver端运行时都会释放到re.frida.server,所以这里在旧版也会被当做一个检测点,而新版已不再释放
2检测map
- adb shell ps | findstr com.zj.wuaipojie
- cat /proc/12186/maps|grep frida
复制代码


`/proc/self/maps` 是一个特殊的文件,它包含了当前进程的内存映射信息。当你打开这个文件时,它会显示一个列表,其中包含了进程中每个内存区域的详细信息。这些信息通常包括:
- 起始地址(Start Address)
- 结束地址(End Address)
- 权限(如可读、可写、可执行)
- 共享/私有标志(Shared or Private)
- 关联的文件或设备(如果内存区域是文件映射的)
- 内存区域的偏移量
- 内存区域的类型(如匿名映射、文件映射、设备映射等)
当注入frida后,在maps文件中就会存在 `frida-agent-64.so`、`frida-agent-32.so` 等文件。
- bool check_maps() {
- // 定义一个足够大的字符数组line,用于存储读取的行
- char line[512];
- // 打开当前进程的内存映射文件/proc/self/maps进行读取
- FILE* fp = fopen("/proc/self/maps", "r");
- if (fp) {
- // 如果文件成功打开,循环读取每一行
- while (fgets(line, sizeof(line), fp)) {
- // 使用strstr函数检查当前行是否包含"frida"字符串
- if (strstr(line, "frida") || strstr(line, "gadget")) {
- // 如果找到了"frida",关闭文件并返回true,表示检测到了恶意库
- fclose(fp);
- return true; // Evil library is loaded.
- }
- }
- // 遍历完文件后,关闭文件
- fclose(fp);
- } else {
- // 如果无法打开文件,记录错误。这可能意味着系统状态异常
- // 注意:这里的代码没有处理错误,只是注释说明了可能的情况
- }
- // 如果没有在内存映射文件中找到"frida",返回false,表示没有检测到恶意库
- return false; // No evil library detected.
- }
复制代码
方法1
`anti脚本`
- // 定义一个函数anti_maps,用于阻止特定字符串的搜索匹配,避免检测到敏感内容如"Frida"或"REJECT"
- function anti_maps() {
- // 查找libc.so库中strstr函数的地址,strstr用于查找字符串中首次出现指定字符序列的位置
- var pt_strstr = Module.findExportByName("libc.so", 'strstr');
- // 查找libc.so库中strcmp函数的地址,strcmp用于比较两个字符串
- var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
- // 使用Interceptor模块附加到strstr函数上,拦截并修改其行为
- Interceptor.attach(pt_strstr, {
- // 在strstr函数调用前执行的回调
- onEnter: function (args) {
- // 读取strstr的第一个参数(源字符串)和第二个参数(要查找的子字符串)
- var str1 = args[0].readCString();
- var str2 = args[1].readCString();
- // 检查子字符串是否包含"REJECT"或"frida",如果包含则设置hook标志为true
- if (str2.indexOf("REJECT") !== -1 || str2.indexOf("frida") !== -1) {
- this.hook = true;
- }
- },
- // 在strstr函数调用后执行的回调
- onLeave: function (retval) {
- // 如果之前设置了hook标志,则将strstr的结果替换为0(表示未找到),从而隐藏敏感信息
- if (this.hook) {
- retval.replace(0);
- }
- }
- });
- // 对strcmp函数做类似的处理,防止通过字符串比较检测敏感信息
- Interceptor.attach(pt_strcmp, {
- onEnter: function (args) {
- var str1 = args[0].readCString();
- var str2 = args[1].readCString();
- if (str2.indexOf("REJECT") !== -1 || str2.indexOf("frida") !== -1) {
- this.hook = true;
- }
- },
- onLeave: function (retval) {
- if (this.hook) {
- // strcmp返回值为0表示两个字符串相等,这里同样替换为0以避免匹配成功
- retval.replace(0);
- }
- }
- });
- }
复制代码
方法2
`重定向maps`
- // 定义一个函数,用于重定向并修改maps文件内容,以隐藏特定的库和路径信息
- function mapsRedirect() {
- // 定义伪造的maps文件路径
- var FakeMaps = "/data/data/com.zj.wuaipojie/maps";
- // 获取libc.so库中'open'函数的地址
- const openPtr = Module.getExportByName('libc.so', 'open');
- // 根据地址创建一个新的NativeFunction对象,表示原生的'open'函数
- const open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
- // 查找并获取libc.so库中'read'函数的地址
- var readPtr = Module.findExportByName("libc.so", "read");
- // 创建新的NativeFunction对象表示原生的'read'函数
- var read = new NativeFunction(readPtr, 'int', ['int', 'pointer', "int"]);
- // 分配512字节的内存空间,用于临时存储从maps文件读取的内容
- var MapsBuffer = Memory.alloc(512);
- // 创建一个伪造的maps文件,用于写入修改后的内容,模式为"w"(写入)
- var MapsFile = new File(FakeMaps, "w");
- // 使用Interceptor替换原有的'open'函数,注入自定义逻辑
- Interceptor.replace(openPtr, new NativeCallback(function(pathname, flag) {
- // 调用原始的'open'函数,并获取文件描述符(FD)
- var FD = open(pathname, flag);
- // 读取并打印尝试打开的文件路径
- var ch = pathname.readCString();
- if (ch.indexOf("/proc/") >= 0 && ch.indexOf("maps") >= 0) {
- console.log("open : ", pathname.readCString());
- // 循环读取maps内容,并写入伪造的maps文件中,同时进行字符串替换以隐藏特定信息
- while (parseInt(read(FD, MapsBuffer, 512)) !== 0) {
- var MBuffer = MapsBuffer.readCString();
- MBuffer = MBuffer.replaceAll("/data/local/tmp/re.frida.server/frida-agent-64.so", "FakingMaps");
- MBuffer = MBuffer.replaceAll("re.frida.server", "FakingMaps");
- MBuffer = MBuffer.replaceAll("frida-agent-64.so", "FakingMaps");
- MBuffer = MBuffer.replaceAll("frida-agent-32.so", "FakingMaps");
- MBuffer = MBuffer.replaceAll("frida", "FakingMaps");
- MBuffer = MBuffer.replaceAll("/data/local/tmp", "/data");
- // 将修改后的内容写入伪造的maps文件
- MapsFile.write(MBuffer);
- }
- // 为返回伪造maps文件的打开操作,分配UTF8编码的文件名字符串
- var filename = Memory.allocUtf8String(FakeMaps);
- // 返回打开伪造maps文件的文件描述符
- return open(filename, flag);
- }
- // 如果不是目标maps文件,则直接返回原open调用的结果
- return FD;
- }, 'int', ['pointer', 'int']));
- }
复制代码
方法3
用eBPF来hook系统调用并修改参数实现目的,使用bpf_probe_write_user向用户态函数地址写内容直接修改参数
- char placeholder[] = "/data/data/com.zj.wuaipojie/maps";
- bpf_probe_write_user((void*)addr, placeholder, sizeof(placeholder));
复制代码
3.检测status(线程名)
- ls /proc/pid/task 列出线程id
- cat /proc/pid/task/线程id/status
复制代码
- 在 `/proc/pid/task` 目录下,可以通过查看不同的线程子目录,来获取进程中每个线程的运行时信息。这些信息包括线程的状态、线程的寄存器内容、线程占用的CPU时间、线程的堆栈信息等。通过这些信息,可以实时观察和监控进程中每个线程的运行状态,帮助进行调试、性能优化和问题排查等工作。
- 在某些app中就会去读取 `/proc/stask/线程ID/status` 文件,如果是运行frida产生的,则进行反调试。例如:`gmain/gdbus/gum-js-loop/pool-frida`等
1. gmain:Frida 使用 Glib 库,其中的主事件循环被称为 GMainLoop。在 Frida 中,gmain 表示 GMainLoop 的线程。
2. gdbus:GDBus 是 Glib 提供的一个用于 D-Bus 通信的库。在 Frida 中,gdbus 表示 GDBus 相关的线程。
3. gum-js-loop:Gum 是 Frida 的运行时引擎,用于执行注入的 JavaScript 代码。gum-js-loop 表示 Gum 引擎执行 JavaScript 代码的线程。
4. pool-frida:Frida 中的某些功能可能会使用线程池来处理任务,pool-frida 表示 Frida 中的线程池。
5. linjector 是一种用于 Android 设备的开源工具,它允许用户在运行时向 Android 应用程序注入动态链接库(DLL)文件。通过注入 DLL 文件,用户可以修改应用程序的行为、调试应用程序、监视函数调用等,这在逆向工程、安全研究和动态分析中是非常有用的。
PS:由于frida可以随时附加到进程,所以写的检测必须覆盖APP的全周期,或者至少是敏感函数执行前
- bool check_status() {
- DIR *dir = opendir("/proc/self/task/");
- struct dirent *entry;
- char status_path[MAX_PATH];
- char buffer[MAX_BUFFER];
- int found = false;
- if (dir) {
- while ((entry = readdir(dir)) != NULL) {
- if (entry->d_type == DT_DIR) {
- if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
- continue;
- }
- snprintf(status_path, sizeof(status_path), "/proc/self/task/%s/status", entry->d_name);
- if (read_file(status_path, buffer, sizeof(buffer)) == -1) {
- continue;
- }
- if (strcmp(buffer, "null") == 0) {
- continue;
- }
- char *line = strtok(buffer, "\n");
- while (line) {
- if (strstr(line, "Name:") != NULL) {
- const char *frida_name = strstr(line, "gmain");
- if (frida_name || strstr(line, "gum-js-loop") || strstr(line, "pool-frida") || strstr(line, "gdbus")) {
- found = true;
- break;
- }
- }
- line = strtok(NULL, "\n");
- }
- if (found) break;
- }
- }
- closedir(dir);
- }
- return found;
- }
复制代码
`anti脚本`
- function replace_str() {
- var pt_strstr = Module.findExportByName("libc.so", 'strstr');
- var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
-
- Interceptor.attach(pt_strstr, {
- onEnter: function (args) {
- var str1 = args[0].readCString();
- var str2 = args[1].readCString();
- if (str2.indexOf("tmp") !== -1 ||
- str2.indexOf("frida") !== -1 ||
- str2.indexOf("gum-js-loop") !== -1 ||
- str2.indexOf("gmain") !== -1 ||
- str2.indexOf("gdbus") !== -1 ||
- str2.indexOf("pool-frida") !== -1||
- str2.indexOf("linjector") !== -1) {
- //console.log("strcmp-->", str1, str2);
- this.hook = true;
- }
- }, onLeave: function (retval) {
- if (this.hook) {
- retval.replace(0);
- }
- }
- });
-
- Interceptor.attach(pt_strcmp, {
- onEnter: function (args) {
- var str1 = args[0].readCString();
- var str2 = args[1].readCString();
- if (str2.indexOf("tmp") !== -1 ||
- str2.indexOf("frida") !== -1 ||
- str2.indexOf("gum-js-loop") !== -1 ||
- str2.indexOf("gmain") !== -1 ||
- str2.indexOf("gdbus") !== -1 ||
- str2.indexOf("pool-frida") !== -1||
- str2.indexOf("linjector") !== -1) {
- //console.log("strcmp-->", str1, str2);
- this.hook = true;
- }
- }, onLeave: function (retval) {
- if (this.hook) {
- retval.replace(0);
- }
- }
- })
-
- }
复制代码
4.检测inlinehook
通过Frida查看一个函数hook之前和之后的机器码,以此来判断是否被Frida的inlinehook注入。

剩下内容太多了,看顶置评论
|
-
查看全部评分
总评分:好评 +5
金币 +5
|