1、上次自己構造了一個app來調用x音的關鍵so,結果在一條“LDR R0, [R4,#0xC] “語句卡住了:通過ida查看得知:R4就是第三個參數,這里被當成了地址使用(java層怪不得用long類型)!第三個參數我是用frida hook得到的,換了個環境地址肯定也變了,所以這里直接”抄襲“拿過來用肯定報錯,這種反調試的方法實在是秒啊!動態調試暫時卡殼,我們先來靜態分析一下卡住語句的上下游:
.text:000139A4 000 F0 B5 PUSH {R4-R7,LR} .text:000139A6 014 03 AF ADD R7, SP, #0xC .text:000139A8 014 2D E9 00 0F PUSH.W {R8-R11} .text:000139AC 024 93 B0 SUB SP, SP, #0x4C .text:000139AE 070 CD E9 03 23 STRD.W R2, R3, [SP,#0x68+var_5C] .text:000139B2 070 05 46 MOV R5, R0 .text:000139B4 070 3A 48 LDR R0, =(__stack_chk_guard_ptr - 0x139BC) .text:000139B6 070 0C 46 MOV R4, R1 .text:000139B8 070 78 44 ADD R0, PC ; __stack_chk_guard_ptr .text:000139BA 070 00 68 LDR R0, [R0] ; __stack_chk_guard .text:000139BC 070 01 90 STR R0, [SP,#0x68+var_64] .text:000139BE 070 00 68 LDR R0, [R0] .text:000139C0 070 38 49 LDR R1, =(unk_99110 - 0x139CA) .text:000139C2 070 12 90 STR R0, [SP,#0x68+var_20] .text:000139C4 070 E0 68 LDR R0, [R4,#0xC] ; 自己構造的app在R4的值是第3個long參數,地址無效 .text:000139C6 070 79 44 ADD R1, PC ; unk_99110 .text:000139C8 070 10 90 STR R0, [SP,#0x68+var_28] .text:000139CA 070 08 31 ADDS R1, #8 .text:000139CC 070 04 30 ADDS R0, #4 ; rwlock .text:000139CE 070 0F 91 STR R1, [SP,#0x68+var_2C] .text:000139D0 070 F3 F7 36 EF BLX pthread_rwlock_rdlock .text:000139D4 070 11 90 STR R0, [SP,#0x68+var_24]
卡住的語句后面做了注釋:從R4+C的地方取4字節數據存入R0,然后把R0存到棧上;接着把R0+4,這里ida已經識別出了是rwlock讀寫鎖,然后就是調用pthread_rwlock_rdlock獲取讀寫鎖的讀鎖!這就很關鍵了:
(1)為什么要對數據加讀鎖了而不是互斥鎖了?在互斥機制中,讀者和寫者都需要獨立獨占互斥量以獨占共享資源;而在讀寫鎖機制下,允許同時有多個讀者讀訪問共享資源,只有寫者才需要獨占資源。相比互斥機制,讀寫機制由於允許多個讀者同時讀訪問共享資源,進一步提高了多線程的並發度。這里我個人猜測:加密字段輸入了長長的url+http頭的很多字段,需要輸出4個加密字段,如果讓單線程順序讀取並計算,效率很低,影響用戶體驗,所以使用多線程機制協同:多個線程同時讀取輸入,分別計算不同的加密字段!
(2)從ida的trace記錄看,前面所有的指令都沒有讀取棧上保存的url+http頭的數據,所以前面肯定還沒來得及生成那4個加密字段;從這里開始用讀寫鎖,結合上面的分析大膽猜測:接下來要開始生成加密字段了!換個角度,這個函數附近有好些地方都調用pThread_Create函數,從這里開始計算機密字段嫌疑很大!
2、既然卡在了第三個地址參數,自然需要分析一下這個參數的來源,直接上frida,先hook h.a方法,看看這個函數在java層的調用路徑:
at ms.bd.c.h.a(Native Method) at ms.bd.c.b.a() at ms.bd.c.u1$a.a(:34013379)
發現是u1$a.a在調用,在jadx靜態分析u1$a.a這個方法,發現是
String[] strArr = (String[]) C85964b.m218696a(50331649, 0, C86039u1.this.f357108d, str, (String[]) arrayList.toArray(new String[0])); 這行代碼調用了b.a方法,里面涉及到了long參數(已經標黃),而這個long參數是u1類的成員變量,在u1.a方法被設置了,這個函數的參數只有1個long類型,繼續hook這個方法:調用棧如下:
神奇地發現:居然還是h.a在調用,形成環形了,唯一的解釋:h.a參數不同,生成的結果就不同!之前hook h.a的時候確實也發現了其他參數,但當時目的是追蹤這4個加密字段,完全沒重視其他參數。哎,悔不當初!
接着追溯:上面調用棧中有一個方法是com.ss.android.ugc.aweme.sec.SecApiImpl.initSec,該方法部分代碼如下(太多了,這里只貼一部分);
public final void initSec(Context context, String str, int i, String str2, String str3, boolean z, SecGetDataCallBack dVar) { String str4; if (!PatchProxy.proxy(new Object[]{context, str, Integer.valueOf(i), str2, str3, Byte.valueOf(z ? (byte) 1 : 0), dVar}, this, changeQuickRedirect, false, 338895).isSupported) { C51302a.m136764a(4, "Sec", "initSec"); if (!PatchProxy.proxy(new Object[]{context, str, Integer.valueOf(i), str2, str3, Byte.valueOf((byte) z), dVar}, null, DmtSec.f284302a, true, 338875).isSupported) { DmtSec.f284308g = new SecInitReceiver(DmtSec.C66800a.f284321b); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("com.ms.init"); DmtSec.m171500a(context, DmtSec.f284308g, intentFilter); SecLogger bVar = SecLogger.f284353b; bVar.mo193744a(SecLogger.f284354c, "init language = " + str + ", aid = " + i + ", appName = " + str2 + ", channel= " + str3); long currentTimeMillis = System.currentTimeMillis(); long currentTimeMillis2 = System.currentTimeMillis(); GlobalContext.setContext(context); C30329a.C30330a aVar = new C30329a.C30330a(String.valueOf(i), "bo95dJizD1WFcV03zOuLzN5Pn1sFtVa3szqiVQmflMJTNW0p0Kpqfw8D4i0zUlfrou4kuYt/i0521YRygM83dwv/wn3DD+TMJF+QFzW9wb8Qq2/1B4jPMbObrDNdyMMukpAYqy1fLWtbLGVIPxsFsZegwQy5lsRX9h49PH/Qx8MwgYvWvH7ZTFLV28LwTWZiljQyBPaBE+TsyumEu0Y+JRkeidHFEYcVs0yRoa+xC004hugQhdPupIt6dBiWA4phsB3fNJZjFTAKGE1lPB4gzt6Qf+FmlgZBbRvT8zekxTV2HZ5dUvSutB2/0QpbHKAvWL4DRA=="); aVar.mo248346a(0); aVar.mo248347a("tk_key", "douyin");
代碼里面有寫死的字符串,比如tk_key、douyin,還有很長的base64編碼的bo95dJizD1WFcV03zOuLzN5Pn1sFtVa3sz....,剛好在hook h.a方法的時候,有一組參數是這樣的:
猜測args[3]就是在initSec這里拼湊出來的!回到u1.a方法的調用棧,分析這個棧的思路:一層一層網上追溯,看看到底是哪個地方改變了long參數的值,根據一些magic number,找到了
ms.bd.c.ml.a()調用ms.bd.c.b.a(67108866,str)函數生成了a,a被強制轉成了long類型,所以這里重點關注ms.bd.c.b.a(67108866,str)這個函數;由於還有str參數不知道是啥,我們自己調用的時候也不知道怎么傳參,所以這里繼續hook ms.bd.c.b.a函數,看看傳入了什么參數,如下:
發現另一個參數是1128,返回是-1500374080=0xFFFFFFFFA6921BC0,看着確實像個地址;數值上和hook u1.a時看着差異較大,但我能確定就是這個函數生成了long參數,原因:
(1)數值不等可能是js不制obj直接轉成long導致的,需要想辦法轉換類型
(2)從ms.bd.c.ml.a()函數的代碼分析,調用ms.bd.c.b.a的返回的值被轉成了long使用!
(3)從frida打印函數調用的順序看,打印了ms.bd.c.u1.a后立即打印ms.bd.c.h.a,說明這兩個存在調用關系
既然確認ms.bd.c.b.a返回了long,這里馬上用自己的app調用ms.bd.c.b.a(67108866,"1128")試試,結果如下:函數的返回值居然是null!
這里出乎意料,我hook x音app時明明有返回的呀,並不是null!又出啥問題了? 不過這次有明顯的改善:自己寫的app至少沒崩,說明參數設置是沒任何問題的,個人猜測是metasec_ml內部可能有些檢測機制,發現是第三方調用就返回null!
為了查明原因,繼續用ida調試,詭異的現象發生了:F7單步調試的時候遇到跳轉的地址有問題,報錯!
不能就這樣算了啊,調式都不行了,怎么排查了?繼續用ida trace,這次在同樣的地方居然不報錯了(F8步過也不報錯,就特么F7步入報錯),trace的時候遇到了和用unidbg類似的問題,總式因為mutex卡在死循環這里鬼打牆:實在沒辦法了,把0x72C8E這4個字節NOP掉,不讓上鎖!
trace又能順利執行了!從trace的結果看:R0和R1分別從兩個地方取值,然后相減得到的結果保存到R0,也就是返回值,這里是0,怪不得67108866=0x4000002和1128這組參數返回的是null;
00003667 libmetasec_ml.so:F31D0514 LDR R0, [SP,#0xC] R0=0A423869 00003667 libmetasec_ml.so:F31D0516 LDR R1, [R5] R1=0A423869 00003667 libmetasec_ml.so:F31D0518 SUBS R0, R1, R0 R0=00000000 Z=1 N=0
由於不知道原app是怎么跑的(感興趣的小伙伴也可以用frida stalker trace原app),這里我也沒法繼續分析R0和R1倆個值為啥相等(因為沒有原app trace的對比,我也不知道自己代碼執行的路徑中哪個分支走錯了)!回到我們自己構造的app卡住的代碼,如下:
R4就是h.a傳入的第三個long函數,這里是個地址,加上0xC后取出值存放在R0,然后又存放在棧上,最后+4后得到rwlock傳入pthread_rwlock_rdlock,那么問題來了:原app中b.a(0x4000002,"1128")得到的是R4的地址,又經過一系列算式得到rwlock,為啥我們不自己構造一個rwlock,然后把地址傳給R4了?自己從寫寫個so,里面生成pthread_rwlock_t對象,然后調用pthread_rwlock_init初始化,把對象的地址保存;由於原app中涉及到R4+0xC和R0+4等“偏移”,所以我在c代碼里用了結構體;為了便於查找,給rwlock周圍的變量復制了0x11111111、0x22222222等數值,結果如下:
從ida調試的結果看,原來卡住的“LDR R0, [R4,#0xC]”的這樣代碼現在能正常執行了!R0確實指向了rwlock(從內存的值看,調用pthread_rwlock_init初始化后還是0,那初始化還有啥用了?)
繼續往下走,又遇到問題:R1明顯是我自己構造magic number,這里被當成地址來用了,說明我當初構造的rwlock結構體前面的數值應該是個地址!這里的偏移0xF357465A-0xF3569000=0xb65a;
往上追溯,這里的R1來自R4+4,這里也剛過上一個卡點;
既然找到原因了,繼續補唄!在自己的結構體把原來的0x22222222換成地址(注意:根據單步調試自己寫的app分析,這里一共有4層指針引用,每層都要自己寫指針嵌套引用);補上后繼續調試,特么又遇到同樣的問題(如下):還是地址為0,只不過換了個地方,說明構造的地址已經通過了上次代碼的執行;
往上追溯調試:偏移為0xB8C0的地方,此時R1就是我自己構造的p4指針,但是這里很雞賊地取了p4+8作為地址,而我並沒有構造p4+8,所以內存是0(p4我構造的是0x77777777);這里的p1到p4是我自定義的指針,層層遞進引用(建議下載末尾的源碼查看具體的定義和調用)!
繼續漫漫追溯,發現這里的push把寄存器的值壓棧上了,這里的偏移:0x6D970
發現R1和R2還是從棧上取得!這里得偏移:0x13A14
發現是之前R1和R2存棧上得!偏移:0xB8C0,又回到了p4+8的地方!
繼續用結構體去補:貌似補上了!
又發現一個地方:0xBE4C;還好自己填的值辨識度高,這里一眼就看出了是那個字段的賦值有問題!繼續補上!
繼續往下:發現我們自己填的0x22222222被當成跳轉的代碼地址了,呵呵...... 偏移:13A42
由於是跳轉到新地址,此時我也不知道應該補什么地址,只能繼續查看原app的跳轉地址。這里可以用frida hook,看看context;也可以直接用ida調試原app;我直接用ida,簡單方便,查到了這里跳轉的地址:偏移是0x14581
來到0x14581這里,發現還有大量的計算邏輯:原本是想把上面的BLX R6直接NOP掉,但是看到還有這么多代碼,擔心邏輯出錯,想想還是算了;只能想辦法得到metasec的基址,再加上0x14581的偏移來替代我之前設置的0x22222222,來保證原有的邏輯正常!
至此,so層所有的邏輯都補全了,h.a可以順利運行完畢,沒有任何,但結果卻是返回0!和上面調用ms.bd.c.b.a(67108866,“1128”)得到long的結果是一樣的!x音原來的app是可以正常運行的,hook的結果也是對的,但自己補全了參數得到的結果是0,直接傳參調用的結果還是0,說明:(1) h.a還有其他的反調試或檢測邏輯:一旦發現so被第三方調用了,直接返回0!(2)我自己補的參數可能有問題導致代碼運行的邏輯出錯!文章末尾是我自己構建的app,哪位能提示甚至幫忙把第三個參數構造好,站內私信我加微信發紅包!
直接來硬的不行那就繼續繞唄!有兩種思路:
- x音原app調用ms.bd.c.b.a(67108866,“1128”)生成了第三個long參數,為啥我不能調用了?
- x音原app調用ms.bd.c.b.a生成那4個加密字段,為啥我不能調用了?唯一不確定的就是第三個long參數,直接hook原app得到后再“為我所用”唄!
自己構造app是為了反調試和主動調用,用frida rpc一樣可以的嘛!為了直接用rpc執行ms.bd.c.b.a,這里徹底地做一次伸手黨:先hook ms.bd.c.b.a得到第三個long參數,保存后再通過rpc主動執行ms.bd.c.b.a生成加密字段!rpc代碼如下:
call.js文件:
function printStringArry(strArr){ var FastJson = Java.use('com.alibaba.fastjson.JSON'); return FastJson.toJSONString(strArr); } function convert2ArrayList(str){ var ArrayList = Java.use('java.util.ArrayList').$new(); var strObj = Java.use('java.lang.String').$new(); var strArray = new Array(); strArray = str.split(","); for(var i=0;i<strArray.length;i++){ ArrayList.add(strArray[i]); } return ArrayList; } var longParam; function getLongParam(){ Java.perform(function(){ var h_class = Java.use("ms.bd.c.h"); h_class.a.overload('int', 'int', 'long', 'java.lang.String', 'java.lang.Object').implementation = function(){ if(arguments[2]!=0){ longParam = arguments[2]; console.log('\n longParam = arguments[2]:' + longParam); } return this.a(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4]); } }); } function ms_bd_c_h_a(args0,args1,args2,args3,args4){ var encrypt_result; Java.perform(function() { args2 = longParam; args4 = convert2ArrayList(args4.toString()); try{ var targetClass = Java.use("ms.bd.c.h"); encrypt_result = targetClass.a(args0,args1,args2,args3,args4); }catch(e){ console.log(e.stack); } console.log("encrypt_result is :"+printStringArry(encrypt_result)); }); return encrypt_result; } setImmediate(getLongParam); rpc.exports = { encrypt: ms_bd_c_h_a };
python文件:注意,args4是post數據包的head,需要改成和url匹配的head,否則app會崩掉!
# -*- coding, utf-8 -*- import codecs import frida import time session = frida.get_usb_device().attach('抖音') #讀取JS腳本 with codecs.open('./call.js', 'r', 'utf-8') as f: source = f.read() script = session.create_script(source) script.load() rpc = script.exports time.sleep(5)#給hook預留時間,讓x音觸發ms.bd.c.h.a函數;期間也可以手動點贊、滑動屏幕等方式觸發ms.bd.c.h.a函數 args0 = 50331649 args1 = 0 args2 = 0 args3 = "https,//aweme.snssdk.com/aweme/v1/commit/item/digg/?aweme_id=7016928444202175783&type=0&channel_id=0&city=510100&activity=0&os_api=25&device_type=SM-G9750&ssmix=a&manifest_version_code=150501&dpi=280&app_name=aweme&version_name=15.5.0&ts=1633766716&cpu_support64=false&app_type=normal&ac=wifi&appTheme=light&host_abi=armeabi-v7a&channel=lephone_xh_sd_1128_0415&update_version_code=15509900&_rticket=1633766717162&device_platform=android&iid=2401771765105502&version_code=150500&cdid=5b43e48d-b149-4c8a-8863-b2d84e650be4&openudid=b83ec6a675adba6a&device_id=4380918606995591&resolution=1080*1920&device_brand=samsung&language=zh&os_version=7.1.2&aid=1128&minor_status=0&mcc_mnc=46000" args4 = ["accept-encoding","gzip","cookie","n_mh=B6WRe0yd-1qIuffF6ZWNO-CSGlW1Q-VhC0E79NrqYTg;uid_tt=b21518a30e097fbd558cdddf9bcfd3a5;uid_tt_ss=b21518a30e097fbd558cdddf9bcfd3a5;sid_tt=5a51412bfb955ee582edae8d48765690;sessionid=5a51412bfb955ee582edae8d48765690;sessionid_ss=5a51412bfb955ee582edae8d48765690;d_ticket=6b7d0d3678f21faa482196c460483d4eed15d;install_id=2542488099492919;ttreq=1$a01a1173c36665a50314924786f4704df6cdeb45;sid_guard=5a51412bfb955ee582edae8d48765690%7C1633073022%7C5184000%7CTue%2C+30-Nov-2021+07%3A23%3A42+GMT;odin_tt=0ea847f78343f7e969e78ba7b9239200535f7a60e9667c1a9bd3eff221ae71e00ec61567785d8a107011c961fae3961c835c25f4bfd09dec3446bd650115e45263f7c216cd7d33846418d3b45f505996","passport-sdk-version","18","sdk-version","2","x-ss-req-ticket","1633766717163","x-tt-dt","AAA3VJFIKPXTQ3DZW622UN5YMCIJ3AQ5BZMAI2TMBJQASG3PQIMUKVBQYQG2YCXGALRNNZBQUJ7XDNZ4FXIGI2TL5LI5XLUXSPCTZ4NJGDU4JCIL2UF6OEV25GKGINMX7ARAHHRP5IU6VITY2YJ4BCI","x-tt-token","005a51412bfb955ee582edae8d48765690009b144562dfd76dacbe19854da5bd41e9bc9c249278cf5cfd759602ab0bab22f34d19554661df3791190f9068394dd1e6f7edd268b7fa3c5a0b745f1aee15931aa0b0f43260e7297fb7d16d872614ca256-1.0.1"] result = rpc.encrypt(args0,args1,args2,args3,args4) print("five gods,",result) session.detach()
在寫rpc調用時遇到的幾個坑在這里分享一下,希望對大家有所幫助:
- 這里涉及到3中不同的語言:java、js、python,每種語言的類型是不一樣的;java層ms.bd.c.b.a的參數是('int', 'int', 'long', 'java.lang.String', 'java.lang.Object'),但是js是弱類型,並沒有long類型;python同理也沒有,該怎么構造long參數了?我這里是直接在js里面hook ms.bd.c.b.a后保存了第三個參數,然后傳入rpc調用;如果通過python傳進來,可以用int(data)轉換,在js統一識別成number類型;我想過用Java.cast、Java.use('java.lang.Long').parseLong.overload('java.lang.String').call(Java.use('java.lang.Long'), args2)等方式強轉成long,但都沒卵用!
- 最后一個參數類型是java.lang.Object,如果直接把key/value字符串轉成string傳進去,app會直接崩掉(如果x音是故意這么干的,說明反調試做的牛逼;如果不是,說明魯棒性沒考慮周全,沒有對錯誤的參數做處理);如果直接傳入["accept-encoding":"gzip","cookie":"n_mh=B6WRe0yd-".....] 這種list類型會爆下面的錯誤,說明python的list根本不匹配java.lang.Object
Error: a(): argument types do not match any of: .overload('int', 'int', 'long', 'java.lang.String', 'java.lang.Object')
public final Map<String, String> mo70417a(String str, Map<String, List<String>> map) { String str2; PatchProxyResult proxy = PatchProxy.proxy(new Object[]{str, map}, this, f357109a, false, 431597); if (proxy.isSupported) { return (Map) proxy.result; } HashMap hashMap = new HashMap(); if (!(str == null || map == null)) { if (str.toLowerCase().contains((String) C85985h.m218727a(16777217, 0, 0, "2412bc", new byte[]{43, 34, 86, 86})) || str.toLowerCase().contains((String) C85985h.m218727a(16777217, 0, 0, "fd1c18", new byte[]{Byte.MAX_VALUE, 114, 86, 7, 29}))) { C86055z1.m218845a().mo248383b(); ArrayList arrayList = new ArrayList(); for (Map.Entry<String, List<String>> entry : map.entrySet()) { String key = entry.getKey(); if (entry.getValue() == null || entry.getValue().size() <= 0) { str2 = null; } else { str2 = entry.getValue().get(0); } if (!(key == null || str2 == null)) { arrayList.add(key); arrayList.add(str2); } } String[] strArr = (String[]) C85964b.m218696a(50331649, 0, C86039u1.this.f357108d, str, (String[]) arrayList.toArray(new String[0])); if (strArr != null) { HashMap hashMap2 = new HashMap(); for (int i = 0; i < strArr.length; i += 2) { hashMap2.put(strArr[i], strArr[i + 1]); } return hashMap2; } } else { throw new RuntimeException((String) C85985h.m218727a(16777217, 0, 0, "3ff0e1", new byte[]{43, 112, 85, 73, 79, 53, 36, 7, 53, 101, 98, 108, 1, 80, 74, 105, 56, 83, 35, 112, 49})); } } return hashMap; }
同時,在metasec_ml偏移0x13220處發現了重要的函數:第一個參數是url
第二個參數是post包的head參數,so層0x13220的這個函數和java層的ms.bd.c.u1$a.a方法在參數上出奇一致,很有可能0x13220也參與了加密字段的生成,至少是提供了加密算法的原始數據!
自己構造app: 鏈接:https://pan.baidu.com/s/1JavGwlMKtcpnQkaVB2zmNw 提取碼:3fd6 調用ms.bd.c.b.a時返回值是0,暫時還沒生成加密字段,問題還在排查中;能幫忙解決問題的私信我加微信,我發紅包!
參考:
1、https://blog.csdn.net/shaoyiju/article/details/53241808 讀寫鎖的原理及用法
2、https://blog.csdn.net/qq_30135181/article/details/108221043 調用so中函數