關(guān)于Android中的簽名校驗(yàn)是一種很普遍的安全防護(hù)策略了,很多應(yīng)用也都做了這部分的工作,在之前我也介紹了幾篇關(guān)于如何爆破應(yīng)用的簽名校驗(yàn)問題的文章,不了解的同學(xué)可以去查看:Android中爆破應(yīng)用簽名校驗(yàn)功能,當(dāng)時(shí)介紹完這篇文章之后,其實(shí)總結(jié)了現(xiàn)在爆破簽名校驗(yàn)的幾種方式,其中方便快捷的就是:全局搜索字符串內(nèi)容:“signature”,因?yàn)橹灰泻灻r?yàn)功能,一定會(huì)調(diào)用系統(tǒng)的一個(gè)方法,而這個(gè)方法中就是包含了這個(gè)字符串內(nèi)容。
之前的這篇文章中介紹的簽名校驗(yàn)處理方式也是如此,找到具體簽名校驗(yàn)功能之后,直接替換正確的簽名信息就可,或者把if判斷手動(dòng)改成true也可以。但是當(dāng)時(shí)說到一個(gè)問題,就是現(xiàn)在應(yīng)用為了加強(qiáng)防護(hù),幾乎在重要的內(nèi)容部分中都加了簽名校驗(yàn)功能,所以如果要手動(dòng)的改,就需要把每個(gè)簽名校驗(yàn)的地方都得改一下,這樣會(huì)顯得很費(fèi)勁,有的人說了,有一個(gè)好的辦法,就是用Xposed來hook這個(gè)應(yīng)用的獲取系統(tǒng)簽名的方法,然后替換方法的返回值即可。這種方式是可以的,但是不是好的,因?yàn)槿绻憬鉀Q了簽名校驗(yàn),二次打包給別人用,不可能叫人家還root手機(jī),然后在裝Xposed框架功,是不合理的。所以我們需要從根本上解決這個(gè)問題,也是本文介紹的一個(gè)重點(diǎn)。后面會(huì)介紹一種萬能的高效方法。
之前的文章中我們可以看到,大部分簽名校驗(yàn)都是在Java層,本地做的校驗(yàn)。所以爆破難度不是很大,而今天我們要介紹的一個(gè)應(yīng)用樣本他的簽名校驗(yàn)是放在so中,而且結(jié)合了服務(wù)端進(jìn)行驗(yàn)證,難度加大。不過萬變不離其宗,簽名校驗(yàn)永遠(yuǎn)都是需要獲取應(yīng)用本身的簽名信息,進(jìn)行比對(duì)操作的。
下面就開始進(jìn)入本文的主題,看一下應(yīng)用樣本的簽名校驗(yàn)問題,拿到樣本之后,直接反編譯,二次打包,安裝出現(xiàn)如下錯(cuò)誤信息提示:
這個(gè)直接彈出對(duì)話框信息,點(diǎn)擊確定就退出程序了,這個(gè)比較簡(jiǎn)單,反編譯之后,通過提示內(nèi)容,找到簽名校驗(yàn)入口,在values/string.xml中找到這個(gè)字符串信息即可:
然后用Jadx打開這個(gè)應(yīng)用,全局搜索這個(gè)name值:
點(diǎn)擊進(jìn)入代碼位置即可:
看到i方法中有一個(gè)show方法,應(yīng)該就是對(duì)話框展示的邏輯。看看這個(gè)i方法調(diào)用的地方:
看到了,在之前有一個(gè)判斷,如果為false,就是走到i方法,展示對(duì)話框,所以這個(gè)checkHashKey方法肯定是簽名校驗(yàn)的方法,進(jìn)入查看:
是個(gè)native方法,全局搜SocketJNI類調(diào)用的地方:
在這里看到會(huì)加載一個(gè)so文件,也就是libmzd.so文件,我們用IDA打開這個(gè)文件(文件在反編譯之后的libs目錄下):
找到對(duì)應(yīng)的native函數(shù),查看具體邏輯代碼:
為了更好的閱讀代碼,使用F5,查看對(duì)應(yīng)的C語(yǔ)言代碼,這里的邏輯非常簡(jiǎn)單,就是把Java層傳遞的簽名字符串內(nèi)容,在做一次MD5值,然后和指定的字符串"8f2a24...."作比對(duì)即可。那么上面?zhèn)魅氲降腸heckHashKey方法的值是通過com.xiaoenai.app.utils.aj.m方法:
這里看到就是標(biāo)準(zhǔn)的獲取應(yīng)用簽名信息的方法。
到這里就看到了一個(gè)Java層的簽名校驗(yàn)方法,這里解決這個(gè)問題有很多種方法,可以修改對(duì)應(yīng)的smali方法,把if判斷強(qiáng)制改成true即可。還可以直接修改m方法的返回值為正確的簽名字符串內(nèi)容。這里選擇第二種,因?yàn)槲覀冃枰玫暮灻址畠?nèi)容,后面會(huì)繼續(xù)用到。這個(gè)獲取方法也很簡(jiǎn)單,直接寫一個(gè)Demo案例,通過應(yīng)用包名構(gòu)建出對(duì)應(yīng)的Context變量,然后就開始獲取簽名信息即可:
運(yùn)行這個(gè)demo程序,前提是你的設(shè)備中要安裝一個(gè)官方版本的應(yīng)用,這樣才能獲取到正確的簽名字符串內(nèi)容:
看到了,其中簽名字符串:"aN+VCd8ns0yqsotX2WuKyScq/ZA=" 就是正確的官方版本的值。下面在順便寫一個(gè)直接返回這個(gè)字符串的方法,然后變編譯得到對(duì)應(yīng)的smali代碼:
然后把這個(gè)方法的的代碼拷貝替換樣本應(yīng)用的aj.m方法代碼即可:
保存,二次打包即可,記住:這里沒必要手動(dòng)的編寫smali代碼返回指定的簽名字符串,除非你對(duì)smali語(yǔ)法非常熟悉了,不過我是不會(huì)這么做的。因?yàn)槲也皇煜mali,笨的有效的方法就是自己手動(dòng)寫一個(gè)Java方法,然后反編譯得到對(duì)應(yīng)的smali代碼即可。
二次打包成功之后,安裝應(yīng)用,運(yùn)行發(fā)現(xiàn)然還有錯(cuò)誤,登錄失敗:
這時(shí)候,就想到了,樣本應(yīng)用可能做了服務(wù)端數(shù)據(jù)驗(yàn)證,下面就來看看如何解決這個(gè)問題,這個(gè)問題的突破口比較簡(jiǎn)單,不要在用提示字符串信息去找了,因?yàn)檫@錯(cuò)誤信息可能是服務(wù)端返回的,這時(shí)候就需要借助抓包了,每次請(qǐng)求一次,就抓一次服務(wù)包,這里用了Fiddler工具:
這里看到,請(qǐng)求的參數(shù)非常簡(jiǎn)單,一個(gè)加密之后的數(shù)據(jù)data和版本號(hào)ver,返回的數(shù)據(jù)提示加密簽名錯(cuò)誤,所以我們可以在Jadx中全局搜"login2"字符串信息,找到突破口:
找到之后,雙擊進(jìn)入代碼:
看到終調(diào)用了,a方法,點(diǎn)擊進(jìn)入a方法:
這里會(huì)通過一個(gè)過程構(gòu)造出一個(gè)json參數(shù)格式,繼續(xù)往下看:
攜帶了這些信息參數(shù)值,為了更好的看到這個(gè)json數(shù)據(jù)格式,我們可以利用Xposed下一個(gè)hook功能:
然后運(yùn)行這個(gè)Xposed模塊,在點(diǎn)擊樣本的登錄功能,查看日志信息:
看到了,result就是終拼接好的json參數(shù)格式內(nèi)容。其中后一個(gè)字符串sig是將簽名整個(gè)參數(shù)做一次加密操作,為了在服務(wù)端校驗(yàn)參數(shù)的完整性。得到這個(gè)json格式之后,會(huì)調(diào)用com.xiaoenai.app.utils.b.a.a方法:
繼續(xù)查看這個(gè)方法實(shí)現(xiàn):
繼續(xù)跟蹤代碼:
又是到了native方法了,到這里其實(shí)上次Java將拼接好的參數(shù)信息以json格式傳遞到native層進(jìn)行加密操作,繼續(xù)看native層代碼實(shí)現(xiàn),依然在之前的那個(gè)libmzd.so中:
找到對(duì)應(yīng)的核心函數(shù)功能,跟蹤查看實(shí)現(xiàn):
繼續(xù)跟蹤:
這里看到了,有data字符串內(nèi)容了,也就是這里開始對(duì)上面?zhèn)鬟f的json數(shù)據(jù)進(jìn)行加密,然后拼接到data中,在native層在拼接一套json:{"data":"加密之后的值", "ver":"1.1"},而在這里有一個(gè)尋找加密key的函數(shù),可惜這里我不在分析了,因?yàn)槲铱吹念^疼,他的加密算法還是比較復(fù)雜的。所以就放棄分析了。
那么到這里,我們有什么方式可以得到加密算法呢?有的同學(xué)可能想到了動(dòng)態(tài)調(diào)試,這個(gè)方法是好的,但是這個(gè)app做了很多反調(diào)試策略,動(dòng)態(tài)調(diào)試也是不好弄的,而且這個(gè)算法有點(diǎn)復(fù)雜,及時(shí)動(dòng)態(tài)調(diào)試,也要詳細(xì)閱讀arm指令,才能猜到這個(gè)加密算法功能。所以這條路不好走。
那么還有其他方法了?當(dāng)然有,就是文章開頭說到的一句解決簽名校驗(yàn)的基本法則:全局搜索字符串"signature";在IDA中也是如此,使用Shirt+F12打開so中的字符串窗口,然后搜索signature:
果然找到了,雙擊進(jìn)入:
看到這里可能是在native層調(diào)用了系統(tǒng)獲取簽名的方法,選中紅色框中的內(nèi)容,然后按X鍵,展示被調(diào)用的地方索引:
雙擊進(jìn)入,這個(gè)就是我們之前分析的那個(gè)SocketJNI的init方法:
到這里,為了更好的閱讀代碼,需要把這個(gè)函數(shù)地址改成可讀,也就是JNIEnv*即可,選中紅色框,按下Y鍵即可:
修改成JNIEnv*,確定即可:
這樣就看的比較清楚了,而這部分代碼也非常簡(jiǎn)單,就是調(diào)用系統(tǒng)獲取簽名的方法,然后賦值到一個(gè)變量中,不過這里獲取的不是字符串內(nèi)容,而是int類型的hashCode值:
在返回到arm代碼處,看到賦值,就是將R0寄存器值搞到R9中去。
到這里,我們一定要思路清楚,就是不管native層怎么加密,終會(huì)利用應(yīng)用的簽名值來做加密的key信息。所以我們只要解決掉獲取簽名信息值即可,也就是這里的R9寄存器值,我們還需要再一次去獲取正版的簽名信息的hashCode值即可:
然后運(yùn)行,查看值:-2081383250
然后我們可以修改上面的arm指令:MOV R9,R0;不過這里還不好弄,因?yàn)檫@個(gè)hashCode值int類型四個(gè)字節(jié),而這里看到一個(gè)命令才2個(gè)字節(jié),肯定不夠用。需要借助LDR偽指令來進(jìn)行操作了。但是這里不做這么費(fèi)勁的修改了。因?yàn)榧偃缙渌胤揭灿羞@樣的功代碼,那么還得去修改,就是回到了我們文章開始說到的那個(gè)問題,要解決所有的簽名校驗(yàn)功能,才是本文的目的。
不過到這里,我們可以先這么嘗試檢驗(yàn)我們的爆破結(jié)果,就是借助Xposed框架,hook這個(gè)應(yīng)用獲取簽名的hashCode方法,將返回值替換成正確的-2081383250值:
修改之后,運(yùn)行:
果然成功了,而且對(duì)于之前的去除對(duì)話框那個(gè)邏輯也可以不用那么麻煩了,這么一來整個(gè)app所有的簽名校驗(yàn)地方都是爆破了。不過這種方式利用的是Xposed,所以不是終方案。下面就來介紹一種高新技術(shù)來解決這個(gè)問題。
不知道大家是否還記得我之前介紹過一個(gè)系統(tǒng)篇系列文章,介紹如何Hook系統(tǒng)的各個(gè)服務(wù),比如AMS,PMS等功能,然后在應(yīng)用內(nèi)進(jìn)行攔截activity啟動(dòng)的功能。不了解的同學(xué)可以看這篇文章:Android中Hook系統(tǒng)服務(wù)功能;原理非常簡(jiǎn)單就是利用反射機(jī)制+動(dòng)態(tài)代理技術(shù),替換系統(tǒng)服務(wù)的Binder對(duì)象即可。這個(gè)技術(shù)好處是免root操作,缺點(diǎn)是只能在應(yīng)用內(nèi)部進(jìn)行操作使用。那有的人好奇了,既然只能在應(yīng)用本身內(nèi)部有效,那有什么用呀?其實(shí)不然,現(xiàn)在有些開發(fā)場(chǎng)景就需要這個(gè)技術(shù),及時(shí)現(xiàn)在沒用到,將來也不一定,這不在這里就用到了。下面我們就用這個(gè)技術(shù)來Hook系統(tǒng)的PMS服務(wù),攔截應(yīng)用獲取簽名信息的方法。因?yàn)橹灰诒緫?yīng)用中操作,所以正好符合要求。
操作步驟很簡(jiǎn)單,我們?cè)跇颖緫?yīng)用啟動(dòng)的入口處,加上我們的攔截代碼即可,那么下面我們先把攔截PMS服務(wù)代碼寫好,這個(gè)代碼下載地址文章末尾給出。
就是利用反射去hook系統(tǒng)的類,然后用動(dòng)態(tài)代理構(gòu)造一個(gè)自己的PMS Binder進(jìn)行替換即可:
在這里我們可以通過方法名來攔截簽名信息,我們用正版的簽名信息構(gòu)造一個(gè)新的Signature對(duì)象,然后將其設(shè)置已有的對(duì)象中即可。因?yàn)槲覀兘K還是需要把代碼放到樣本案例的入口處,所以就在這里將這兩個(gè)類放到樣本入口類的包下面,具體包名可以手動(dòng)構(gòu)造:
然后將這個(gè)demo反編譯,得到對(duì)應(yīng)的smali代碼,拷貝到樣本案例對(duì)應(yīng)的包下面,然后在樣本案例的入口處加上方法調(diào)用:ServiceManagerWraper.hookPMS(this); 對(duì)應(yīng)的smali代碼如下:
invoke-static {p0}, Lcom/xiaoenai/app/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V
而樣本入口可以從AndroidManifest.xml中的application找到:
在Application的onCreate方法入口添加即可,添加完成之后,二次打包,再次使用Jadx打開修改之后的apk包:
入口處代碼已經(jīng)添加完畢了。然后安裝運(yùn)行,就可以完成。而這么操作之后應(yīng)用所有的簽名信息就是正確的。包括native層的簽名校驗(yàn)邏輯。所以這種方式是靠譜的。不要改多處簽名校驗(yàn)的邏輯了。
到這里我們就成功的爆破了一款服務(wù)端+Native雙簽名校驗(yàn)的樣本案例了,而這次操作簽名爆破之后,后面將不會(huì)在介紹更多的簽名校驗(yàn)了,原因很簡(jiǎn)單,因?yàn)檫@次操作之后,我們發(fā)明了一個(gè)通用的爆破方式,可以解決簽名校驗(yàn)問題了,下面就來總結(jié)一下現(xiàn)階段Android中簽名校驗(yàn)邏輯處理方式:
、基本法則不能忘:全局搜索字符串"signature",Java層可以用Jadx進(jìn)行搜索,so中可以用IDA進(jìn)行搜索。
第二、在逆向領(lǐng)域中,字符串信息永遠(yuǎn)是選擇的爆破的突破口,在之前的文章我已經(jīng)多次講到了,不管是IDA,還是Jadx工具,只要全局搜索關(guān)鍵字符串信息,就可以找到我們想要的入口。
第三、本文的重點(diǎn):發(fā)明了一種全新的高效的爆破應(yīng)用簽名校驗(yàn)邏輯,就是可以手動(dòng)Hook樣本應(yīng)用的PMS功能,然后在應(yīng)用的入口處加上hook代碼。終在hook的invoke方法中攔截想要的方法即可。
有了本文的思路,后面會(huì)開發(fā)一個(gè)工具,一鍵式解決簽名問題,原理就是利用我之前介紹的 icodetools工具 和本文介紹的hook系統(tǒng)PMS服務(wù),篡改應(yīng)用簽名信息。關(guān)于具體細(xì)節(jié)和工具開發(fā)敬請(qǐng)期待。如果此工具開發(fā)完成,那么對(duì)于簽名校驗(yàn)的應(yīng)用絕對(duì)是一個(gè)新的挑戰(zhàn)。安全不息,逆向不止!
嚴(yán)重聲明:本文的目的只有一個(gè),通過一個(gè)案例來分析現(xiàn)在應(yīng)用逆向分析技巧,如果有人利用本文內(nèi)容進(jìn)行任何商業(yè)目的和非法牟利,帶來的任何法律責(zé)任將由操作者本人承擔(dān),和本文作者沒有任何關(guān)系,所以還是由衷的希望大家秉著技術(shù)學(xué)習(xí)的目的閱讀此文,非常感謝!
Hook代碼下載地址:https://github.com/fourbrother/HookPmsSignature
本文介紹的內(nèi)容稍微有點(diǎn)多,所以大家看的可能有點(diǎn)累,其實(shí)還有一部分內(nèi)容沒介紹,就是如何訪問已有的so文件中的函數(shù),變量值,這個(gè)是我在這個(gè)樣本案例中用到的一個(gè)方法,限于篇幅原因就不多介紹了。但是一定要記住本文的后一種爆破簽名校驗(yàn)方式的方法。此等絕對(duì)高級(jí)正能量。希望可以言傳。后看完文章,如果覺得有收獲,就多多點(diǎn)贊擴(kuò)散分享,如果有打賞那就好啦啦!!
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。