编译frida-server隐藏特征
前言
在逆向分析安卓应用的时候,经常会遇到frida-server的检测,frida-server的检测方式有很多种,比如检测进程名、检测端口等等,本文主要介绍如何编译frida-server隐藏特征,使其在运行时不容易被检测到。
frida项目:https://github.com/frida/frida
patch项目:https://github.com/taisuii/rusda
常见的frida检测方法
D-Bus
fridaserver是使用 D-Bus 协议通信,通过为每个开放端口发送 D-Bus 的认证消息,哪个端口回复了哪个就是 fridaserver,但是此方法需要的时间会比较长,并且其他的服务也可能回复D-Bus的认证消息,所以此方法并不是很准确。
for(i = 0 ; i <= 65535 ; i++) {
sock = socket(AF_INET , SOCK_STREAM , 0);
sa.sin_port = htons(i);
if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "FRIDA DETECTION [1]: Open Port: %d", i);
memset(res, 0 , 7);
// send a D-Bus AUTH message. Expected answer is “REJECT"
send(sock, "\x00", 1, NULL);
send(sock, "AUTH\r\n", 6, NULL);
usleep(100);
if (ret = recv(sock, res, 6, MSG_DONTWAIT) != -1) {
if (strcmp(res, "REJECT") == 0) {
/* Frida server detected. Do something… */
}
}
}
close(sock);
}
进程名
进程名方式检测在高版本Android上是无法使用的,由于系统安全性和权限限制的增强,应用程序无法通过遍历系统进程名来检测特定的调试器进程,目前使用这种方式检测frida的基本上已经没有了,这种检测方案基本上是没有意义的。
// Android5.1以上无效
public boolean checkRunningProcesses() {
boolean returnValue = false;
// Get currently running application processes
List<RunningServiceInfo> list = manager.getRunningServices(300);
if(list != null){
String tempName;
for(int i=0;i<list.size();++i){
tempName = list.get(i).process;
if(tempName.contains("fridaserver")) {
returnValue = true;
}
}
}
return returnValue;
}
默认端口
frida默认端口27047,通过检测默认端口是否开放来检测frida是否开启,在启动时指定端口可绕过。
boolean is_frida_server_listening() {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(27047);
inet_aton("127.0.0.1", &(sa.sin_addr));
int sock = socket(AF_INET , SOCK_STREAM , 0);
if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
}
}
默认路径
frida默认会在/data/local/tmp/re.frida.server/frida-agent-64.so中存放frida-agent(负责注入相关),可以查找此路径下是否存在对应文件,可通过更改frida源码将frida名字改为其他名字,或者将frida-agent-64.so文件放到其他路径下绕过。
new File("/data/local/tmp/re.frida.server/frida-agent-64.so").exists()
maps
在Android系统中,/proc/[pid]/maps 文件是一个重要的系统文件,用于记录进程的内存映射信息
包括:
- 内存段的地址范围:表示内存段的起始地址和结束地址。
- 权限:表示该内存段的读(r)、写(w)、执行(x)权限。
- 偏移量:表示该内存段在文件中的偏移位置。
- 设备号和inode号:用于标识该内存段对应的文件。
- 路径名:表示该内存段对应的文件路径
frida在注入App后会在maps中显示frida的frida-agent.so的内存信息,可以通过搜索特征字符串来检测frida, 可以通过修改frida的名称来绕过。
char line[512];
FILE* fp;
fp = fopen("/proc/self/maps", "r");
if (fp) {
while (fgets(line, 512, fp)) {
if (strstr(line, "frida")) {
}
}
fclose(fp);
} else {
}
}
查看tcp连接信息
frida-server启动后/proc/net/tcp和/proc/net/tcp6中会有特殊标识:69a2,可以通过搜索tcp中的该字符串来检测frida是否启动, Frida Server 默认监听的端口是 27042,其对应的十六进制字符串是 :69a2。如果修改 Frida Server 的监听端口,那么 /proc/net/tcp 和 /proc/net/tcp6 中就不会出现 :69a2。
public static boolean mCheckFridaTcp(){
String[] stringArrayTcp6;
String[] stringArrayTcp;
String tcpStringTcp6 = mReadFile("/proc/net/tcp6");
String tcpStringTcp = mReadFile("/proc/net/tcp");
boolean isFridaExits = false;
if(null != tcpStringTcp6 && !"".equals(tcpStringTcp6)){
stringArrayTcp6 = tcpStringTcp6.split("\n");
for(String sa : stringArrayTcp6){
if(sa.toLowerCase().contains(":69a2")){
Log.e(TAG,"tcp文件中发现Frida特征");
isFridaExits = true;
}
}
}
if(null != tcpStringTcp && !"".equals(tcpStringTcp)){
stringArrayTcp = tcpStringTcp.split("\n");
for(String sa : stringArrayTcp){
if(sa.toLowerCase().contains(":69a2")){
Log.e(TAG,"tcp文件中发现Frida特征");
isFridaExits = true;
}
}
}
return isFridaExits;
内存扫描库特征
其实也是maps中的特征信息,可通过编译时修改LIBFRIDA的名称绕过。
static char keyword[] = "LIBFRIDA";
num_found = 0;
int scan_executable_segments(char * map) {
char buf[512];
unsigned long start, end;
sscanf(map, "%lx-%lx %s", &start, &end, buf);
if (buf[2] == 'x') {
return (find_mem_string(start, end, (char*)keyword, 8) == 1);
} else {
return 0;
}
}
void scan() {
if ((fd = open(AT_FDCWD, "/proc/self/maps", O_RDONLY, 0)) >= 0) {
while ((read_one_line(fd, map, MAX_LINE)) > 0) {
if (scan_executable_segments(map) == 1) {
num_found++;
}
}
if (num_found > 1) {
}
status文件特征
frida注入App后在App的/proc/self/task/pid/status文件中会存在一些frida的特征信息,如gmain、pool-frida、gdbus,可以通过这些特征进行检测,同样可通过改名解决。
if (strstr(line, "frida") || strstr(line, "gum-js") || strstr(line, "gmain")) {
fclose(fp);
rst = filepath;
strcat(rst, "==>");
strcat(rst, line);
return rst;
}
以上检测方法检测皆使用系统函数读取文件和进行检测,实际应用中有很多检测方案会自己通过汇编实现读取文件或者比对方法,此时就需要对具体情况进行具体分析
编译环境
- Ubuntu 22.04
- frida 16.6.6
- ndk v22.12.0
- node v22.12.0
编译步骤
# 在Ubuntu上安装编译所需的依赖包:
sudo apt update
sudo apt-get install build-essential git lib32stdc++-9-dev libc6-dev-i386
pip3 install lief # 用来修改二进制文件
1. 下载frida源码
cd ~
mkdir build & cd build
mkdir fs
git clone --recurse-submodules -b 16.2.1 https://github.com/frida/frida
cd frida
2. 安装node.js
# 构造下载URL
NODE_TAR_URL="https://nodejs.org/dist/v22.12.0/node-v22.12.0-linux-x64.tar.xz"
wget $NODE_TAR_URL
# 解压Node.js安装包到用户目录
tar -xf node-v22.12.0-linux-x64.tar.xz -C $HOME/bin
rm -r node-v22.12.0-linux-x64.tar.xz
# 设置环境变量
echo "export NODE_HOME=\$HOME/bin/node-v22.12.0-linux-x64" >> ~/.bashrc
echo "export PATH=\$NODE_HOME/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
# 验证安装
node -v
3. 安装Android NDK
# 查看所需的NDK版本
cat releng/setup-env.sh | grep "ndk_required="
# 输出示例:ndk_required=25
wget https://dl.google.com/android/repository/android-ndk-r25c-linux.zip
unzip android-ndk-r25c-linux.zip -d $HOME/bin/
rm -r android-ndk-r25c-linux.zip
# 设置环境变量
echo "export ANDROID_NDK_ROOT=\$HOME/bin/android-ndk-r25c" >> ~/.bashrc
echo "export PATH=\$ANDROID_NDK_ROOT:\$PATH" >> ~/.bashrc
source ~/.bashrc
# 验证安装
ndk-build -v
4.编译Frida
4.1正常编译Frida
cd ~/build/fs/frida
make core-android-arm64 -j8
-j8是为了加快编译速度,具体数值可以根据cpu核心数调整,笔者的习惯是1:1。
4.2魔改编译Frida
pip install lief # 安装lief库,用来修改二进制文件
patch项目:https://github.com/taisuii/rusda
rusda项目是frida-server的魔改版本,通过修改frida-server的源码,使其在运行时不容易被检测到。
cd ~/build/fs/
git clone https://github.com/taisuii/rusda
# 应用补丁文件
cd ~/build/fs/frida/frida-core
for patch in ~/build/fs/rusda/frida-core/*.patch; do
git apply "$patch"
done
cd ~/build/fs/frida/frida-gum
for patch in ~/build/fs/rusda/frida-gum/*.patch; do
git apply "$patch"
done
# 把python脚本移过去
cp ~/build/fs/rusda/frida-core/src/topatch.py ~/build/fs/frida/frida-core/src
开始编译
cd ~/build/fs/frida
ad
等待编译完成,下图可见编译好的frida-server的path是/home/ubuntu/build/fs/frida/build/frida-android-arm64/bin
将frida-server拷贝到手机上
adb push frida-server /data/local/tmp
adb shell
su
cd /data/local/tmp
chmod +x frida-server
./frida-server
也可根据patch手动修改。
5测试效果
以东呈青猫会app为例,首先启动原版的frida-server
注入如下脚本
Java.perform(function () {
console.log(111)
})
frida -U -f com.dossen.app -l test.js
提示:Process terminated
启动自编译的魔改frida-server
此时并未发现注入进程终端,说明魔改成功绕过了东城青猫会的检测。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,如有问题请邮件至2454612285@qq.com。