一枚酸心果子

果子果子果子果子果子~~~

Android App抓包防护与绕过技术

Android App抓包防护概述

什么是抓包防护

抓包防护是Android应用为了防止网络流量被第三方工具(如Burp Suite、Fiddler、Charles等)拦截和分析而实施的安全措施。这些防护措施旨在保护敏感数据的传输安全,防止中间人攻击和数据泄露。

Android App抓包防护架构图

graph TB
    A[Android App] --> B[网络请求]
    B --> C{防护机制检测}
    
    C --> D[SSL Pinning]
    C --> E[证书验证检测]
    C --> F[代理检测]
    C --> G[反调试检测]
    C --> H[应用完整性检测]
    
    D --> I[证书固定验证]
    E --> J[CA证书检测]
    F --> K[代理设置检查]
    G --> L[调试状态检查]
    H --> M[签名验证]
    
    I --> N{验证通过?}
    J --> N
    K --> N
    L --> N
    M --> N
    
    N -->|是| O[允许网络请求]
    N -->|否| P[拒绝网络请求]
    
    Q[抓包工具] --> R[Burp Suite]
    Q --> S[Charles]
    Q --> T[Fiddler]
    
    R --> U[中间人攻击]
    S --> U
    T --> U
    
    U --> V[拦截网络流量]
    V --> W[分析敏感数据]

主要防护机制

1. SSL/TLS证书固定(Certificate Pinning)

防护原理:应用在代码中硬编码了服务器的证书信息,只信任特定的证书,拒绝其他证书(包括用户安装的CA证书)。

SSL Pinning证书固定流程图

sequenceDiagram
    participant App as Android App
    participant SSL as SSL/TLS层
    participant Server as 服务器
    participant Proxy as 代理工具
    
    Note over App,Proxy: 正常连接流程
    App->>SSL: 发起HTTPS请求
    SSL->>Server: 建立TLS连接
    Server->>SSL: 返回服务器证书
    SSL->>App: 验证证书有效性
    App->>App: 检查证书哈希值
    App->>App: 与预设哈希值比较
    App->>Server: 证书验证通过,继续通信
    
    Note over App,Proxy: 代理拦截流程
    App->>SSL: 发起HTTPS请求
    SSL->>Proxy: 连接被代理拦截
    Proxy->>App: 返回代理证书
    App->>App: 检查证书哈希值
    App->>App: 与预设哈希值不匹配
    App->>App: 拒绝连接,抛出异常

实现方式
证书固定通常通过OkHttp库实现,开发者会在代码中硬编码服务器的证书哈希值。当应用发起HTTPS请求时,会验证服务器证书是否与预设的哈希值匹配,如果不匹配则拒绝连接。这种方式可以有效防止中间人攻击,但也会阻止合法的抓包工具。

1
2
3
4
5
6
7
// OkHttp证书固定示例
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build())
.build();

检测方法
检测证书固定可以通过尝试使用自签名证书连接服务器来实现。如果应用没有实施证书固定,连接会成功;如果实施了证书固定,会抛出证书验证异常。

1
2
3
4
5
6
7
8
9
10
11
12
// 检测证书固定
public boolean isCertificatePinningEnabled() {
try {
// 尝试使用自签名证书连接
URL url = new URL("https://api.example.com");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.connect();
return false; // 如果没有抛出异常,说明没有证书固定
} catch (Exception e) {
return true; // 抛出异常说明有证书固定
}
}

2. 证书验证绕过检测

防护原理:应用检测是否安装了用户CA证书,如果检测到则拒绝连接。

实现方式
应用通过检查系统的信任存储来检测是否安装了用户CA证书。常见的抓包工具如Burp Suite、Charles、Fiddler等都会安装自己的CA证书,应用可以通过检查证书的颁发者信息来识别这些工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 检测用户CA证书
public boolean hasUserCACertificate() {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];

// 检查是否有用户添加的CA证书
X509Certificate[] acceptedIssuers = defaultTrustManager.getAcceptedIssuers();
for (X509Certificate cert : acceptedIssuers) {
String subject = cert.getSubjectDN().getName();
if (subject.contains("Burp") || subject.contains("Charles") ||
subject.contains("Fiddler") || subject.contains("Custom")) {
return true;
}
}
return false;
} catch (Exception e) {
return false;
}
}

3. 网络环境检测

防护原理:检测设备是否连接了代理服务器,如果检测到代理则拒绝网络请求。

实现方式
网络环境检测主要通过检查系统代理设置和网络连接信息来实现。当用户配置了代理服务器时,系统会设置相应的代理属性,应用可以通过检查这些属性来判断是否存在代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 检测代理设置
public boolean isProxyEnabled() {
try {
// 检查系统代理设置
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");

if (proxyHost != null && proxyPort != null) {
return true;
}

// 检查网络连接
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

if (activeNetwork != null && activeNetwork.isConnected()) {
// 检查是否有代理
ProxyInfo proxyInfo = activeNetwork.getProxyInfo();
if (proxyInfo != null) {
return true;
}
}

return false;
} catch (Exception e) {
return false;
}
}

4. 反调试检测

防护原理:检测应用是否在调试模式下运行,如果检测到调试则拒绝网络请求。

反Hook检测流程图

flowchart TD
    A[应用启动] --> B[检测Hook工具]
    
    B --> C{检测Frida}
    C -->|发现| D[标记为被Hook]
    C -->|未发现| E{检测Xposed}
    
    E -->|发现| D
    E -->|未发现| F{检测Substrate}
    
    F -->|发现| D
    F -->|未发现| G{检测内存修改}
    
    G -->|发现| D
    G -->|未发现| H{检测异常行为}
    
    H -->|发现| D
    H -->|未发现| I[正常运行]
    
    D --> J[触发反Hook机制]
    J --> K[清除Hook痕迹]
    K --> L[退出应用]
    
    I --> M[继续正常执行]
    
    N[检测方法] --> O[文件系统检查]
    N --> P[进程检查]
    N --> Q[端口检查]
    N --> R[内存检查]
    N --> S[行为分析]

实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 检测调试状态
public boolean isDebugging() {
try {
// 检查调试标志
boolean isDebug = (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
if (isDebug) {
return true;
}

// 检查调试器连接
boolean isDebuggerConnected = Debug.isDebuggerConnected();
if (isDebuggerConnected) {
return true;
}

// 检查调试端口
try {
Socket socket = new Socket("127.0.0.1", 8700);
socket.close();
return true;
} catch (Exception e) {
// 端口未开放,不是调试状态
}

return false;
} catch (Exception e) {
return false;
}
}

5. 应用完整性检测

防护原理:检测应用是否被修改或重打包,如果检测到则拒绝网络请求。

实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 检测应用签名
public boolean isAppModified() {
try {
PackageManager pm = getPackageManager();
PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);

// 获取当前签名
Signature[] signatures = packageInfo.signatures;
String currentSignature = signatures[0].toCharsString();

// 与预期签名比较
String expectedSignature = "预期签名的SHA1值";
if (!currentSignature.equals(expectedSignature)) {
return true;
}

return false;
} catch (Exception e) {
return true;
}
}

绕过技术详解

Frida Hook原理图

graph LR
    A[原始函数] --> B[函数入口点]
    B --> C[原始函数体]
    C --> D[函数返回]
    
    E[Frida Hook] --> F[Hook函数入口]
    F --> G[自定义Hook函数]
    G --> H[修改参数/返回值]
    H --> I[调用原始函数]
    I --> J[处理返回结果]
    J --> K[返回修改后的结果]
    
    B -.->|被Hook| F
    F -.->|替换| B
    
    L[应用进程] --> M[加载Frida Agent]
    M --> N[注册Hook点]
    N --> O[动态修改函数]
    O --> P[执行Hook逻辑]

1. SSL Pinning绕过

方法一:Frida Hook

Frida Hook是最常用的SSL Pinning绕过方法,通过Hook证书验证相关的函数来实现绕过。这种方法不需要修改应用代码,只需要在运行时动态Hook即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Frida Hook SSL Pinning
Java.perform(function() {
// Hook OkHttp的CertificatePinner
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function(hostname, peerCertificates) {
console.log("绕过SSL Pinning: " + hostname);
return;
};

// Hook X509TrustManager
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
X509TrustManager.checkClientTrusted.implementation = function(chain, authType) {
console.log("绕过客户端证书检查");
};

X509TrustManager.checkServerTrusted.implementation = function(chain, authType) {
console.log("绕过服务端证书检查");
};

X509TrustManager.getAcceptedIssuers.implementation = function() {
return Java.use("java.util.ArrayList").$new();
};
});

方法二:Xposed模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Xposed模块绕过SSL Pinning
public class SSLUnpinning implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// Hook OkHttp
XposedHelpers.findAndHookMethod("okhttp3.CertificatePinner", lpparam.classLoader,
"check", String.class, List.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return null;
}
});

// Hook TrustManager
XposedHelpers.findAndHookMethod("javax.net.ssl.X509TrustManager", lpparam.classLoader,
"checkServerTrusted", X509Certificate[].class, String.class, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return null;
}
});
}
}

方法三:内存补丁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 内存补丁绕过SSL Pinning
#include <jni.h>
#include <string.h>

JNIEXPORT void JNICALL Java_com_example_SSLUnpinner_unpin(JNIEnv *env, jobject thiz) {
// 获取SSL_CTX_set_verify函数地址
void *ssl_ctx_set_verify = dlsym(RTLD_DEFAULT, "SSL_CTX_set_verify");

if (ssl_ctx_set_verify) {
// 修改函数实现
unsigned char patch[] = {0x90, 0x90, 0x90, 0x90, 0x90}; // NOP指令
mprotect(ssl_ctx_set_verify, 5, PROT_READ | PROT_WRITE | PROT_EXEC);
memcpy(ssl_ctx_set_verify, patch, 5);
mprotect(ssl_ctx_set_verify, 5, PROT_READ | PROT_EXEC);
}
}

2. 代理检测绕过

方法一:Hook系统属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Frida Hook代理检测
Java.perform(function() {
// Hook System.getProperty
var System = Java.use("java.lang.System");
System.getProperty.overload("java.lang.String").implementation = function(key) {
if (key === "http.proxyHost" || key === "http.proxyPort" ||
key === "https.proxyHost" || key === "https.proxyPort") {
console.log("绕过代理检测: " + key);
return null;
}
return this.getProperty(key);
};

// Hook ConnectivityManager
var ConnectivityManager = Java.use("android.net.ConnectivityManager");
ConnectivityManager.getActiveNetworkInfo.implementation = function() {
var networkInfo = this.getActiveNetworkInfo();
if (networkInfo) {
// 清除代理信息
networkInfo.setProxyInfo(null);
}
return networkInfo;
};
});

方法二:修改网络配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 修改网络配置绕过代理检测
public class ProxyBypass {
public static void bypassProxyDetection() {
try {
// 清除系统代理设置
System.setProperty("http.proxyHost", "");
System.setProperty("http.proxyPort", "");
System.setProperty("https.proxyHost", "");
System.setProperty("https.proxyPort", "");

// 修改网络配置
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
// 强制使用直连
cm.setNetworkPreference(ConnectivityManager.TYPE_WIFI);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

3. 反调试绕过

方法一:Frida Hook反调试检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Frida Hook反调试检测
Java.perform(function() {
// Hook Debug.isDebuggerConnected
var Debug = Java.use("android.os.Debug");
Debug.isDebuggerConnected.implementation = function() {
console.log("绕过调试检测");
return false;
};

// Hook ApplicationInfo.FLAG_DEBUGGABLE
var ApplicationInfo = Java.use("android.content.pm.ApplicationInfo");
ApplicationInfo.flags.implementation = function() {
var flags = this.flags;
// 清除调试标志
flags = flags & ~ApplicationInfo.FLAG_DEBUGGABLE.value;
return flags;
};

// Hook Socket连接检测
var Socket = Java.use("java.net.Socket");
Socket.$init.overload("java.lang.String", "int").implementation = function(host, port) {
if (host === "127.0.0.1" && port === 8700) {
console.log("绕过调试端口检测");
throw Java.use("java.net.ConnectException").$new("Connection refused");
}
return this.$init(host, port);
};
});

方法二:修改应用标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 修改应用标志绕过反调试
public class DebugBypass {
public static void bypassDebugDetection() {
try {
// 修改调试标志
ApplicationInfo appInfo = getApplicationInfo();
appInfo.flags = appInfo.flags & ~ApplicationInfo.FLAG_DEBUGGABLE;

// 修改系统属性
System.setProperty("ro.debuggable", "0");
System.setProperty("ro.secure", "1");

} catch (Exception e) {
e.printStackTrace();
}
}
}

4. 应用完整性绕过

方法一:Hook签名验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Frida Hook签名验证
Java.perform(function() {
// Hook PackageManager.getPackageInfo
var PackageManager = Java.use("android.content.pm.PackageManager");
PackageManager.getPackageInfo.overload("java.lang.String", "int").implementation = function(packageName, flags) {
var packageInfo = this.getPackageInfo(packageName, flags);

if (flags == PackageManager.GET_SIGNATURES.value) {
// 替换签名信息
var Signature = Java.use("android.content.pm.Signature");
var fakeSignature = Signature.$new("伪造的签名信息");
packageInfo.signatures = [fakeSignature];
}

return packageInfo;
};
});

方法二:修改签名文件

1
2
3
4
5
6
7
# 使用apktool重新签名
apktool d app.apk
# 修改应用代码
# 重新打包
apktool b app -o app_modified.apk
# 签名
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore app_modified.apk alias_name

进阶绕过技术

1. 动态Hook技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动态Hook技术
Java.perform(function() {
// 等待类加载
Java.choose("com.example.SecurityChecker", {
onMatch: function(instance) {
console.log("找到SecurityChecker实例");

// Hook实例方法
instance.checkSecurity.implementation = function() {
console.log("绕过安全检查");
return false; // 返回安全检查通过
};
},
onComplete: function() {
console.log("搜索完成");
}
});
});

2. 内存修改技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 内存修改技术
#include <sys/mman.h>
#include <unistd.h>

void patchMemory(void *addr, unsigned char *patch, size_t patchSize) {
// 修改内存保护
mprotect(addr, patchSize, PROT_READ | PROT_WRITE | PROT_EXEC);

// 应用补丁
memcpy(addr, patch, patchSize);

// 恢复内存保护
mprotect(addr, patchSize, PROT_READ | PROT_EXEC);
}

3. 系统调用Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 系统调用Hook
#include <sys/ptrace.h>
#include <sys/user.h>

long syscall_hook(int syscall_num, struct user_regs_struct *regs) {
switch (syscall_num) {
case SYS_ptrace:
// 绕过ptrace检测
return 0;
case SYS_kill:
// 绕过kill检测
return 0;
default:
return syscall(syscall_num, regs->rdi, regs->rsi, regs->rdx);
}
}

检测与防护

1. 检测Hook工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 检测Hook工具
public boolean isHooked() {
try {
// 检测Frida
if (isFridaDetected()) {
return true;
}

// 检测Xposed
if (isXposedDetected()) {
return true;
}

// 检测Substrate
if (isSubstrateDetected()) {
return true;
}

return false;
} catch (Exception e) {
return true;
}
}

private boolean isFridaDetected() {
try {
// 检测Frida相关文件
String[] fridaFiles = {
"/data/local/tmp/frida-server",
"/data/local/tmp/re.frida.server",
"/system/bin/frida-server"
};

for (String file : fridaFiles) {
if (new File(file).exists()) {
return true;
}
}

// 检测Frida端口
try {
Socket socket = new Socket("127.0.0.1", 27042);
socket.close();
return true;
} catch (Exception e) {
// 端口未开放
}

return false;
} catch (Exception e) {
return false;
}
}

2. 反Hook技术

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 反Hook技术
public class AntiHook {
public static void enableAntiHook() {
// 检测并清除Hook
if (isHooked()) {
// 退出应用
System.exit(0);
}

// 定期检查
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (isHooked()) {
System.exit(0);
}
}
}, 0, 5000); // 每5秒检查一次
}
}

结语

技术对抗演进图

timeline
    title Android抓包防护与绕过技术演进
    
    section 初期阶段
        2010-2015 : 基础HTTPS
                  : 简单代理检测
                  : 基础反调试
    
    section 发展阶段
        2015-2020 : SSL Pinning普及
                  : 证书固定技术
                  : 应用完整性检测
                  : Frida Hook兴起
                  : Xposed模块开发
    
    section 成熟阶段
        2020-2023 : 多层防护机制
                  : 智能检测算法
                  : 动态Hook技术
                  : 内存修改技术
                  : 系统调用Hook
    
    section 未来趋势
        2023+ : AI驱动检测
              : 机器学习防护
              : 云原生安全
              : 零信任架构
              : 量子加密

绕过技术分类图

mindmap
  root((Android抓包绕过技术))
    静态修改
      重打包应用
      修改证书验证
      替换CA证书
      修改网络配置
    动态Hook
      Frida Hook
        SSL Pinning绕过
        代理检测绕过
        反调试绕过
        签名验证绕过
      Xposed模块
        系统API Hook
        应用层Hook
        框架层Hook
      Substrate Hook
        iOS平台Hook
        运行时修改
    内存修改
      直接内存操作
      函数替换
      指令修改
      数据结构修改
    系统调用Hook
      内核层Hook
      系统调用拦截
      进程间通信Hook
      文件系统Hook

在实际应用中,无论是进行安全测试、漏洞挖掘,还是应用安全评估,都需要在合法合规的前提下使用这些技术。同时,开发者也需要不断加强应用的安全防护,采用多层防护策略,提高应用的安全性。

持续输出技术分享,您的支持将鼓励我继续创作!