一枚酸心果子

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

绕过SSL Pinning实战:从原理到工具链的完整指南

SSL Pinning基础回顾

什么是SSL Pinning?

SSL Pinning(证书固定)是一种安全机制,通过将服务器的证书或公钥”固定”在客户端应用中,确保只接受特定的证书,防止中间人攻击。

核心原理

1
2
传统HTTPS验证:客户端 → 系统证书库 → 验证服务器证书
SSL Pinning验证:客户端 → 预置固定证书 → 直接验证

为什么需要绕过SSL Pinning?

  1. 安全测试:渗透测试中需要分析HTTPS流量
  2. 漏洞挖掘:发现应用中的安全缺陷
  3. 逆向分析:理解应用的网络通信机制
  4. 调试开发:开发过程中的网络调试需求

移动端SSL Pinning绕过

Android平台绕过技术

1. 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
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// Frida脚本:Android SSL Pinning绕过
function bypass_android_ssl_pinning() {
console.log("[+] Starting Android SSL Pinning bypass...");

// Hook X509TrustManager
try {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var X509Certificate = Java.use("java.security.cert.X509Certificate");

// Hook checkClientTrusted
X509TrustManager.checkClientTrusted.implementation = function(chain, authType) {
console.log("[+] checkClientTrusted called - bypassed");
};

// Hook checkServerTrusted
X509TrustManager.checkServerTrusted.implementation = function(chain, authType) {
console.log("[+] checkServerTrusted called - bypassed");
};

// Hook getAcceptedIssuers
X509TrustManager.getAcceptedIssuers.implementation = function() {
console.log("[+] getAcceptedIssuers called - returning empty array");
return Java.array("java.security.cert.X509Certificate", []);
};

console.log("[+] X509TrustManager hooked successfully");
} catch (e) {
console.log("[-] X509TrustManager hook failed: " + e);
}

// Hook OkHttp的CertificatePinner
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function(hostname, peerCertificates) {
console.log("[+] CertificatePinner.check called for: " + hostname + " - bypassed");
};
console.log("[+] OkHttp CertificatePinner hooked successfully");
} catch (e) {
console.log("[-] OkHttp CertificatePinner not found");
}

// Hook TrustKit
try {
var TrustKit = Java.use("com.datatheorem.android.trustkit.TrustKit");
TrustKit.getInstance.implementation = function() {
console.log("[+] TrustKit.getInstance called - bypassed");
return this.getInstance();
};
console.log("[+] TrustKit hooked successfully");
} catch (e) {
console.log("[-] TrustKit not found");
}

// Hook SSLContext
try {
var SSLContext = Java.use("javax.net.ssl.SSLContext");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(keyManagers, trustManagers, secureRandom) {
console.log("[+] SSLContext.init called - bypassed");
this.init(keyManagers, null, secureRandom);
};
console.log("[+] SSLContext hooked successfully");
} catch (e) {
console.log("[-] SSLContext hook failed: " + e);
}
}

// 执行绕过
bypass_android_ssl_pinning();

2. Xposed模块绕过

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
// Xposed模块:SSL Pinning绕过
public class SSLPinningBypass implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (!lpparam.packageName.equals("com.target.app")) {
return;
}

// Hook X509TrustManager
XposedHelpers.findAndHookMethod("javax.net.ssl.X509TrustManager",
lpparam.classLoader, "checkServerTrusted",
X509Certificate[].class, String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("[+] checkServerTrusted bypassed");
}
});

// Hook OkHttp CertificatePinner
XposedHelpers.findAndHookMethod("okhttp3.CertificatePinner",
lpparam.classLoader, "check",
String.class, List.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("[+] CertificatePinner.check bypassed");
}
});
}
}

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
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Python脚本:内存补丁绕过SSL Pinning
import frida
import sys

def on_message(message, data):
if message['type'] == 'send':
print(f"[*] {message['payload']}")
else:
print(message)

def bypass_ssl_pinning_memory_patch():
"""使用内存补丁绕过SSL Pinning"""

# 获取目标进程
device = frida.get_usb_device()
session = device.attach("com.target.app")

# 注入脚本
script = session.create_script("""
// 内存补丁绕过SSL Pinning
function patch_ssl_verification() {
// 查找并替换证书验证函数
var verify_cert_addr = Module.findExportByName("libssl.so", "SSL_CTX_set_verify");
if (verify_cert_addr) {
console.log("[+] Found SSL_CTX_set_verify at: " + verify_cert_addr);

// 替换函数实现
Interceptor.replace(verify_cert_addr, new NativeCallback(function(ctx, mode, callback) {
console.log("[+] SSL_CTX_set_verify called, disabling verification");
return 0;
}, 'int', ['pointer', 'int', 'pointer']));
}

// 查找并替换证书获取函数
var get_cert_addr = Module.findExportByName("libssl.so", "SSL_get_peer_certificate");
if (get_cert_addr) {
console.log("[+] Found SSL_get_peer_certificate at: " + get_cert_addr);

Interceptor.replace(get_cert_addr, new NativeCallback(function(ssl) {
console.log("[+] SSL_get_peer_certificate called, returning null");
return ptr(0);
}, 'pointer', ['pointer']));
}

// 查找并替换证书验证函数
var verify_cert_chain_addr = Module.findExportByName("libssl.so", "SSL_CTX_set_verify_callback");
if (verify_cert_chain_addr) {
console.log("[+] Found SSL_CTX_set_verify_callback at: " + verify_cert_chain_addr);

Interceptor.replace(verify_cert_chain_addr, new NativeCallback(function(ctx, callback) {
console.log("[+] SSL_CTX_set_verify_callback called, disabling callback");
return 0;
}, 'int', ['pointer', 'pointer']));
}
}

// 执行补丁
patch_ssl_verification();
""")

script.on('message', on_message)
script.load()

# 保持脚本运行
sys.stdin.read()

if __name__ == "__main__":
bypass_ssl_pinning_memory_patch()

iOS平台绕过技术

1. 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
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Frida脚本:iOS SSL Pinning绕过
function bypass_ios_ssl_pinning() {
console.log("[+] Starting iOS SSL Pinning bypass...");

// Hook SecTrustEvaluate
var SecTrustEvaluate = Module.findExportByName("Security", "SecTrustEvaluate");
if (SecTrustEvaluate) {
Interceptor.attach(SecTrustEvaluate, {
onEnter: function(args) {
console.log("[+] SecTrustEvaluate called");
},
onLeave: function(retval) {
console.log("[+] Forcing SecTrustEvaluate to return success");
retval.replace(ptr(0)); // errSecSuccess
}
});
console.log("[+] SecTrustEvaluate hooked successfully");
}

// Hook SecTrustEvaluateWithError (iOS 12+)
var SecTrustEvaluateWithError = Module.findExportByName("Security", "SecTrustEvaluateWithError");
if (SecTrustEvaluateWithError) {
Interceptor.attach(SecTrustEvaluateWithError, {
onEnter: function(args) {
console.log("[+] SecTrustEvaluateWithError called");
},
onLeave: function(retval) {
console.log("[+] Forcing SecTrustEvaluateWithError to return true");
retval.replace(ptr(1));
}
});
console.log("[+] SecTrustEvaluateWithError hooked successfully");
}

// Hook NSURLSessionDelegate
var NSURLSessionDelegate = ObjC.classes.NSURLSessionDelegate;
if (NSURLSessionDelegate) {
var originalDidReceiveChallenge = NSURLSessionDelegate['- URLSession:didReceiveChallenge:completionHandler:'];
Interceptor.attach(originalDidReceiveChallenge.implementation, {
onEnter: function(args) {
console.log("[+] NSURLSessionDelegate didReceiveChallenge called");
},
onLeave: function(retval) {
// 修改completionHandler调用
var completionHandler = args[4];
var block = new ObjC.Block(completionHandler);
block.implementation = function(disposition, credential) {
console.log("[+] Modified completionHandler called");
// 强制使用服务器证书
block.implementation(0, credential); // URLSessionAuthChallengeUseCredential
};
}
});
console.log("[+] NSURLSessionDelegate hooked successfully");
}

// Hook CFNetwork
var CFNetworkCopySystemProxySettings = Module.findExportByName("CFNetwork", "CFNetworkCopySystemProxySettings");
if (CFNetworkCopySystemProxySettings) {
Interceptor.attach(CFNetworkCopySystemProxySettings, {
onEnter: function(args) {
console.log("[+] CFNetworkCopySystemProxySettings called");
},
onLeave: function(retval) {
console.log("[+] CFNetworkCopySystemProxySettings bypassed");
retval.replace(ptr(0));
}
});
console.log("[+] CFNetworkCopySystemProxySettings hooked successfully");
}
}

// 执行绕过
bypass_ios_ssl_pinning();

2. Theos Tweak绕过

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
// Theos Tweak:iOS SSL Pinning绕过
%hook NSURLSessionConfiguration

- (NSURLSessionConfiguration *)defaultSessionConfiguration {
NSURLSessionConfiguration *config = %orig;
config.URLCredentialStorage = nil;
return config;
}

%end

%hook NSURLSession

- (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration {
NSURLSession *session = %orig;
return session;
}

%end

%hook NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
NSLog(@"[+] URLSession didReceiveChallenge called - bypassed");
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

%end

Web端SSL Pinning绕过

浏览器扩展绕过

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
// Chrome扩展:SSL Pinning绕过
// manifest.json
{
"manifest_version": 2,
"name": "SSL Pinning Bypass",
"version": "1.0",
"permissions": [
"webRequest",
"webRequestBlocking",
"https://*/*",
"http://*/*"
],
"background": {
"scripts": ["background.js"]
}
}

// background.js
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
// 拦截HTTPS请求,修改为HTTP
if (details.url.startsWith('https://')) {
var newUrl = details.url.replace('https://', 'http://');
return {redirectUrl: newUrl};
}
},
{urls: ["<all_urls>"]},
["blocking"]
);

代理服务器绕过

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# Python脚本:SSL Pinning代理绕过
import socket
import ssl
import threading
from urllib.parse import urlparse

class SSLPinningBypassProxy:
def __init__(self, local_port=8080):
self.local_port = local_port
self.server_socket = None

def start_proxy(self):
"""启动SSL Pinning绕过代理"""
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind(('localhost', self.local_port))
self.server_socket.listen(5)

print(f"[+] SSL Pinning Bypass Proxy started on port {self.local_port}")

while True:
client_socket, addr = self.server_socket.accept()
print(f"[+] New connection from {addr}")

# 处理客户端连接
thread = threading.Thread(target=self.handle_client, args=(client_socket,))
thread.daemon = True
thread.start()

def handle_client(self, client_socket):
"""处理客户端连接"""
try:
# 接收客户端数据
data = client_socket.recv(4096)
if not data:
return

# 解析HTTP请求
request = data.decode('utf-8', errors='ignore')
print(f"[+] Request: {request.split('\\n')[0]}")

# 提取目标URL
first_line = request.split('\\n')[0]
method, url, version = first_line.split(' ')

# 解析URL
parsed_url = urlparse(url)
host = parsed_url.hostname
port = parsed_url.port or 443

# 连接到目标服务器
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.connect((host, port))

# 创建SSL连接
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE

ssl_socket = context.wrap_socket(server_socket, server_hostname=host)

# 转发请求
ssl_socket.send(data)

# 接收响应
response = ssl_socket.recv(4096)

# 转发响应给客户端
client_socket.send(response)

# 关闭连接
ssl_socket.close()
server_socket.close()
client_socket.close()

except Exception as e:
print(f"[-] Error handling client: {e}")
client_socket.close()

def stop_proxy(self):
"""停止代理"""
if self.server_socket:
self.server_socket.close()

# 使用示例
if __name__ == "__main__":
proxy = SSLPinningBypassProxy(8080)
try:
proxy.start_proxy()
except KeyboardInterrupt:
proxy.stop_proxy()

自动化工具集成

1. 集成到Burp Suite

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
# Burp Suite扩展:SSL Pinning绕过
from burp import IBurpExtender, IHttpListener, IProxyListener
from java.io import PrintWriter

class SSLPinningBypass(IBurpExtender, IHttpListener, IProxyListener):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
callbacks.setExtensionName("SSL Pinning Bypass")
callbacks.registerHttpListener(self)
callbacks.registerProxyListener(self)

# 获取stdout和stderr
self._stdout = PrintWriter(callbacks.getStdout(), True)
self._stderr = PrintWriter(callbacks.getStderr(), True)

self._stdout.println("SSL Pinning Bypass extension loaded")

def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
"""处理HTTP消息"""
if messageIsRequest:
# 处理请求
request = messageInfo.getRequest()
self._stdout.println("Processing request: " + self._helpers.analyzeRequest(request).getUrl().toString())

# 修改请求头,绕过SSL Pinning
modified_request = self.modify_request_headers(request)
messageInfo.setRequest(modified_request)
else:
# 处理响应
response = messageInfo.getResponse()
self._stdout.println("Processing response")

def modify_request_headers(self, request):
"""修改请求头"""
request_info = self._helpers.analyzeRequest(request)
headers = request_info.getHeaders()

# 添加绕过SSL Pinning的请求头
headers.add("X-Forwarded-Proto: https")
headers.add("X-Forwarded-For: 127.0.0.1")
headers.add("X-Real-IP: 127.0.0.1")

# 重新构造请求
body = request[request_info.getBodyOffset():]
modified_request = self._helpers.buildHttpMessage(headers, body)

return modified_request

2. 集成到OWASP ZAP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# OWASP ZAP脚本:SSL Pinning绕过
from org.zaproxy.zap.extension.script import ScriptVars
from org.parosproxy.paros.network import HttpMessage
from org.parosproxy.paros.network import HttpRequestHeader
from org.parosproxy.paros.network import HttpResponseHeader

def sendingRequest(msg, initiator, helper):
"""发送请求前处理"""
# 修改请求头
headers = msg.getRequestHeader()
headers.setHeader("X-Forwarded-Proto", "https")
headers.setHeader("X-Forwarded-For", "127.0.0.1")
headers.setHeader("X-Real-IP", "127.0.0.1")

# 记录日志
print("[+] Modified request headers for SSL Pinning bypass")

def responseReceived(msg, initiator, helper):
"""接收响应后处理"""
# 记录响应信息
response_header = msg.getResponseHeader()
print("[+] Response received: " + str(response_header.getStatusCode()))

检测与防护

SSL Pinning检测工具

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# Python脚本:SSL Pinning检测
import os
import re
import zipfile
from pathlib import Path

class SSLPinningDetector:
def __init__(self, target_path):
self.target_path = target_path
self.pinning_indicators = []

def detect_ssl_pinning(self):
"""检测SSL Pinning"""
if self.target_path.endswith('.apk'):
return self.detect_apk_ssl_pinning()
elif self.target_path.endswith('.ipa'):
return self.detect_ios_ssl_pinning()
elif os.path.isdir(self.target_path):
return self.detect_source_ssl_pinning()
else:
return False

def detect_apk_ssl_pinning(self):
"""检测APK中的SSL Pinning"""
try:
with zipfile.ZipFile(self.target_path, 'r') as apk:
# 检查classes.dex
if 'classes.dex' in apk.namelist():
dex_data = apk.read('classes.dex')
if self.scan_dex_for_pinning(dex_data):
return True

# 检查资源文件
for file_name in apk.namelist():
if file_name.endswith(('.xml', '.json')):
file_data = apk.read(file_name)
if self.scan_resources_for_pinning(file_data):
return True

except Exception as e:
print(f"[-] Error analyzing APK: {e}")

return False

def scan_dex_for_pinning(self, dex_data):
"""扫描DEX文件中的SSL Pinning代码"""
dex_str = dex_data.decode('utf-8', errors='ignore')

# SSL Pinning相关字符串
pinning_strings = [
'CertificatePinner',
'TrustKit',
'SSLContext',
'X509TrustManager',
'checkServerTrusted',
'pinning',
'certificate',
'sha256',
'public-key-pins'
]

found_indicators = []
for string in pinning_strings:
if string.lower() in dex_str.lower():
found_indicators.append(string)

if found_indicators:
self.pinning_indicators.extend(found_indicators)
return True

return False

def scan_resources_for_pinning(self, resource_data):
"""扫描资源文件中的SSL Pinning配置"""
resource_str = resource_data.decode('utf-8', errors='ignore')

# 检查网络安全配置
if 'network_security_config' in resource_str:
if 'pin-set' in resource_str or 'certificates' in resource_str:
self.pinning_indicators.append('network_security_config')
return True

# 检查TrustKit配置
if 'trustkit' in resource_str.lower():
self.pinning_indicators.append('trustkit_config')
return True

return False

def detect_ios_ssl_pinning(self):
"""检测iOS应用中的SSL Pinning"""
# iOS应用检测逻辑
pass

def detect_source_ssl_pinning(self):
"""检测源代码中的SSL Pinning"""
for root, dirs, files in os.walk(self.target_path):
for file in files:
if file.endswith(('.java', '.kt', '.swift', '.m', '.h')):
file_path = os.path.join(root, file)
if self.scan_source_file(file_path):
return True

return False

def scan_source_file(self, file_path):
"""扫描单个源文件"""
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()

# 检查SSL Pinning相关代码
pinning_patterns = [
r'CertificatePinner',
r'TrustKit',
r'SSLContext',
r'X509TrustManager',
r'checkServerTrusted',
r'pinning',
r'certificate.*pin',
r'sha256.*pin'
]

for pattern in pinning_patterns:
if re.search(pattern, content, re.IGNORECASE):
self.pinning_indicators.append(f"{file_path}: {pattern}")
return True

except Exception as e:
print(f"[-] Error scanning file {file_path}: {e}")

return False

def get_pinning_indicators(self):
"""获取检测到的SSL Pinning指标"""
return self.pinning_indicators

# 使用示例
if __name__ == "__main__":
detector = SSLPinningDetector("target.apk")
has_pinning = detector.detect_ssl_pinning()

if has_pinning:
print("[+] SSL Pinning detected!")
print("Indicators:")
for indicator in detector.get_pinning_indicators():
print(f" - {indicator}")
else:
print("[-] No SSL Pinning detected")

结语

SSL Pinning绕过是移动端和Web端安全测试中的重要技能,同样对于防护端来说也是必不可少的一个环节

本文关键点:从原理到实现的完整技术栈,各个端的对应实施策略,以及防护端的建议

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