吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 971|回复: 3
上一主题 下一主题
收起左侧

[Android 原创] AndProxy——Syscall及Binder运行时代理库

[复制链接]
跳转到指定楼层
楼主
孤木落 发表于 2026-3-31 18:16 回帖奖励

Native层binder与syscall代理技术

灵感来源:【Android】深入Binder底层拦截

Github项目名:AndProxyDemo

由于本人是个菜鸟(自学android野路子这一块),并不是很了解binder通信的实际原理与调用链,所以AndProxy其实还是有很多问题。而且学业繁忙,检查问题与修复bug能力有限,期待更多大佬贡献代码。

代理技术原理

  • Syscall代理:源于我对老文章某去签工具分析(这里有一个兼容性问题,seccomp_notify机制需要内核版本大于5.10,所以一般要出场Android 12以上的机型才可用)
  • ioctl hook:就是简单的GotHook。但是有两个比较重要的细节(坑)
    1. 由于Android的命名空间机制,dl_iterate_phdr无法找到libbinder.so,只能手动扫描/proc/self/maps
    2. Got项没有写权限,必须先mprotect赋予w可写再修改,最后把权限改回去
  • binder数据解析与修改:参考【Android】深入Binder底层拦截

Syscall 代理技术的一些细节

其实这个框架最早是作为我写的一个拦截皇室战争的exit_group等退出函数的frida模块被开发出的。为什么不使用已经成熟的seccomp+sigaction机制?因为皇室战争的加固提供方Promon已经对此通过主动注册信号处理函数作了防范,但是seccomp_notify处于兼容性原因,暂时没有进行防范。

在拦截退出时,我们有一个基本需求:打印推出前的函数调用栈,快速判断触发闪退位置。

但是,seccomp_notify机制本身不支持获取syscall被调用时的寄存器上下文,所以我们必须利用额外的机制,自然联想到信号量。基本流程是,若要获取寄存器,则在supervisor_loop启动前注册一个信号处理函数,syscall被调用时会触发supervisior,此时supervisor会接收到当前寄存器,再通过shared memory传回supervisior。优点是可以利用SIG_USER2这种很少被注册处理函数,但是可以触发处理函数的信号,这样防御成本就大大增加了。

然后就是第一个坑,通过查阅文档,syscall调用后,进程处于阻塞状态,此时如果发送了信号,就会打断阻塞,syscall也不会被调用。为此,我们必须将sig的flag加上restart,这样就可以再次调用syscall进入supervisor。我们将在第一次进入supervisor进入时获取寄存器上下文,在第二次进入时调用回调函数进行真正的处理。

static void supervisor_loop(int lfd, int cmd_fd, int use_signal) {
    LOGD("supervisor_loop started, lfd=%d", lfd);
    g_notify_fd = lfd;
    struct seccomp_notif_sizes sizes{};
    if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes) != 0) {
        LOGE("GET_NOTIF_SIZES failed\n");
        _exit(-1);
    }

    auto *req = static_cast<struct seccomp_notif *>(malloc(sizes.seccomp_notif));
    auto *resp = static_cast<struct seccomp_notif_resp *>(malloc(sizes.seccomp_notif_resp));
    if (!req || !resp) {
        LOGE("malloc failed\n");
        _exit(-1);
    }

    struct pollfd fds[2];
    fds[0].fd = lfd;
    fds[0].events = POLLIN;
    fds[1].fd = cmd_fd;
    fds[1].events = POLLIN;

    int is_first_enter = 1;

    while (true) {
        int ret = poll(fds, 2, -1);
        if (ret < 0) {
            if (errno == EINTR) continue;
            break;
        }

        // 处理 seccomp 通知
        if (fds[0].revents & POLLIN) {
            memset(req, 0, sizes.seccomp_notif);
            if (ioctl(lfd, SECCOMP_IOCTL_NOTIF_RECV, req) < 0) {
                if (errno == EINTR) continue;
                LOGE("NOTIF_RECV failed: %d\n", errno);
                break;
            }
            LOGD("supervisor: received notification id=%llu, nr=%d, pid=%d",
                 req->id, req->data.nr, req->pid);

            hook_regs_t regs = {0};
            if (use_signal && is_first_enter) {
                if (tgkill(g_parent_pid, static_cast<pid_t>(req->pid), SIGUSR2) == 0) {
                    LOGD("supervisor: tgkill sent to pid=%d", req->pid);
                    int timeout = 100;
                    while (timeout-- > 0 && g_shared->ready == 0) {
                        usleep(1000);
                    }
                    if (g_shared->ready) {
                        regs = g_shared->regs;
                        g_shared->ready = 0;
                        LOGD("supervisor: pc %llx from reg_pipe", regs.pc);
                    }
                    is_first_enter = 0;
                    continue;
                } else {
                    LOGE("tgkill failed: %d\n", errno);
                }
            } else if (use_signal) {
                is_first_enter = 1;
            }

            hook_request_t hook_req;
            hook_req.id = req->id;
            hook_req.syscall_nr = req->data.nr;
            hook_req.pid = static_cast<pid_t>(req->pid);
            memcpy(hook_req.args, req->data.args, sizeof(req->data.args));
            hook_req.regs = regs;

            hook_response_t hook_resp;
            memset(&hook_resp, 0, sizeof(hook_resp));
            hook_resp.id = req->id;
            hook_resp.action = HOOK_ACTION_ALLOW;

            // 执行回调
            LOGD("supervisor: before callbacks");
            // pthread_mutex_lock(&g_hook_lock);
            hook_node_t *node = g_hook_list;
            while (node) {
                if (node->nr == hook_req.syscall_nr) {
                    node->callback(&hook_req, &hook_resp, node->userdata);
                }
                node = node->next;
            }
            // pthread_mutex_unlock(&g_hook_lock);
            LOGD("supervisor: after callbacks");

            memset(resp, 0, sizes.seccomp_notif_resp);
            resp->id = req->id;
            if (hook_resp.action == HOOK_ACTION_ALLOW) {
                resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
                resp->error = 0;
                resp->val = 0;
            } else if (hook_resp.action == HOOK_ACTION_DENY) {
                resp->flags = 0;
                resp->error = hook_resp.error;
                resp->val = 0;
            } else {
                resp->flags = 0;
                resp->error = 0;
                resp->val = hook_resp.val;
            }

            if (ioctl(lfd, SECCOMP_IOCTL_NOTIF_ID_VALID, &req->id) == 0) {
                if (ioctl(lfd, SECCOMP_IOCTL_NOTIF_SEND, resp) < 0) {
                    LOGE("SEND error: %d\n", errno);
                }
            }
        }

        // 处理父进程命令
        if (fds[1].revents & POLLIN) {
            cmd_t cmd;
            ssize_t n = read(cmd_fd, &cmd, sizeof(cmd));
            if (n == sizeof(cmd)) {
                if (cmd.type == CMD_REGISTER) {
                    seccomp_hook_register(cmd.nr, (hook_callback_t)cmd.callback, (void*)cmd.userdata);
                } else if (cmd.type == CMD_UNREGISTER) {
                    seccomp_hook_unregister(cmd.nr, (hook_callback_t)cmd.callback, (void*)cmd.userdata);
                }
            } else if (n == 0) {
                break;
            } else {
                LOGE("read cmd failed: %zd\n", n);
            }
        }
    }

    free(req);
    free(resp);
    close(lfd);
    close(cmd_fd);
    _exit(0);
}

第二个坑是,supervisor和主进程不是同一个进程,所以注册callback时可能会有bug。我主要利用的是fork不会改变模块布局,所以如果模块在supervisor启动前就已经被加载,那么他的函数指针在supervisor和被监控进程中是等价的,直接传递即可。

int seccomp_hook_register_remote(const int *syscall_list,
                                 const struct sock_fprog *custom_filter, hook_callback_t callback, void *userdata) {
    if (!syscall_list || *syscall_list == -1) return -1;
    struct sock_fprog prog{};
    if (custom_filter) {
        prog = *custom_filter;
    } else {
        prog = build_filter(syscall_list);
    }
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) < 0) {
        LOGE("failed to add filter");
    }
    for (const int* nr = syscall_list; *nr != -1; nr++) {
        cmd_t cmd;
        cmd.type = CMD_REGISTER;
        cmd.nr = *nr;
        cmd.callback = (uintptr_t)callback;
        cmd.userdata = (uintptr_t)userdata;
        ssize_t w = write(g_cmd_pipe_wr, &cmd, sizeof(cmd));
        if (w != sizeof(cmd))
            return -1;
    }
    return 0;
}

int seccomp_hook_unregister_remote(const int *syscall_list, hook_callback_t callback, void *userdata) {
    for (const int* nr = syscall_list; *nr != -1; nr++) {
        cmd_t cmd;
        cmd.type = CMD_UNREGISTER;
        cmd.nr = *nr;
        cmd.callback = (uintptr_t)callback;
        cmd.userdata = (uintptr_t)userdata;
        ssize_t w = write(g_cmd_pipe_wr, &cmd, sizeof(cmd));
        if (w != sizeof(cmd))
            return -1;
    }
    return 0;
}

最后是如何在被监控进程调用可能被seccomp限制的回调?这个利用了seccomp的限制以线程为单位的特性,执行java callback的线程先于seccomp的bpf规则注册就不会被限制,所以会在JNI_OnLoad中先启动线程,init函数在此之后调用。(从这个角度看SvcInterceptor的addFile方法其实没有什么用,可能会在后续版本移除)

extern "C" JNIEXPORT jint JNICALL Java_com_gumuluo_proxy_SvcInterceptor_init
        (JNIEnv *env, jclass clazz, jintArray syscallList) {
    if (syscallList == nullptr) return -1;
    if (g_handler_running > 0) return 0;

    // 1. 解析系统调用号数组(与之前相同)
    jsize len = env->GetArrayLength(syscallList);
    jint *elems = env->GetIntArrayElements(syscallList, nullptr);
    if (!elems) return -1;

    int hasTerm = 0;
    for (int i = 0; i < len; i++) {
        if (elems[i] == -1) { hasTerm = 1; break; }
    }

    int *c_list;
    int list_len;
    if (hasTerm) {
        c_list = (int*)malloc(len * sizeof(int));
        memcpy(c_list, elems, len * sizeof(int));
        list_len = len;
    } else {
        c_list = (int*)malloc((len + 1) * sizeof(int));
        memcpy(c_list, elems, len * sizeof(int));
        c_list[len] = -1;
        list_len = len + 1;
    }
    env->ReleaseIntArrayElements(syscallList, elems, JNI_ABORT);

    int num_entries = list_len - 1;
    auto *entries = (hook_entry_t*)malloc(num_entries * sizeof(hook_entry_t));
    if (!entries) { free(c_list); return -1; }
    for (int i = 0; i < num_entries; i++) {
        entries[i].nr = c_list[i];
        entries[i].callback = global_java_callback;
        entries[i].userdata = nullptr;
    }

    // 2. 创建 socketpair(父子进程通信)
    int sv[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
        LOGE("socketpair failed");
        free(c_list); free(entries);
        return -1;
    }
    g_sock_fd = sv[0];        // 父进程端(用于 handler 线程)
    g_child_sock_fd = sv[1];  // 子进程端(fork 后子进程使用)

    // 3. 启动处理线程(父线程)
    pthread_create(&g_handler_thread, nullptr, handler_thread_func, (void*)(intptr_t)g_sock_fd);
    g_handler_running = 1;

    // 4. 调用库初始化(内部会 fork 子进程)
    int ret = seccomp_hook_init(c_list, nullptr, entries, num_entries, 1);
    free(c_list);
    free(entries);

    if (ret != 0) {
        // 初始化失败,关闭 socket,线程将因 recv 错误而退出
        close(g_sock_fd);
        close(g_child_sock_fd);
        return ret;
    }

    // 5. 父进程关闭子进程端(子进程已有副本)
    close(g_child_sock_fd);
    g_child_sock_fd = -1;

    return 0;
}

具体实现参考:seccomp_hook.cpp, SvcInterceptor.cpp

binder代理的一些细节

感谢 @iofomo(数字锋芒)大佬的的无私分享,主要技术细节都在他的blog中,这里只分享几个小问题的解决方案。

首先是服务名解析(Stub中的DESCRIPTOR字段),按照大佬分享的方法没有解析出来。但是注意到[strlen][utf16_str]的固定结构,采用了模糊搜索方法,成功解析服务名(缺点是很容易误匹配,所以我加了很多限制)。

std::string get_server_name(const binder_transaction_data* txn) {
    if (!txn || !txn->data.ptr.buffer || txn->data_size < 16) {
        return "";
    }

    const uint8_t* base = reinterpret_cast<const uint8_t*>(
            static_cast<uintptr_t>(txn->data.ptr.buffer));
    size_t size = txn->data_size;

    // 辅助函数:从给定位置提取 UTF-16 字符串
    auto extract_utf16 = [&](size_t offset, int32_t len) -> std::string {
        if (offset + 4 + len * 2 > size) return "";
        const uint16_t* name16 = reinterpret_cast<const uint16_t*>(base + offset + 4);
        std::string result;
        result.reserve(len);
        for (int32_t i = 0; i < len; ++i) {
            uint16_t ch = name16[i];
            if (ch < 0x80) {
                result.push_back(static_cast<char>(ch));
            } else if (ch < 0x800) {
                result.push_back(static_cast<char>(0xC0 | (ch >> 6)));
                result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
            } else {
                result.push_back(static_cast<char>(0xE0 | (ch >> 12)));
                result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
                result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
            }
        }
        return result;
    };

    // 服务名合法性检查:只允许字母、数字、点、下划线、美元符号
    auto is_valid_name = [](const std::string& s) -> bool {
        if (s.empty()) return false;
        for (char c : s) {
            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
                  (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '$')) {
                return false;
            }
        }
        return true;
    };

    // 首先寻找 "TSYS" 标志(flat_binder_object.hdr.type == BINDER_TYPE_BINDER)
    const uint32_t TSYS_MAGIC = 0x54535953;  // "TSYS"
    for (size_t offset = 8; offset + 8 <= size; ++offset) {
        if (*(uint32_t*)(base + offset) == TSYS_MAGIC) {
            size_t len_pos = offset + 4;
            if (len_pos + 4 > size) continue;
            int32_t nameLen = *(int32_t*)(base + len_pos);
            if (nameLen > 3 && nameLen <= 256) {
                std::string candidate = extract_utf16(len_pos, nameLen);
                if (!candidate.empty() && is_valid_name(candidate) &&
                    candidate.find('.') != std::string::npos) {
                    return candidate;
                }
            }
        }
    }

    // 回退到模糊搜索
    for (size_t offset = 0; offset + 6 <= size; ++offset) {
        const int32_t* len_ptr = reinterpret_cast<const int32_t*>(base + offset);
        int32_t nameLen = *len_ptr;
        if (nameLen <= 3 || nameLen > 256) continue;
        size_t str_bytes = static_cast<size_t>(nameLen) * 2;
        if (offset + 4 + str_bytes > size) continue;

        const uint16_t* name16 = reinterpret_cast<const uint16_t*>(base + offset + 4);
        bool valid = true;
        for (int32_t i = 0; i < nameLen; ++i) {
            uint16_t ch = name16[i];
            if ((ch & 0xFF00) == 0) {
                uint8_t lo = ch & 0xFF;
                if (lo < 0x20 || lo > 0x7E) {
                    valid = false;
                    break;
                }
            }
        }
        if (!valid) continue;

        std::string result = extract_utf16(offset, nameLen);
        if (is_valid_name(result)) {
            return result;
        }
    }

    return "";
}

然后是为什么选择Got Hook?因为我不想在代码段留脏页,不想改rx内存的权限为可写(bushi)。其实是因为Got Hook比较简单,而且binder通信位置相对集中在libbinder.so,所以选择对libbinder.so进行Got Hook。

// 通过 /proc/self/maps 获取库基址
static uintptr_t get_library_base(const char *libname) {
    char line[512];
    FILE *fp = fopen("/proc/self/maps", "r");
    if (!fp) {
        LOGE("Failed to open /proc/self/maps: %s", strerror(errno));
        return 0;
    }

    uintptr_t base = 0;
    while (fgets(line, sizeof(line), fp)) {
        char *path = strchr(line, '/');
        if (path) {
            if (strstr(path, libname) != NULL) {
                char *dash = strchr(line, '-');
                if (dash) {
                    *dash = '\0';
                    base = strtoull(line, NULL, 16);
                    LOGD("Found library %s at base 0x%lx", libname, base);
                    break;
                } else {
                    LOGE("Invalid map line (no dash): %s", line);
                }
            }
        }
    }
    fclose(fp);
    if (base == 0) {
        LOGE("Library %s not found in /proc/self/maps", libname);
    }
    return base;
}

// 从动态段中获取指定类型的信息(返回偏移量,需加 base)
static uintptr_t get_dynamic_info_offset(const Elf64_Dyn *dyn, int64_t tag) {
    for (; dyn->d_tag != DT_NULL; ++dyn) {
        if (dyn->d_tag == tag) {
            return dyn->d_un.d_ptr;
        }
    }
    return 0;
}

// 根据函数名在指定库中查找 GOT 条目地址
static uintptr_t find_got_entry(const char *libname, const char *funcname) {
    uintptr_t base = get_library_base(libname);
    if (base == 0) {
        LOGE("Failed to get base address for %s", libname);
        return 0;
    }

    // 读取 ELF 头
    Elf64_Ehdr *ehdr = (Elf64_Ehdr*)base;
    if (ehdr->e_ident[EI_MAG0] != ELFMAG0 ||
        ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
        ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
        ehdr->e_ident[EI_MAG3] != ELFMAG3) {
        LOGE("Invalid ELF header at base 0x%lx", base);
        return 0;
    }
    LOGD("Valid ELF header at base 0x%lx", base);

    // 找到 PT_DYNAMIC 程序头
    Elf64_Phdr *phdr = (Elf64_Phdr*)(base + ehdr->e_phoff);
    Elf64_Dyn *dyn = NULL;
    for (int i = 0; i < ehdr->e_phnum; ++i) {
        if (phdr[i].p_type == PT_DYNAMIC) {
            dyn = (Elf64_Dyn*)(base + phdr[i].p_vaddr);
            LOGD("Found PT_DYNAMIC at offset 0x%lx, vaddr 0x%lx",
                 phdr[i].p_offset, phdr[i].p_vaddr);
            break;
        }
    }
    if (!dyn) {
        LOGE("No PT_DYNAMIC segment found");
        return 0;
    }

    // 获取动态段中的关键信息(偏移量)
    uintptr_t symtab_off = get_dynamic_info_offset(dyn, DT_SYMTAB);
    uintptr_t strtab_off = get_dynamic_info_offset(dyn, DT_STRTAB);
    uintptr_t relplt_off = get_dynamic_info_offset(dyn, DT_JMPREL);
    size_t relplt_size = get_dynamic_info_offset(dyn, DT_PLTRELSZ);
    uintptr_t pltgot_off = get_dynamic_info_offset(dyn, DT_PLTGOT);

    if (!symtab_off) {
        LOGE("DT_SYMTAB not found");
        return 0;
    }
    if (!strtab_off) {
        LOGE("DT_STRTAB not found");
        return 0;
    }
    if (!relplt_off) {
        LOGE("DT_JMPREL not found");
        return 0;
    }
    if (!relplt_size) {
        LOGE("DT_PLTRELSZ is zero");
        return 0;
    }
    if (!pltgot_off) {
        LOGE("DT_PLTGOT not found");
        return 0;
    }

    // 计算实际内存地址(基址 + 偏移)
    Elf64_Sym *symtab = (Elf64_Sym*)(base + symtab_off);
    const char *strtab = (const char*)(base + strtab_off);
    Elf64_Rela *relplt = (Elf64_Rela*)(base + relplt_off);
    uintptr_t pltgot = base + pltgot_off;

    LOGD("symtab=0x%lx (offset 0x%lx), strtab=0x%lx, relplt=0x%lx, relplt_size=%zu, pltgot=0x%lx",
         (uintptr_t)symtab, symtab_off, (uintptr_t)strtab, (uintptr_t)relplt, relplt_size, pltgot);

    // 遍历 .rel.plt 重定位表
    size_t num_rel = relplt_size / sizeof(Elf64_Rela);
    LOGD("Number of relocations in .rel.plt: %zu", num_rel);
    for (size_t i = 0; i < num_rel; ++i) {
        uint32_t sym_idx = ELF64_R_SYM(relplt[i].r_info);
        const char *sym_name = strtab + symtab[sym_idx].st_name;
        if (strcmp(sym_name, funcname) == 0) {
            uintptr_t got_entry = base + relplt[i].r_offset;  // r_offset 也是偏移,需加基址
            LOGD("Found GOT entry for %s at 0x%lx (r_offset=0x%lx)",
                 funcname, got_entry, relplt[i].r_offset);
            return got_entry;
        }
    }

    LOGE("Function %s not found in .rel.plt", funcname);
    return 0;
}

static void got_hook(uintptr_t got_addr, void *new_func, void **old_func) {
    void **got_ptr = (void**)got_addr;
    void *orig = *got_ptr;

    long page_size = sysconf(_SC_PAGESIZE);
    if (page_size <= 0) {
        LOGE("sysconf(_SC_PAGESIZE) failed: %s", strerror(errno));
        return;
    }

    uintptr_t page_start = got_addr & ~(page_size - 1);
    LOGD("Got address 0x%lx, page_start 0x%lx, page_size %ld",
         got_addr, page_start, page_size);

    if (mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE) == -1) {
        LOGE("mprotect (write) failed: %s", strerror(errno));
        return;
    }

    // 执行 GOT 替换
    *got_ptr = new_func;
    LOGD("GOT entry at 0x%lx changed from %p to %p", got_addr, orig, new_func);

    if (mprotect((void*)page_start, page_size, PROT_READ) == -1) {
        LOGE("mprotect (read-only) failed: %s", strerror(errno));
        // 即使恢复失败,hook 已生效,只打印警告
    }

    if (old_func) *old_func = orig;
}

void BinderHook::init(JavaVM* vm) {
    uintptr_t got_entry = find_got_entry("libbinder.so", "ioctl");
    if (!got_entry) {
        LOGE("Failed to find GOT entry for ioctl");
        return;
    }

    got_hook(got_entry, (void*) ioctl_proxy, NULL);
    jvm_ = vm;
    JNIEnv* env;
    if (jvm_->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        jvm_->AttachCurrentThread(&env, nullptr);
    }

    jclass dispatcherClass = env->FindClass("com/gumuluo/proxy/binder/BinderDispatcher");
    dispatcherClass_ = (jclass)env->NewGlobalRef(dispatcherClass);
    dispatchBeforeMid_ = env->GetStaticMethodID(dispatcherClass_, "dispatchBefore",
                                                "(Ljava/lang/String;Ljava/lang/String;Landroid/os/Parcel;Landroid/os/Parcel;)Z");
    dispatchAfterMid_ = env->GetStaticMethodID(dispatcherClass_, "dispatchAfter",
                                               "(Ljava/lang/String;Ljava/lang/String;Landroid/os/Parcel;Landroid/os/Parcel;)Z");

    env->DeleteLocalRef(dispatcherClass);
}

最后是解包Parcel对象,这个放在java层做比较简单,具体示例在app模块中有演示,注意一定要清除缓存

private fun enableFraud() {
        // 先注销已有的拦截器(如果有),避免重复
        BinderDispatcher.unregisterAfter("android.content.pm.IPackageManager", "getApplicationInfo")
        CacheHandling.clearCaches()

        // 注册新的 after 回调
        BinderDispatcher.registerAfter(
            "android.content.pm.IPackageManager",
            "getApplicationInfo",
            BinderInterceptor { data, outReply ->
                Log.d("Bypass", "========== Interceptor triggered ==========")
                Log.d("Bypass", "dataSize=${data.dataSize()}, dataAvail=${data.dataAvail()}")

                try {
                    // 1. 读取异常(安全跳过所有异常相关信息)
                    data.readException()
                } catch (e: Exception) {
                    // 如果服务返回了异常,记录日志并继续处理(但通常不会)
                    Log.w("Bypass", "Service threw exception: $e")
                    // 注意:这里不能直接返回 false,因为我们需要继续处理数据(可能有部分数据)
                }

                // 2. 读取 ApplicationInfo 对象(可能为 null)
                val originalInfo = data.readTypedObject(ApplicationInfo.CREATOR)
                if (originalInfo == null) {
                    Log.w("Bypass", "No ApplicationInfo returned")
                    return@BinderInterceptor false
                }

                // 3. 修改 flags
                val originalFlags = originalInfo.flags
                originalInfo.flags = originalFlags and ApplicationInfo.FLAG_DEBUGGABLE.inv()
                Log.d("Bypass", "Modified flags: 0x${Integer.toHexString(originalFlags)} -> 0x${Integer.toHexString(originalInfo.flags)}")

                // 4. 构造新的 outReply,模仿服务端的构造方式
                outReply.setDataPosition(0)
                outReply.writeNoException()                 // 写入异常码及可能的 StrictMode 信息
                outReply.writeTypedObject(originalInfo, 0)  // 写入对象标志 + 对象数据

                Log.d("Bypass", "Modified data size: ${outReply.dataSize()}")
                true
            }
        )

        // 重新获取状态(此时拦截器已生效)
        val isDebuggable = getDebuggableFlag()
        resultText = if (isDebuggable) "欺诈后状态: DEBUGGABLE = true" else "欺诈后状态: DEBUGGABLE = false"
    }

技术展望

我开发AndProxy的最初目的是做android运行时监控,但是AndProxy还可以用于做隔离沙盒,PMS Hook,IO重定向等等。目前我计划将其用于开发APP多开和监控沙盒,但是苦于不会Framework,而且学业繁忙,希望有大佬可以加入开发。

免费评分

参与人数 4威望 +1 吾爱币 +23 热心值 +4 收起 理由
buluo533 + 1 + 1 用心讨论,共获提升!
杨辣子 + 1 + 1 谢谢@Thanks!
zmolli775 + 1 + 1 虽然不懂·但是写这么多一定很厉害,支持一个。
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
cadd 发表于 2026-4-1 12:48
好帖,感谢发布原创作品!
3#
imxz 发表于 2026-4-1 14:56
4#
liltn 发表于 2026-4-4 22:24
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-12 00:03

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表