https://github.com/cyrus-studio/frida_thread
Frida 调用 kill 命令挂起&恢复 Android 线程(Frida uses the `kill` command to suspend & resume Android threads.)
https://github.com/cyrus-studio/frida_thread
frida kill thread threads
Last synced: 9 months ago
JSON representation
Frida 调用 kill 命令挂起&恢复 Android 线程(Frida uses the `kill` command to suspend & resume Android threads.)
- Host: GitHub
- URL: https://github.com/cyrus-studio/frida_thread
- Owner: CYRUS-STUDIO
- Created: 2025-04-09T10:07:01.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-04-09T10:19:36.000Z (11 months ago)
- Last Synced: 2025-06-10T16:41:15.624Z (9 months ago)
- Topics: frida, kill, thread, threads
- Language: JavaScript
- Homepage: https://cyrus-studio.github.io/blog/posts/frida-%E8%B0%83%E7%94%A8-kill-%E5%91%BD%E4%BB%A4%E6%8C%82%E8%B5%B7%E6%81%A2%E5%A4%8D-android-%E7%BA%BF%E7%A8%8B/
- Size: 5.86 KB
- Stars: 1
- Watchers: 1
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
> 版权归作者所有,如有转发,请注明文章出处:
# **Android 线程相关命令**
## **获取 PID**
```
adb shell pidof com.shizhuang.duapp
```
## **查看线程信息**
### **方法一:进入 /proc//task**
进入 adb shell 执行下面命令
```
cd /proc/$(pidof com.shizhuang.duapp)/task
ls
```
这个目录中每一个子目录的名字就是该 App 的一个线程的 TID(Thread ID)。
你还可以进一步查看每个线程的状态:
```
cat /proc/$(pidof com.shizhuang.duapp)/task//status
```
例如:
```
cat /proc/$(pidof com.shizhuang.duapp)/task/22432/status
```
### **方法二:使用 top 或 htop 查看线程**
使用 top 查看线程信息
```
top -H -p $(pidof com.shizhuang.duapp)
```
- -H 表示以线程方式查看
- -p 指定 PID

或者使用 htop 查看线程信息
```
htop -p $(pidof com.shizhuang.duapp)
```

## **通过 kill 命令 停止 / 挂起 / 恢复线程**
kill 是 Linux 系统中用来向进程发送信号的命令,最常用于终止进程。虽然它名字叫 “kill”,但它可以发送多种信号,不只是“终止”。
进入 adb shell,比如线程/进程 id 为 22432
```
# 停止指定进程/线程
kill 22432
# 强制停止进程/线程
kill -9 22432
# 强制停止 com.cyrus.example
kill -9 $(pidof com.cyrus.example)
# 挂起进程/线程
kill -19 22432
# 继续进程/线程
kill -18 22432
```
kill 命令详细介绍:[https://man7.org/linux/man-pages/man1/kill.1.html](https://man7.org/linux/man-pages/man1/kill.1.html)
常见信号类型(默认是 SIGTERM):
| 信号名 | 数值 | 含义 |
|--- | --- | ---|
| SIGHUP | 1 | 挂起信号,通常用于重启进程配置 |
| SIGINT | 2 | 中断信号,类似 Ctrl+C |
| SIGQUIT | 3 | 退出信号,类似 Ctrl+\ |
| SIGKILL | 9 | 强制终止信号(无法捕获) |
| SIGTERM | 15 | 终止信号(可捕获、默认) |
| SIGSTOP | 19 | 暂停进程(无法捕获) |
| SIGCONT | 18 | 恢复被暂停的进程 |
# **使用 Frida 调用 kill 命令**
kill 在 C 语言中是定义在 中的一个标准函数,它本质上是一个系统调用的封装函数。
kill 函数(C 标准库中的定义)
```
#include
int kill(pid_t pid, int sig);
```
- pid:要发送信号的进程 ID。
- sig:要发送的信号编号,比如 SIGKILL, SIGTERM, SIGSTOP 等。
- 返回值:返回 0 表示成功。返回 -1 表示失败,并设置 errno。
在底层,kill() 实际上会触发系统调用(比如 Linux 的 syscall kill),让内核发送信号给指定的进程。
它是 UNIX/Linux 系统里最常用的进程间通信(IPC)手段之一。
## **1. JS + NativeFunction 调用 libc 中的 kill**
查找 libc 中的 kill 函数,并使用 NativeFunction 封装引用
```
const killPtr = Module.findExportByName(null, 'kill');
const kill = new NativeFunction(killPtr, 'int', ['int', 'int']);
```
## **2. 在 JS 中调用这个函数**
挂起线程
```
// 挂起进程
const result = kill(22432, 19);
if (result === 0) {
console.log("✅ Signal sent!");
} else {
console.log("❌ Failed to send signal.");
}
```
恢复线程
```
// 恢复线程
const result = kill(22432, 18);
if (result === 0) {
console.log("✅ Signal sent!");
} else {
console.log("❌ Failed to send signal.");
}
```
## **3. suspendThread / resumeThread**
封装 suspendThread / resumeThread 函数用于挂起和恢复线程。
kill.js:
```
// 加载 libc 中的 kill 函数
const killPtr = Module.findExportByName(null, 'kill');
const kill = new NativeFunction(killPtr, 'int', ['int', 'int']);
// 信号常量
const SIGSTOP = 19; // 暂停进程(不可被捕获)
const SIGCONT = 18; // 继续执行进程
// 封装 suspend/resume 函数
function suspendThread(pid) {
const result = kill(pid, SIGSTOP);
if (result === 0) {
console.log(`✅ 成功挂起 PID=${pid}`);
} else {
console.log(`❌ 挂起失败 PID=${pid}`);
}
}
function resumeThread(pid) {
const result = kill(pid, SIGCONT);
if (result === 0) {
console.log(`✅ 成功恢复 PID=${pid}`);
} else {
console.log(`❌ 恢复失败 PID=${pid}`);
}
}
```
执行脚本:
```
frida -H 127.0.0.1:1234 -F -l kill.js
```
调用示例:
```
// 挂起线程
suspendThread(22432); // 替换为目标线程或进程的 PID
// 恢复线程
resumeThread(22432);
```

# **suspendOtherThread**
实现一个 suspendOtherThread 函数,挂起除了 excludeList 中指定名字的所有线程。
## **1. 读取线程列表**
遍历 /proc/${pid}/task/ 目录下所有目录的名称得到 线程id
```
const taskDir = `/proc/${pid}/task/`;
var dirList = Java.use('java.io.File').$new(taskDir).listFiles()
for (let i = 0; i < dirList.length; i++) {
// 线程id
const tid = parseInt(dirList[i].getName());
}
```
## **2. 读取线程名称**
线程名称就在 /proc/${pid}/task/${tid}/comm 文件中,读取 comm 文件内容得到线程名称。
```
/**
* 读取文件内容并返回字符串
*
* @param path 文件路径
* @returns {string|null} 文件内容
*/
function readFileAsString(path) {
const File = Java.use("java.io.File");
const FileInputStream = Java.use("java.io.FileInputStream");
const InputStreamReader = Java.use("java.io.InputStreamReader");
const BufferedReader = Java.use("java.io.BufferedReader");
const StringBuilder = Java.use("java.lang.StringBuilder");
const file = File.$new(path);
if (!file.exists()) {
console.log(`❌ 文件不存在: ${path}`);
return null;
}
const fis = FileInputStream.$new(file);
const isr = InputStreamReader.$new(fis, "UTF-8");
const reader = BufferedReader.$new(isr);
const sb = StringBuilder.$new();
let line;
while ((line = reader.readLine()) !== null) {
sb.append(line);
}
reader.close();
return sb.toString();
}
/**
* comm 文件内容就是线程名称
*
* @param pid 进程 id
* @param tid 线程 id
* @returns {string} 线程名称
*/
function readComm(pid, tid) {
const path = `/proc/${pid}/task/${tid}/comm`;
return readFileAsString(path);
}
```
相关文档:
- [https://docs.oracle.com/javase/8/docs/api/java/io/File.html](https://docs.oracle.com/javase/8/docs/api/java/io/File.html)
- [https://frida.re/docs/javascript-api/#file](https://frida.re/docs/javascript-api/#file)
## **3. 挂起线程**
挂起除了 excludeList 中指定名字的所有线程
```
function suspendOtherThread(pid, excludeList) {
Java.perform(function () {
const taskDir = `/proc/${pid}/task/`;
var dirList = Java.use('java.io.File').$new(taskDir).listFiles()
for (let i = 0; i < dirList.length; i++) {
const tid = parseInt(dirList[i].getName());
if (isNaN(tid)) continue;
const threadName = readComm(pid, tid);
if (!threadName) continue;
// 只要 threadName 包含 excludeList 中的任意关键字,就会被排除(跳过不挂起)
if (excludeList.some(keyword => threadName.includes(keyword))) {
console.log(`🟢 跳过线程 "${threadName}" (TID=${tid})`);
} else {
console.log(`🛑 挂起线程 "${threadName}" (TID=${tid})`);
suspendThread(tid);
}
}
})
}
```
## **4. 调用示例**
```
// 示例:只保留 excludeList 中指定的线程运行,其它全部挂起
const pid = Process.id;
const excludeList = [
"main",
"RenderThread",
"m.cyrus.example",
"frida",
];
suspendOtherThread(pid, excludeList);
```
执行脚本
```
frida -H 127.0.0.1:1234 -F -l kill.js
```
效果如下:

# **resumeOtherThread**
恢复线程也是类似。
```
/**
* 恢复除了 excludeList 中指定名字的所有线程
* @param {number} pid - 当前进程 PID
* @param {string[]} excludeList - 不需要恢复的线程名字数组
*/
function resumeOtherThread(pid, excludeList) {
Java.perform(function () {
const taskDir = `/proc/${pid}/task/`;
var dirList = Java.use('java.io.File').$new(taskDir).listFiles()
for (let i = 0; i < dirList.length; i++) {
const tid = parseInt(dirList[i].getName());
if (isNaN(tid)) continue;
const threadName = readComm(pid, tid);
if (!threadName) continue;
// 只要 threadName 包含 excludeList 中的任意关键字,就会被排除(跳过不恢复)
if (excludeList.some(keyword => threadName.includes(keyword))) {
console.log(`🟢 跳过线程 "${threadName}" (TID=${tid})`);
} else {
console.log(`🔄 恢复线程 "${threadName}" (TID=${tid})`);
resumeThread(tid);
}
}
})
}
```
执行脚本
```
frida -H 127.0.0.1:1234 -F -l kill.js
```
调用示例:
```
// 示例:只保留 excludeList 中指定的线程运行,其它全部挂起
const pid = Process.id;
const excludeList = [
"main",
"RenderThread",
"m.cyrus.example",
"frida",
];
resumeOtherThread(pid, excludeList);
```
输出如下:
```
🟢 跳过线程 "m.cyrus.example" (TID=31263)
🔄 恢复线程 "Jit thread pool" (TID=31269)
✅ 成功恢复 PID=31269
🔄 恢复线程 "Signal Catcher" (TID=31274)
✅ 成功恢复 PID=31274
🔄 恢复线程 "ADB-JDWP Connec" (TID=31275)
✅ 成功恢复 PID=31275
🔄 恢复线程 "HeapTaskDaemon" (TID=31276)
✅ 成功恢复 PID=31276
🔄 恢复线程 "ReferenceQueueD" (TID=31277)
✅ 成功恢复 PID=31277
🔄 恢复线程 "FinalizerDaemon" (TID=31278)
✅ 成功恢复 PID=31278
🔄 恢复线程 "FinalizerWatchd" (TID=31279)
✅ 成功恢复 PID=31279
🔄 恢复线程 "Binder:31263_1" (TID=31280)
✅ 成功恢复 PID=31280
🔄 恢复线程 "Binder:31263_2" (TID=31281)
✅ 成功恢复 PID=31281
🔄 恢复线程 "Binder:31263_3" (TID=31282)
✅ 成功恢复 PID=31282
🔄 恢复线程 "Profile Saver" (TID=31283)
✅ 成功恢复 PID=31283
🟢 跳过线程 "RenderThread" (TID=31284)
🔄 恢复线程 "Binder:31263_4" (TID=31287)
✅ 成功恢复 PID=31287
🟢 跳过线程 "m.cyrus.example" (TID=31293)
🟢 跳过线程 "gmain" (TID=31294)
🔄 恢复线程 "gdbus" (TID=31296)
✅ 成功恢复 PID=31296
🔄 恢复线程 "Thread-20" (TID=31297)
✅ 成功恢复 PID=31297
🟢 跳过线程 "m.cyrus.example" (TID=31263)
🔄 恢复线程 "Jit thread pool" (TID=31269)
✅ 成功恢复 PID=31269
🔄 恢复线程 "Signal Catcher" (TID=31274)
✅ 成功恢复 PID=31274
🔄 恢复线程 "ADB-JDWP Connec" (TID=31275)
✅ 成功恢复 PID=31275
🔄 恢复线程 "HeapTaskDaemon" (TID=31276)
✅ 成功恢复 PID=31276
🔄 恢复线程 "ReferenceQueueD" (TID=31277)
✅ 成功恢复 PID=31277
🔄 恢复线程 "FinalizerDaemon" (TID=31278)
✅ 成功恢复 PID=31278
🔄 恢复线程 "FinalizerWatchd" (TID=31279)
✅ 成功恢复 PID=31279
🔄 恢复线程 "Binder:31263_1" (TID=31280)
✅ 成功恢复 PID=31280
🔄 恢复线程 "Binder:31263_2" (TID=31281)
✅ 成功恢复 PID=31281
🔄 恢复线程 "Binder:31263_3" (TID=31282)
✅ 成功恢复 PID=31282
🔄 恢复线程 "Profile Saver" (TID=31283)
✅ 成功恢复 PID=31283
🟢 跳过线程 "RenderThread" (TID=31284)
🔄 恢复线程 "Binder:31263_4" (TID=31287)
✅ 成功恢复 PID=31287
🟢 跳过线程 "m.cyrus.example" (TID=31293)
🟢 跳过线程 "gmain" (TID=31294)
🔄 恢复线程 "gdbus" (TID=31296)
✅ 成功恢复 PID=31296
🔄 恢复线程 "Thread-38" (TID=31297)
✅ 成功恢复 PID=31297
```
# **完整源码**
```
// 加载 libc 中的 kill 函数
const killPtr = Module.findExportByName(null, 'kill');
const kill = new NativeFunction(killPtr, 'int', ['int', 'int']);
// 信号常量
const SIGSTOP = 19; // 暂停进程(不可被捕获)
const SIGCONT = 18; // 继续执行进程
/**
* 挂起线程
*
* @param pid 线程 id
*/
function suspendThread(pid) {
const result = kill(pid, SIGSTOP);
if (result === 0) {
console.log(`✅ 成功挂起 PID=${pid}`);
} else {
console.log(`❌ 挂起失败 PID=${pid}`);
}
}
/**
* 恢复线程
*
* @param pid 线程 id
*/
function resumeThread(pid) {
const result = kill(pid, SIGCONT);
if (result === 0) {
console.log(`✅ 成功恢复 PID=${pid}`);
} else {
console.log(`❌ 恢复失败 PID=${pid}`);
}
}
/**
* 读取文件内容并返回字符串
*
* @param path 文件路径
* @returns {string|null} 文件内容
*/
function readFileAsString(path) {
const File = Java.use("java.io.File");
const FileInputStream = Java.use("java.io.FileInputStream");
const InputStreamReader = Java.use("java.io.InputStreamReader");
const BufferedReader = Java.use("java.io.BufferedReader");
const StringBuilder = Java.use("java.lang.StringBuilder");
const file = File.$new(path);
if (!file.exists()) {
console.log(`❌ 文件不存在: ${path}`);
return null;
}
const fis = FileInputStream.$new(file);
const isr = InputStreamReader.$new(fis, "UTF-8");
const reader = BufferedReader.$new(isr);
const sb = StringBuilder.$new();
let line;
while ((line = reader.readLine()) !== null) {
sb.append(line);
}
reader.close();
return sb.toString();
}
/**
* comm 文件内容就是线程名称
*
* @param pid 进程 id
* @param tid 线程 id
* @returns {string} 线程名称
*/
function readComm(pid, tid) {
const path = `/proc/${pid}/task/${tid}/comm`;
return readFileAsString(path);
}
/**
* 挂起除了 excludeList 中指定名字的所有线程
* @param {number} pid - 当前进程 PID
* @param {string[]} excludeList - 不需要挂起的线程名字数组
*/
function suspendOtherThread(pid, excludeList) {
Java.perform(function () {
const taskDir = `/proc/${pid}/task/`;
var dirList = Java.use('java.io.File').$new(taskDir).listFiles()
for (let i = 0; i < dirList.length; i++) {
const tid = parseInt(dirList[i].getName());
if (isNaN(tid)) continue;
const threadName = readComm(pid, tid);
if (!threadName) continue;
// 只要 threadName 包含 excludeList 中的任意关键字,就会被排除(跳过不挂起)
if (excludeList.some(keyword => threadName.includes(keyword))) {
console.log(`🟢 跳过线程 "${threadName}" (TID=${tid})`);
} else {
console.log(`🛑 挂起线程 "${threadName}" (TID=${tid})`);
suspendThread(tid);
}
}
})
}
/**
* 恢复除了 excludeList 中指定名字的所有线程
* @param {number} pid - 当前进程 PID
* @param {string[]} excludeList - 不需要恢复的线程名字数组
*/
function resumeOtherThread(pid, excludeList) {
Java.perform(function () {
const taskDir = `/proc/${pid}/task/`;
var dirList = Java.use('java.io.File').$new(taskDir).listFiles()
for (let i = 0; i < dirList.length; i++) {
const tid = parseInt(dirList[i].getName());
if (isNaN(tid)) continue;
const threadName = readComm(pid, tid);
if (!threadName) continue;
// 只要 threadName 包含 excludeList 中的任意关键字,就会被排除(跳过不恢复)
if (excludeList.some(keyword => threadName.includes(keyword))) {
console.log(`🟢 跳过线程 "${threadName}" (TID=${tid})`);
} else {
console.log(`🔄 恢复线程 "${threadName}" (TID=${tid})`);
resumeThread(tid);
}
}
})
}
// 示例:只保留 excludeList 中指定的线程运行,其它全部挂起
const pid = Process.id;
const excludeList = [
"main",
"RenderThread",
"m.cyrus.example",
"frida",
];
// suspendOtherThread(pid, excludeList);
resumeOtherThread(pid, excludeList);
```
执行脚本
```
frida -H 127.0.0.1:1234 -F -l kill.js
```
开源地址:[https://github.com/CYRUS-STUDIO/frida_thread](https://github.com/CYRUS-STUDIO/frida_thread)