0%

Android安全网络通信相关

  • Android安全网络通信相关

VPN检测

  • 检测常见VPN接口名以及网络特征
  • 网络接口:tun0 ppp0
  • 网络特征:WIFI|VPN
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
public final boolean Check_Vpn1() {
try {
//获取所有网络接口
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
if (networkInterfaces == null) {
return false;
}
Iterator it = Collections.list(networkInterfaces).iterator();
while (it.hasNext()) {
NetworkInterface networkInterface = (NetworkInterface) it.next();
//网络接口开启和接口地址非空
if (networkInterface.isUp() && !networkInterface.getInterfaceAddresses().isEmpty()) {
Log.d("zj595", "isVpn NetworkInterface Name: " + networkInterface.getName());
//常见VPN接口名:tun0 ppp0 p2p0 ccmni0
if (Intrinsics.areEqual(networkInterface.getName(), "tun0") || Intrinsics.areEqual(networkInterface.getName(), "ppp0") || Intrinsics.areEqual(networkInterface.getName(), "p2p0") || Intrinsics.areEqual(networkInterface.getName(), "ccmni0")) {
return true;
}
}
}
return false;
} catch (Throwable th) {
th.printStackTrace();
return false;
}
}
public final boolean Check_Vpn2() {
boolean z;
String networkCapabilities;
try {
Object systemService = getApplicationContext().getSystemService("connectivity");
Intrinsics.checkNotNull(systemService, "null cannot be cast to non-null type android.net.ConnectivityManager");
ConnectivityManager connectivityManager = (ConnectivityManager) systemService;
NetworkCapabilities networkCapabilities2 = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
Log.i("zj595", "networkCapabilities -> " + networkCapabilities2);
// Android 4 代表VPN
boolean z2 = networkCapabilities2 != null && networkCapabilities2.hasTransport(4);
// 检查网络能力是否包含 "WIFI|VPN"
if (networkCapabilities2 != null && (networkCapabilities = networkCapabilities2.toString()) != null) {
if (StringsKt.contains$default((CharSequence) networkCapabilities, (CharSequence) "WIFI|VPN", false, 2, (Object) null)) {
z = true;
return !z || z2;
}
}
z = false;
if (z) {
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
  • frida绕过
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
function hook_vpn() {
Java.perform(function () {
var NetworkInterface = Java.use("java.net.NetworkInterface");
NetworkInterface.getName.implementation = function () {
var name = this.getName(); //hook java层的getName方法
console.log("name: " + name);
if (name === "tun0" || name === "ppp0") {
return "rmnet_data0";
} else {
return name;
}
}

var NetworkCapabilities = Java.use("android.net.NetworkCapabilities");
NetworkCapabilities.hasTransport.implementation = function () {
return false;
}

NetworkCapabilities.appendStringRepresentationOfBitMaskToStringBuilder.implementation = function (sb, bitMask, nameFetcher, separator) {
if (bitMask == 18) {
console.log("bitMask", bitMask);
sb.append("WIFI");
} else {
console.log(sb, bitMask);
this.appendStringRepresentationOfBitMaskToStringBuilder(sb, bitMask, nameFetcher, separator);
}
}


})
}

SSLPinning(加强版单向校验)

指纹校验

例子:okhttp3

CertificatePinner的check方法会检测给定hash和证书hash是否匹配

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
/**
* 检查指定主机名的证书链是否符合预设的哈希值(证书固定)。
* @param hostname 要验证的主机名。
* @param peerCertificates 待验证的证书列表。
* @throws SSLPeerUnverifiedException 如果证书不符合预设的哈希值,则抛出此异常。
*/
public void check(String hostname, List<Certificate> peerCertificates)
throws SSLPeerUnverifiedException {
// 查找与主机名匹配的哈希值列表(证书固定列表)。
List<Pin> pins = findMatchingPins(hostname);
// 如果没有找到任何匹配的哈希值,则直接返回,表示无需进一步检查。
if (pins.isEmpty()) return;

// 如果存在证书链清理器,则先清理证书链中的冗余证书。
if (certificateChainCleaner != null) {
peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
}
// 遍历每一个证书进行检查。
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
// 获取当前证书。
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
// 懒加载计算每个证书的SHA-1和SHA-256哈希值。
ByteString sha1 = null;
ByteString sha256 = null;
// 遍历预设的哈希值列表。
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
// 根据预设的哈希算法进行检查。
if (pin.hashAlgorithm.equals("sha256/")) {
// 如果尚未计算SHA-256哈希值,则进行计算。
if (sha256 == null) sha256 = sha256(x509Certificate);
// 如果证书的SHA-256哈希值与预设值相同,则返回成功。
if (pin.hash.equals(sha256)) return;
} else if (pin.hashAlgorithm.equals("sha1/")) {
// 如果尚未计算SHA-1哈希值,则进行计算。
if (sha1 == null) sha1 = sha1(x509Certificate);
// 如果证书的SHA-1哈希值与预设值相同,则返回成功。
if (pin.hash.equals(sha1)) return;
} else {
// 如果遇到不支持的哈希算法,则抛出错误。
throw new AssertionError("unsupported hashAlgorithm: " + pin.hashAlgorithm);
}
}
}
// 如果遍历完所有证书和哈希值都没有匹配,则抛出异常。
throw new SSLPeerUnverifiedException("No matching certificate found.");
}

frida绕过(check函数直接置空干掉)

1
2
3
4
5
6
7
function anti_ssl_key() {
//check方法置空即可
var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');
okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {
console.log('[+] Bypassing SSL key pinning: ' + a);
return;
}}

证书校验

  • 将服务器发来的证书的公钥base64和本地证书公钥base64进行匹配
  • trustManager 类的checkServerTrusted接口

同样frida绕过,模拟实例化trustManager类置空,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
function anti_ssl_cert() {
// 使用Frida获取Java类X509TrustManager的引用
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
// 使用Frida获取Java类SSLContext的引用
var SSLContext = Java.use('javax.net.ssl.SSLContext');
// 注册一个自定义的TrustManager类
var TrustManager = Java.registerClass({
// 指定自定义TrustManager的全名
name: 'dev.asd.test.TrustManager',
// 指定自定义TrustManager实现的接口
implements: [X509TrustManager],
// 定义自定义TrustManager的方法实现
methods: {
// 客户端证书信任检查,这里不实现任何逻辑
checkClientTrusted: function(chain, authType) {},
// 服务器证书信任检查,这里不实现任何逻辑
checkServerTrusted: function(chain, authType) {},
// 返回受信任的CA证书数组,这里返回空数组
getAcceptedIssuers: function() {return []; }
}
});
// 准备一个TrustManager数组,用于传递给SSLContext.init()方法
var TrustManagers = [TrustManager.$new()];
// 获取SSLContext.init()方法的引用,该方法用于初始化SSL上下文
var SSLContext_init = SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom'
);
try {
// 覆盖init方法的实现,指定使用自定义的TrustManager
SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
console.log('[+] Bypassing Trustmanager (Android < 7) pinner');
// 调用原始的init方法,并使用自定义的TrustManager数组
SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
};
} catch (err) {
// 如果覆盖init方法失败,打印错误信息
console.log('[-] TrustManager (Android < 7) pinner not found');
console.log(err); // 可以取消注释来打印异常的详细信息
}
}

双向认证

  • 服务端发送服务器公钥–>客户端校验后发送客户端证书公钥–>服务端校验客户端证书,拿到客户端公钥–>沟通加密方案–>对称加密

案例

  1. 服务端
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
from flask import Flask, jsonify 
import ssl

app = Flask(__name__)

# ssl 证书校验
@app.route('/ca')
def ssl_verify():
return jsonify({"message": "HTTPS server with mutual SSL verification started."})

# 配置ssl上下文,关键函数
def get_ssl_context():
# CA根证书路径
ca_crt_path = 'certs/ca.crt'
# 服务端证书和密钥路径
server_crt_path = 'certs/server.crt'
server_key_path = 'certs/server.key'
# 创建SSL上下文,使用TLS服务器模式
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# 设置验证模式为需要客户端证书
ssl_context.verify_mode = ssl.CERT_REQUIRED
# 启用主机名检查(根据需要设置)
ssl_context.check_hostname = False
# 设置加密套件
ssl_context.set_ciphers("HIGH:!SSLv3:!TLSv1:!aNULL:@STRENGTH")
# 加载CA根证书,用于验证客户端证书
ssl_context.load_verify_locations(cafile=ca_crt_path)
# 加载服务端证书和私钥
ssl_context.load_cert_chain(certfile=server_crt_path, keyfile=server_key_path)
return ssl_context

if __name__ == '__main__':
ssl_context = get_ssl_context()
app.run(host="192.168.124.21", port=8088,ssl_context=ssl_context)
  1. 客户端
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
fun ssl_verify() = Thread {  
// 初始化一个用于信任管理的对象
var trustManager: X509TrustManager? = null
try {
// 获取默认的信任管理工厂实例
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
// 初始化信任管理工厂,传入null表示使用系统默认的信任存储
trustManagerFactory.init(null as KeyStore?)
// 获取信任管理器列表
val trustManagers = trustManagerFactory.trustManagers
// 检查信任管理器列表是否只有一个X509TrustManager类型的对象
if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
// 如果不符合预期,则抛出异常
throw IllegalStateException("Unexpected default trust managers: ${trustManagers.contentToString()}")
}
// 赋值信任管理器
trustManager = trustManagers[0] as X509TrustManager
} catch (e: Exception) {
// 捕获异常并打印堆栈跟踪信息
e.printStackTrace()
}
// 创建 OkHttpClient 实例并配置SSL套接字工厂和主机名验证器
val client = OkHttpClient.Builder()
.sslSocketFactory(
// 使用应用程序上下文获取自定义的SSL套接字工厂
ClientSSLSocketFactory.getSocketFactory(applicationContext),
// 设置信任管理器,如果为null则抛出异常
trustManager ?: throw IllegalStateException("TrustManager is null")
)
.hostnameVerifier { hostname, session -> true }
.build()
// 构建请求
val request = Request.Builder()
.url("https://192.168.124.21:8088/ca") // 设置请求的URL
.build()
try {
// 发送HTTP请求并获取响应
val response = client.newCall(request).execute()
// 打印响应的状态码,表明HTTPS双向认证成功
Log.d(TAG, "双向检测通过:${response.code()}")
} catch (e: IOException) {
// 如果请求失败,打印错误信息,并记录堆栈跟踪
Log.d(TAG, "双向检测不通过")
e.printStackTrace()
}
}.start()
  • frida dump内置证书
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
function hook_KeyStore_load() {
Java.perform(function () {
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
var myArray=new Array(1024);
var i = 0
for (i = 0; i < myArray.length; i++) {
myArray[i]= 0x0;
}
var buffer = Java.array('byte',myArray);
var StringClass = Java.use("java.lang.String");
var KeyStore = Java.use("java.security.KeyStore");
KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log("KeyStore.load1:", arg0);
this.load(arg0);
};
KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
if (arg0){
var file = Java.use("java.io.File").$new("/data/user/0/com.zj.wuaipojie/files/client"+".p12");
var out = Java.use("java.io.FileOutputStream").$new(file);
var r;
while( (r = arg0.read(buffer)) > 0){
out.write(buffer,0,r)
}
console.log("证书保存成功!")
out.close()
}
this.load(arg0, arg1);
};
});
}

原文链接:[原创]《安卓逆向这档事》第二十一课、抓包学得好,牢饭吃得饱(中)-Android安全-看雪-安全社区|安全招聘|kanxue.com

Socket通信Hook

1
2
3
4
5
6
7
8
# tcp
socketRead0 socketWrite0
recvfrom sendto
# udp
sendtoBytes readBytes
recvfrom sendto
# ssl
SSL_write SSL_read【find SSL库函数】

python爬虫