我們?cè)诰幋a美麗微信公眾號(hào)已經(jīng)弄過(guò)了很多app了定罢,不管是協(xié)議還是外掛,我們都是那么一路走過(guò)來(lái)了旁瘫,在操作的過(guò)程中也發(fā)現(xiàn)了很多問(wèn)題就是應(yīng)用不在乎安全問(wèn)題帶來(lái)的后果祖凫,因?yàn)榘踩冀K都是不可忽視的問(wèn)題,辛辛苦苦寫(xiě)的代碼被人看的體無(wú)完膚對(duì)不起自己也對(duì)不起公司酬凳,所以如果你做了這幾件事至少可以防止一些人把你的app給強(qiáng)奸了惠况。本文就來(lái)總結(jié)一下不用加固方式也可以讓你的應(yīng)用變得更加安全可靠。
一宁仔、混淆永遠(yuǎn)都不可或缺
這里說(shuō)的混淆不是說(shuō)的傳統(tǒng)大家都知道的簡(jiǎn)單混淆策略稠屠,而是高級(jí)一點(diǎn)的混淆策略,首先是代碼混淆翎苫,大家可以參考小黃車(chē)app的代碼:
看到了吧人家把代碼混淆成中國(guó)人可以看懂的信息权埠,可惜這樣的信息對(duì)于我們破解來(lái)說(shuō)就很麻煩了,關(guān)于怎么做到的煎谍,之前的文章已經(jīng)介紹了攘蔽,大家可以查閱這里:Android中把代碼混淆成中文,當(dāng)然可以簡(jiǎn)單一點(diǎn)就是用 -classobfuscationdictionary實(shí)現(xiàn)也可以呐粘。代碼混淆成這樣不算什么满俗,還有更厲害的就是把資源混淆成一坨屎的應(yīng)用:
看到了嗎資源文件反編譯之后全是這鳥(niǎo)樣,感覺(jué)世界都快崩潰了作岖,這個(gè)方案暫時(shí)沒(méi)研究不過(guò)大家可以進(jìn)入編碼美麗小密圈咨詢(xún)bin神找MT管理器作者咨詢(xún)方案唆垃。當(dāng)把代碼混淆成一坨屎,把資源混淆成一坨翔的時(shí)候痘儡,讓破解者頂著一坨翔破解吧辕万。
二、防止應(yīng)用被抓包
在混淆之后我們還是覺(jué)得不夠安全,因?yàn)橛械娜撕苜v渐尿,總是抓包偷窺我們的數(shù)據(jù)醉途,不過(guò)我一般不會(huì)這么賤因?yàn)槲乙话阏蠊饷鞯淖グ磾?shù)據(jù),應(yīng)用的網(wǎng)絡(luò)訪(fǎng)問(wèn)接口和返回?cái)?shù)據(jù)有的時(shí)候很重要涡戳,所以為了防止應(yīng)用不被干结蟋,能用https的一定要用,雖然用了也不管用渔彰,因?yàn)槲覀冇蠿posed嵌屎。用了https之后也要重寫(xiě)SSLSocketFactory類(lèi),不懂這個(gè)技術(shù)可以看看這篇文章:Android中破解防止被抓包應(yīng)用方案 有了這個(gè)還是不行恍涂,因?yàn)槲覀儠?huì)反編譯干掉這個(gè)類(lèi)宝惰。還需要加上系統(tǒng)的api來(lái)判斷當(dāng)前設(shè)備有沒(méi)有掛代理不管是Fiddler還是VPN只要有就認(rèn)為是不安全的,這兩個(gè)api方法很簡(jiǎn)單:
System.getProperty(“http.proxyHost”);
System.getProperty(“http.proxyPort”);
正常情況下這兩個(gè)返回值是null再沧,所以你們懂得怎么做了尼夺,如果發(fā)現(xiàn)不為空那么就表示當(dāng)前設(shè)備被掛代理了,那么就有可能被抓包的風(fēng)險(xiǎn)了炒瘸,這里額外說(shuō)一下破解者在除了用Fiddler抓包淤堵,還可以直接在手機(jī)上用Packet Capture和Debug Proxy這兩個(gè)工具進(jìn)行抓包:
不過(guò)可惜這兩個(gè)工具都用了系統(tǒng)的VPNService功能做的,所以這時(shí)候上面接口返回的值就不為空了顷扩,那么這兩個(gè)工具就等于廢了拐邪。那么加了這些就一定管用嗎?肯定不管用隘截,因?yàn)橐枪苡媚俏蚁旅孢€說(shuō)什么呢扎阶?因?yàn)檫@些都是Java層代碼,所以安全點(diǎn)就把這些判斷放到native層做婶芭。只要找到了這個(gè)地方直接nop掉就好了东臀。所以我們還得想辦法防止,抓包的時(shí)候我們要是隱藏我們的接口域名就好了犀农,因?yàn)槠平庹咄ㄟ^(guò)抓包找到域名惰赋,然后再去Jadx中搜索就定位到了參數(shù)加密地方了,那要是我們把域名做成動(dòng)態(tài)下發(fā)的井赌,不用域名訪(fǎng)問(wèn)而是ip地址訪(fǎng)問(wèn)谤逼,這些ip地址通過(guò)配置獲取,這樣被抓到了也搜不到增加破解難度仇穗。不過(guò)這些還是沒(méi)啥用,因?yàn)槟銊?dòng)態(tài)下發(fā)只要找到這個(gè)地方就會(huì)找到ip地址了戚绕,到時(shí)候還是一樣被干了纹坐。所以我們還可以這么做,網(wǎng)絡(luò)請(qǐng)求用底層的socket進(jìn)行訪(fǎng)問(wèn)操作舞丛,這樣就不會(huì)被抓包了耘子,可惜的是這世界上還有一個(gè)叫做tcpdump+WireShark的工具果漾,也是于事無(wú)補(bǔ)。所以我已經(jīng)瘋了谷誓,因?yàn)橄氩坏绞裁捶桨噶巳拚稀W詈笳?qǐng)求數(shù)據(jù)中一定要記得帶上簽名加密信息,不要一味的放在請(qǐng)求參數(shù)中捍歪,我偷偷的告訴你很多破解者容易忽視請(qǐng)求頭信息户辱。
三、防止應(yīng)用被Hook操作
有時(shí)候最煩人的就是自己的應(yīng)用被別人hook了糙臼,感覺(jué)自己的腚被人捅了一樣庐镐,所以為了保護(hù)好自己的腚,我們需要做一些事防止被hook变逃,這個(gè)就要學(xué)習(xí)人家支付寶了必逆,同樣是腰間盤(pán)為什么別人總是這么突出:
雖然支付寶的防護(hù)被一個(gè)帥氣英俊的美男子程序猿破了,但是還是要說(shuō)他的方案也是備選之一揽乱,如何防止應(yīng)用被hook酌肌:
第一、Xposed框架將Hook信息存儲(chǔ)在字段fieldCache凰棉,methodCache损拢,constructorCache 中, 利用java 反射機(jī)制獲取這些信息渊啰,檢測(cè)Hook信息中是否含有支付寶App中敏感的方法探橱,字段,構(gòu)造方法
第二绘证、檢測(cè)進(jìn)程中使用so名中包含關(guān)鍵”hack|inject|hook|call” 的信息隧膏,這個(gè)主要是防止有的人不用這個(gè)Xposed框架,而是直接自己寫(xiě)一個(gè)注入功能
第三嚷那、root檢測(cè)原理是:是否含有su程序和ro.secure是否為1
第四胞枕、防止被Hook的方式就是可以查看XposedBridge這個(gè)類(lèi),有一個(gè)全局的hook開(kāi)關(guān)魏宽,所有有的應(yīng)用在啟動(dòng)的時(shí)候就用反射把這個(gè)值設(shè)置成true腐泻,這樣X(jué)posed的hook功能就是失效了:
第五、如果應(yīng)用被Xposed進(jìn)行hook操作之后队询,拋出的異常堆棧信息中就會(huì)包含Xposed字樣派桩,所以可以通過(guò)應(yīng)用自身內(nèi)部拋出異常來(lái)檢測(cè)是否包含Xposed字段來(lái)進(jìn)行防護(hù):
可惜雖然他很突出但是忘了一點(diǎn)就是把操作放到native層會(huì)好點(diǎn),畢竟我不怎么熟悉arm指令的蚌斩,但是我會(huì)Frida铆惑。
四、防止應(yīng)用被調(diào)試
在你都加了上面的幾個(gè)操作之后,腚的確沒(méi)有被人捅了员魏,但是感覺(jué)心中被人植入了蒼老師丑蛤,讓你感覺(jué)渾身發(fā)熱。所以做了以上這么多之后還有一點(diǎn)很重要就是不要讓你的應(yīng)用被人家扒光了一樣看你的身體撕阎。因?yàn)楝F(xiàn)在很多破解者在靜態(tài)方式不成之后就采用了動(dòng)態(tài)調(diào)試辦法進(jìn)行操作受裹。所以我們?yōu)榱俗寫(xiě)?yīng)用更安全就做好防止反調(diào)試操作
第一種:先占坑,自己附加
代碼非常簡(jiǎn)單虏束,在so中加上這行代碼即可:ptrace(PTRACE_TRACEME, 0, 0, 0)棉饶;其中PTRACE_TRACEME代表:本進(jìn)程被其父進(jìn)程所跟蹤。其父進(jìn)程應(yīng)該希望跟蹤子進(jìn)程魄眉,一般一個(gè)進(jìn)程只能被附加一次砰盐,我們?cè)谄平庹{(diào)試的時(shí)候都會(huì)附加需要調(diào)試應(yīng)用的進(jìn)程,如果我們先占坑坑律,父進(jìn)程附加自己岩梳,那么后面在附加調(diào)試就會(huì)失敗。加上這段代碼我們運(yùn)行之后看一下效果:
我們?cè)谶M(jìn)行破解動(dòng)態(tài)調(diào)試的時(shí)候都知道附加進(jìn)程的status文件中的TracerPid字段就是被調(diào)試的進(jìn)程pid晃择,這里我們運(yùn)行程序之后冀值,查看進(jìn)程對(duì)應(yīng)的status文件,發(fā)現(xiàn)TracerPid值就是進(jìn)程的父進(jìn)程pid值宫屠。那么后面如果有進(jìn)程在想附加調(diào)試就是會(huì)失敗的列疗。這種方式啟動(dòng)一定的調(diào)試作用,但是不是絕對(duì)安全的浪蹂。關(guān)于解決這種反調(diào)試方案后面再說(shuō)抵栈。
第二種:調(diào)試狀態(tài)檢查
這種方式是純屬借助Android中的api進(jìn)行檢驗(yàn),有兩種方法:
第一:檢查應(yīng)用是否屬于debug模式
直接調(diào)用Android中的flag屬性:ApplicationInfo.FLAG_DEBUGGABLE坤次,判斷是否屬于debug模式:
這個(gè)其實(shí)就是為了防止現(xiàn)在破解者為了調(diào)試應(yīng)用將應(yīng)用反編譯在A(yíng)ndroidManifest.xml中添加:android:debuggable屬性值古劲,將其設(shè)置true。然后就可以進(jìn)行調(diào)試缰猴。
添加這個(gè)屬性之后产艾,我們可以用 dumpsys package [packagename] 命令查看debug狀態(tài):
所以我們可以檢查應(yīng)用的AppliationInfo的flag字段是否為debuggable即可。不過(guò)這種方式也不是萬(wàn)能的滑绒,后面會(huì)介紹如何解決這種反調(diào)試問(wèn)題闷堡。
第二:檢查應(yīng)用是否處于調(diào)試狀態(tài)
這個(gè)也是借助系統(tǒng)的一個(gè)api來(lái)進(jìn)行判斷:android.os.Debug.isDebuggerConnected();這個(gè)就是判斷當(dāng)前應(yīng)用有沒(méi)有被調(diào)試疑故,我們加上這段代碼之后杠览,之前IDA動(dòng)態(tài)調(diào)試的時(shí)候其中有一個(gè)步驟進(jìn)行jdb連接操作:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700,當(dāng)連接成功之后纵势,這個(gè)方法就會(huì)返回true倦零,那么我們可以利用這個(gè)api來(lái)進(jìn)行判斷當(dāng)前應(yīng)用是否處于調(diào)試狀態(tài)來(lái)進(jìn)行反調(diào)試操作误续。但是這種方式不是萬(wàn)能的吨悍,后面會(huì)介紹如何解決這種反調(diào)試問(wèn)題扫茅。
第三種:循環(huán)檢查端口
我們?cè)谥捌平饽嫦虻臅r(shí)候,需要借助一個(gè)強(qiáng)大利器育瓜,那就是IDA葫隙,在使用IDA的時(shí)候,我們知道需要在設(shè)備中啟動(dòng)android_server作為通信躏仇,那么這個(gè)啟動(dòng)就會(huì)默認(rèn)占用端口23946:
我們可以查看設(shè)備的tcp端口使用情況 cat /proc/net/tcp:
其中5D8A轉(zhuǎn)化成十進(jìn)制就是23946恋脚,而看到uid是0,因?yàn)槲覀冞\(yùn)行android_server是root身份的焰手,uid肯定是0了糟描。所以我們可以利用端口檢查方式來(lái)進(jìn)行反調(diào)試策略,當(dāng)然這種方式不是萬(wàn)能的书妻,后面會(huì)詳細(xì)介紹如何解決這樣的反調(diào)試方法船响。同時(shí)還要檢查有沒(méi)有android_server這個(gè)進(jìn)程,有的話(huà)也是表示有可能被調(diào)試了躲履。
當(dāng)然還有最近很常用的Frida框架见间,他的端口號(hào)是27042和27043,以及進(jìn)程名是frida-server工猜。另可錯(cuò)殺一千不能放過(guò)一個(gè)米诉。
第四種:循環(huán)檢查T(mén)racerPid值
在第一種方式中,我們簡(jiǎn)單的介紹了如果應(yīng)用被調(diào)試了篷帅,那么他的TracerPid值就是調(diào)試進(jìn)程的pid值史侣,而在使用IDA進(jìn)行調(diào)試的時(shí)候,需要在設(shè)備端啟動(dòng)android_server進(jìn)行通信魏身,那么被調(diào)試的進(jìn)程就會(huì)被附加惊橱,這就是android_server進(jìn)程的pid值了:
查看一下android_server的pid值:
所以我們可以在自己的應(yīng)用中的native層加上一個(gè)循環(huán)檢查自己status中的TracerPid字段值,如果非0或者是非自己進(jìn)程pid(如果采用了第一種方案的話(huà)叠骑,這里也是需要做一次過(guò)濾的)李皇;那么就認(rèn)為被附加調(diào)試了。當(dāng)然這里還有一種方案宙枷,就是可以檢查進(jìn)程列表中有沒(méi)有android_server進(jìn)程掉房,不過(guò)這種方式都不是萬(wàn)能的,后面會(huì)詳細(xì)介紹如何解決這種反調(diào)試方案慰丛。
五卓囚、簽名校驗(yàn)防護(hù)策略
在做了上面的一些工作之后發(fā)現(xiàn)腚也不疼了,心中也坦然了诅病,可是誰(shuí)知道有些人太壞了偷偷的把應(yīng)用的衣服給換了哪亿,本來(lái)是一個(gè)待入閨中的大家閨秀粥烁,結(jié)果被換了一套比基尼搖身一變成了外圍女?dāng)R誰(shuí)誰(shuí)受得了,所以為了不讓自己的應(yīng)用衣服被換我們得買(mǎi)點(diǎn)膠水把衣服粘住蝇棉,不要被人惡意拆解我們的應(yīng)用然后二次打包謀取暴利讨阻,簽名校驗(yàn)一般方案有兩種:
第一:直接在本地做防護(hù),如果發(fā)現(xiàn)簽名不一致直接退出應(yīng)用
第二:將簽名信息攜帶請(qǐng)求參數(shù)中參與加密篡殷,服務(wù)端進(jìn)行簽名校驗(yàn)钝吮,失敗就返回錯(cuò)誤數(shù)據(jù)即可
而這兩種方式也都不是最安全的防護(hù),因?yàn)橹灰泻灻r?yàn)的邏輯板辽,在本地都可以進(jìn)行過(guò)濾掉奇瘦。特別用了我的kstools工具之后完全就處于游離狀態(tài)了。不過(guò)簽名校驗(yàn)不要按照傳統(tǒng)方式去做了因?yàn)槟莻€(gè)已經(jīng)眾人皆知不安全了劲弦,我們需要校驗(yàn)各個(gè)文件的信息耳标,比如微信的dex文件校驗(yàn),阿里聚安全的簽名文件校驗(yàn)等高強(qiáng)度操作邑跪。對(duì)于文件校驗(yàn)也是最實(shí)際一點(diǎn)的次坡,不要在用系統(tǒng)方法獲取簽名信息校驗(yàn)了,那個(gè)已經(jīng)過(guò)時(shí)了呀袱。
六贸毕、加固方案
說(shuō)了這么多了感覺(jué)還是不安全,的確肯定不安全夜赵,因?yàn)樯厦娴墓ぷ髂阋沁€是放在java層去做那么等于沒(méi)說(shuō)明棍,永遠(yuǎn)記住NDK不僅僅是為了性能效率考慮的,也是為了安全考慮使用寇僧,很多人看得懂Java代碼但是看不懂a(chǎn)rm指令摊腋,如果在Native層做了這么多防護(hù)肯定效果會(huì)更好,當(dāng)然如果你的應(yīng)用為了更安全那就要考慮加固了嘁傀,加固是最終方案了至于什么加固廠(chǎng)商那就要自己選擇了兴蒸,但是現(xiàn)在很多大公司不會(huì)考慮加固原因有兩個(gè):
第一、現(xiàn)在沒(méi)有一個(gè)加固廠(chǎng)商跑出來(lái)說(shuō)我們家的加固方案最牛逼细办,保證0崩潰率橙凳,只要有崩潰率那么對(duì)于千萬(wàn)級(jí)的應(yīng)用也是很難忍受的,因?yàn)槊刻於加袔浊踔翈兹f(wàn)用戶(hù)手機(jī)的應(yīng)用在崩潰會(huì)瘋的笑撞。
第二岛啸、現(xiàn)在加固廠(chǎng)商也就那么幾家,用過(guò)加固的人或者沒(méi)用過(guò)用腚想想也知道茴肥,肯定需要上傳簽名文件的坚踩,因?yàn)樗庸套詈筮€得重新簽名,最主要的是你把沒(méi)加固前的應(yīng)用給他瓤狐,你能保證他和我一樣很正直不會(huì)偷窺你的應(yīng)用瞬铸?不會(huì)在你的應(yīng)用中加點(diǎn)什么他們想要的批幌?所以現(xiàn)在很多公司為了更安全點(diǎn)算了還是不去找加固廠(chǎng)商了。本來(lái)還安全點(diǎn)結(jié)果弄得不安全了嗓节。
現(xiàn)在也有很多公司已經(jīng)在投入人力物力財(cái)力自己做加固荧缘,就是自己公司開(kāi)發(fā)一套加固方案然后用于自己公司的所有產(chǎn)品,這樣肯定是最安全的了赦政,但是這個(gè)前提是人力物力財(cái)力的投入胜宇,如果小公司那肯定是不會(huì)考慮的。所以加固本身來(lái)說(shuō)技術(shù)肯定是最高端的也是最終的方案的恢着。但是也不是說(shuō)百分百安全,因?yàn)檫@個(gè)世界上還有一個(gè)完美的動(dòng)作叫做脫殼财破。