1、網絡請求——裸奔的數據
無論是網頁還是APP,都不可避免與后臺服務進行訪問,可能從服務器獲取數據或者提交數據到服務器,這時就需要客戶端發起網絡請求。由于http協議簡單、靈活和無狀態等特點,目前大部分應用都是采用的http協議進行的網絡請求。一個常見的網絡請求如下圖:
1.1 無處不在的安全隱患
因為http協議是明文傳輸的,所以上面的用戶名和密碼也是明文在網絡中傳輸的,在傳輸過程中的任何一個環節,攻擊者都可以直接看到明文數據。當然現在很多系統都不傳輸和保存明文賬號密碼,通常會采取MD5值傳輸和存儲,之前比較出名的CSDN數據庫泄漏,就是因為密碼是明文保存,黑客可以方便的拿去撞庫,加重了事件的嚴重性。近幾年MD5破解能力提高,例如黑客看到庫中一個密碼數量比較多,就可以嘗試去爆破,加上也可以直接用密碼的MD5值去撞庫,所以現在通常生成MD5值時都需要加鹽,例如MD5(name+pwd)作為密碼存儲,同樣的密碼生成的值是不一樣的,在一定程度上提高了安全性。
攻擊不一定需要用戶名密碼,或許可以直接打token和uid的主意。通過上圖可以看到,通過上圖可以看到,一旦用戶登錄后,服務器就通過token來標識身份。如果這個token被黑客劫持了,他就可以冒充你的身份進行攻擊,如果token機制設計不合理,攻擊者甚至可以直接暴力去撞token。大部分網站的機制也類似,服務器通過一個sessionId標識用戶,攻擊者一旦拿到這個sessionId,就可以去冒充一個合法用戶。
當然,雖說這些數據明文傳輸,但是如果你不是網管、網絡運營商,想拿到這些數據也不是那么容易,要不然也不會有那么人在公共場合開放WIFI去釣魚了。即使抓不到別人的數據,也并不意味著什么也做不了,想想看我們能做什么?抓不到別人的數據,可以抓自己的數據,分析服務器接口,尋找設計漏洞做突破口。下面是一些應用場景:
- 微信投票刷票。微信中充斥著各種投票活動,什么萌娃大賽、最美警察、舞蹈之星。基本都是一個鏈接甩過來,打開先提示獲取你的公開信息(昵稱、頭像等),確認后通常是一個90年代風格粗糙的網頁,找到對應編號的參賽者后,勾選提交。做這些操作前,設置好你的代理服務器,看看投票時發送的請求參數,多半情況下你會發現一些問題,用postman驗證你的想法,然后差不多花半個小時,就能寫一個刷票工具。這時你可以思考下,怎么設計才能讓投票程序更健壯。
- 微博xss事件。微博API本身是開放的,通過xss漏洞,注入調用關注、轉發、私信API的js腳本。
- 微信、點評、微博等機器人。在很多行業,大眾點評網站中商戶的好評差評,對商戶的生意和口碑影響很大,于是出現了很多水軍刷好評和惡意詆毀對手的事件,人肉養號和刷評論很顯然成本比較高,加上活躍度越高的用戶級別越高,級別越高的用戶評論時對商戶打分的權重也越高,所以當時有很多養機器人小號做不法勾當,直到后面公司投入很大資源成立專門的誠信團隊這種現象才好轉。微信公眾號也有同樣問題,一個名人公眾號發篇文章才幾百的閱讀量,這樣多沒面子啊,花點錢找水軍刷刷量吧,印象中微信針對這個事也從政策和技術上整頓過好幾次了。微博就不多說了,我甚至懷疑官方的態度,水清則無魚嘛。甚至連蘋果APP Store都不能幸免,不過只能人肉和眾包的形式。
1.2 使用https是否就萬事大吉
https分單向認證和雙向認證,大部分的應用場景是c/s模式,這時通常都是采用的單向認證的方式,也就是說可以保證客戶端拿到的數據是后臺發送的。這時想攻擊確實很難了,除非你能忽悠別人安裝并信任你的證書,實際上也并不是做不到,很多人蹭wifi時或者在網上下載一些資源,系統提示要信任什么東西,看都不看就點確定。這些暫時不提,上面提到的那些攻擊,都依賴于對后臺接口的分析和調用,對于這類攻擊者來說,你使用http和https是沒有區別的。
使用抓包工具時,給目標設備安裝并信任裝包工具的自簽名證書,這時候就可以分析https請求了。下面是正常抓https請求的包和配置過證書后的抓包
1.3 使用簽名和加密數據
上面可以看到,https并不能阻擋攻擊者分析請求接口并發起攻擊,為了增加攻擊者分析請求的難度,通常可以采用兩種方式:
- 使用簽名。即給你的請求參數添加一個簽名,后臺服務接收到請求時,先驗證簽名,簽名不正確的話,則不予處理。簽名規則五花八門,大致策略就是根據請求參數做一些運算最后生成一個唯一的字符串當做sign,微信支付的簽名大家可以做一個參考:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
- 數據加密。post到服務器和從服務器返回的數據都做加密,這樣的話即使攻擊者拿到你的數據,他不知道你的加密算法就無能為力了。
上面說的兩種方式可以同時使用,但是大家還需要考慮一個問題:如何防止攻擊者獲取到你的簽名生成規則和加密算法,例如你加密使用的AES算法,你的秘鑰放在哪里呢?
2、反編譯,讓你的代碼沒有秘密
這里主要指Android項目。大家知道,很多應用尤其是單機版游戲類,都只有iOS版本,沒有Android版本。一些聯網游戲,有Android版本用戶數據和iOS用戶數據也是隔離的。這些現象的原因,一方面是Android應用分發的混亂,另一方面,就是比較低的反編譯和二次打包發布的門檻。
2.1 項目結構
Android項目早期沒有自己單獨的IDE工具,是使用Eclipse加ADT插件進行開發,項目結構跟其他Java項目一致,即一個workspace表示一個工程,里面一個一個的project表示子項目。后面google推出了基于IntelliJ IDEA的Android Studio,項目結構也順其自然的變成了Project+moduled形式。雖然目錄層級有所變化,但是整體結構變化不大。
我們可以看下圖,是一個典型Android項目,PermissionGrantor是項目名稱,里面有兩個子項目,分別是app和grantor,兩個子項目圖標看起來不一樣,是因為他們一個是應用項目,即編譯后可以生成一個可以在Android設備上運行的應用,另外一個是庫項目,可以生成一個被其他項目引用的aar庫。
我們看一下app項目即應用項目,紅框標注的是幾個比較重要的目錄,libs是用來放項目依賴庫的,java目錄是項目的代碼,res則是存放資源,包括圖片、布局、字符串等
最下面的AndroidManifest.xml可以理解成項目的配置文件,操作系統加載一個應用,從哪里開始執行、每個頁面調用那個類和都有哪些服務哪些權限,都需要在這里配置。從這里可以透露一個很重要的信息,就是應用的入口和各個模塊甚至是服務的類,都可以從這里看到,而且是必須要配置到這里并且這個文件是要附帶到發布APK里面的。
2.2 編譯與反編譯
2.2.1 混淆
項目編譯正式包時,默認是開啟混淆的,即類名方法名會被混淆成a,b,c,d的樣子,增加破解著的難度。Android采用的Proguard的開源方案,只能對java類進行混淆。上面的分析中我們知道,程序的啟動類(Application)和業務類(Activity)都需要注冊到AndroidManifest.xml中,而混淆只能對java類進行混淆,并不支持xml這種配置文件,所以Android默認對于Application、Activity、Service、UI相關的類都是不混淆的,而一些通過字符串反射生成類的地方也不能混淆(網絡數據轉為本地java對象),而jni調用方法也不能混淆。下圖是QQ閱讀反編譯后的一個Activity和數據對象。
通過上面分析,我們發現雖然Android引入了混淆技術,但是由于當前混淆方案的缺陷和Android系統框架中大量使用反射技術,使得很多重要的類不能混淆,這極大的降低了反編譯后閱讀源碼的成本。1.3中提到的使用簽名和加密算法,這時你會發現,被破解也不是特別的難,為了增加破解難度,我們可以把算法放到native代碼中,即用C/C++實現算法,通過jni調用。
2.2.2 編譯
項目編譯成apk文件,主要分為以下幾部分:
xml 編譯為二進制文件。xml中所有字符串都會被放到一個資源池中,原來使用字符串的地方會被替換成資源的地址。所以xml編譯為二進制不僅可以減少占用的空間,而且可以提高加載速度。
生成resource.arsc資源索引表
java生成dex文件
2.2.3 簽名
APP編譯成apk包后,必須對包進行簽名才能在手機上安裝。我們分析簽名后的包會發現里面多了MANIFEST.MF、CERT.SF和CERT.RSA三個文件。我們直接用文本編輯器打開MANIFEST.MF如下圖:
可以看到這個文件里面存儲了每一個資源的SHA1摘要的base64值,這里就可以解釋你直接替換了apk包里面的某個資源為什么apk無法安裝,因為程序在安裝時驗證資源的摘要跟MANIFEST.MF中的不一致,可以判斷出這個包被人動了手腳。
我們繼續用文本編輯器打開CERT.SF,如下圖:
我們會發現除了最上面多了一個SHA1-Digest-Manifest值,下面的文件跟第一個文件沒區別,我們可以測試得知,這個值就是第一個文件MANIFEST.MF的SHA1摘要的base64值。(目前最新實現與原來已有些不同,可以參考這篇文章和這篇文章 所以上面提到的,替換APK資源文件后,如果你同時更新MANIFEST.MF中對應的值也不行,因為此時通不過CERT.SF的驗證。那么我同時修改CERT.SF中的值呢?我們可以繼續看第三個文件CERT.RSA,我們發現用文本編輯器打開,它是二進制文件,可以通過下面的命令打開此文件:
openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs -text
打開后的內容為:
網上有資料解析這個結構,我們不展開分析,只要知道,上面那一坨16進制編碼是簽名的公鑰,下面那一坨是簽名,即用你keystore文件中的私鑰對上面的域和值進行加密得到的結果。程序安裝時使用上面的公鑰進行校驗,如果你拿不到對方的私鑰就沒辦法對簽名進行偽造。這就回答了上面拋出的問題,替換apk包中的資源后,同時修改MANIFEST.MF和CERT.SF也無法通過驗證。
但是如果我同時把上面的公鑰也替換了呢?答案是當然可以通過驗證啊。因為你的這一系列動作等同于對APK重新簽名了,不過使用的是你自己的私鑰。Android采用的是自簽名的證書,所以如果有人拿到了你的APP,用自己的keystore重新簽名然后發布,用戶并不知道他是山寨的還是正版的。這就解釋了為什么很多單機游戲都不做Android版本的,你辛苦做一個收費的游戲,攻擊者很容易破解在里面加點廣告,然后再二次打包發布。一些不愿意花錢買游戲的用戶只需要忍受下廣告就可以免費玩游戲了。通過這里的分析,我們知道攻擊者很難偽造我們的簽名,所以我們可以在程序中插入簽名檢查的代碼,提高攻擊門檻。
2.3 反編譯
上面已經把APK結構分析的差不多了,反編譯要做的工作就是反匯編.dex和還原xml文件。我們可以使用apktool直接對一個apk進行反編譯,可以得到還原后的xml文件和.smali文件。這里的smali文件是可以運行在android虛擬機上的匯編語言,讀起來還是有些晦澀的,如果有讀源碼的需求,可以直接解壓apk包,使用dex2jar工具直接反編譯dex文件,可以得到反編譯后的class文件,即我們能得到反編譯后的java源碼。我們1.3中提到的加密算法和秘鑰,就可以通過分析反編譯后的源碼去獲取。為了不讓別人獲取類似敏感信息,很多人選擇這些信息繼續向底層放到lib.so中,下面一節我們會介紹。
3、NDK與反匯編
3.1 NDK
NDK全稱為Native Development Kit,翻譯過來叫原生開發工具包,官方解釋是可以在Android中使用C/C++的工具。我沒找到這里Native的詳細解釋,個人理解常規Android程序是運行在虛擬機上面的,而Native指直接運行在操作系統上面的程序,可以調用操作系統的API。一般情況下我們沒有必要使用NDK,官方也提到了使用native開發會增加開發過程的復雜性。但是對于一些計算密集型的應用,例如游戲、圖像處理,使用NDK能提高運算性能。還有一些情況為了復用現有庫或者跨平臺庫,也會選擇NDK。上面提到的一些核心算法和秘鑰,大家選擇放到native層,潛意識中也是默認native的破解難度比java高,還有欺負大部分Android程序員不會寫C/C++代碼:)
我們可以創建一個新工程,創建時勾選上"Include C++ support",如下圖:
這時會創建一個支持NDK的默認工程,項目里包含從C++代碼中獲取一個字符串的demo:
我們編譯出apk并反編譯這個程序如下圖,會發現包里面多了一堆libnative-lib.so(其實只是一個庫,為了支持不同的CPU架構編程出了多個版本)。
字符串的生成的實現放到了這個so庫里面,相比較直接在java代碼中生成字符串,破解起來的確麻煩了些。這個時候如果想破解拿到這個字符串,該怎么辦呢?
一個簡單的辦法,直接寫java代碼,通過jni調用這個so的方法。這尼瑪就尷尬了,本來封裝so是為了隱藏數據,結果倒好,別人才不管你怎么實現,把你直接拿來用。為了避免這種尷尬,我們可以在so中判斷當前應用的簽名,前面分析過,只要別人拿不到你的keystore就沒辦法偽造你的簽名。如果沒辦法直接調用這個so,只能想辦法破解才能洞察到里面隱藏的秘密了,下面我們談一下怎么來破解。
3.2 PE/ELF和反匯編
上面提到了我們需要破解so文件才能獲取里面的信息,在破解之前我們要先理解so文件到底是什么。這里我們我們需要了解一種文件格式,即Windows系統中的PE(Portable Executable)和Linux系統下的ELF(Executable Linkable Format),看全稱基本了解這種文件的用途。其實在Windows系統上我們平時運行的.exe安裝文件和使用共(破)享(解)軟件時用來覆蓋的.dll動態鏈接庫文件,都是PE格式文件。在Linux系統上(我們可以把Android理解為Linux系統)的.o目標文件、.a靜態連接庫和.so動態連接庫,都屬于ELF文件。如下圖所示,一段C程序編譯生成ELF文件后的結構
當然我們直接用文本編輯器打開so文件,看到的是如下二進制文件:
根據ELF文件格式的定義,可以把這個二進制文件還原為圖3.4右邊的結構。這些細節很復雜我們也沒必要去全部弄清楚,我們可以使用一些命令直接查看ELF文件。以圖3.2中的C++代碼生成的so為例,我們輸入:
readelf -a libnative-lib.so
可以看到結果:
內容很長,不了解ELF格式的話很難看懂(我這里在使用“greadelf”是因為使用的mac系統自己安裝的類似工具),我們可以先大概看一下,這里的“節頭”其實就是對應的圖3.4中的section,我們可以查看第12個segment——.rodata:
我們發現了之前辛苦藏到so中的字符串"Hello from C++"。如果想看代碼實現,可以使用objdump命令,能看到反匯編后的代碼。
現在我們可以理解ELF文件已經是機器指令了,我們如果想看一些代碼邏輯,要么像機器一樣讀機器指令,要么把這些機器指令反編譯成人類方便閱讀的匯編代碼(匯編語言人類也很難讀的好吧),這個過程我們就是反匯編。
當然真正去破解一些so文件時,使用上面的辦法效率太低,這是可以去使用一些專業的破解工具,里面集成了很多很強大的功能,可以大大提高工作效率,例如使用IDA直接把so打開,我們可以很方便的定位到jni函數:
從這個例子的分析,我們為了增加破解難度,可以動態注冊jni函數并且自定義函數名,避免破解者一眼就找到Java_com_xxx這樣的native函數。另外一點就是要隱藏在字符串,不可以直接明文寫在代碼中,至少做個拼接吧,或者添加一定的邏輯動態生成。大家可以想一下,還有沒有其他的辦法增加破解難度?
4、加殼與脫殼
通過上面的分析我們可以知道,無論是編譯java代碼生成的dex文件,還是編譯C/C++代碼生成的so文件,反編譯成本都不是特別的高,如果想增加破解難度,還能做什么呢?其實這個問題一直存在,大家想想從十幾年前的PC時代,大部分人使用的都是windows,當時大家使用的office、photoshop有幾個不是破解版的?因為當時網絡不發達,很多游戲和工具軟件都是本地驗證授權的,再加上法律監管空白,軟件破解行業甚是紅火。
在當時就有了加殼脫殼這個說法,看一些破解軟件的文章時,第一步基本都是脫殼。加殼直觀理解就是給程序加一層殼,可以用來對原程序進行資源壓縮、防調試、防注入、防反編譯,也就是說通過一個殼把原來的程序保護了起來。我們知道一個常規Android程序它的所有代碼都在dex文件中,程序啟動時要先把這個dex文件載入到內存中,所以如果要加殼的話,主要工作就是把原dex文件加密或者隱藏起來,放一個新的殼dex到apk中,程序啟動時運行這個殼dex,然后這個殼dex在運行時再加載原dex,用一張圖表示如下:
其實這時候我們發現,破解原dex的工作變成了破解殼dex了,最終的原理是一樣的,早期的梆梆、360等公司的加固方案都被人破解,網上能查到破解步驟。當然他們的加固方案升級很快,網上看的破解方案對于新的加固方案基本已經無效。我們使用騰訊的樂固加密一個不包含so庫的APK后反編譯后可以看到如下:
我們可以看到程序一開始就加載了libshella.so庫,這里我們可以推測我們的原dex被加密為了mix.dex和mixz.dex,dex的動態還原方案的實現放到了libshell.so庫中,即dex的加殼方案放到了so實現,再次佐證了大家公認so的破解難度比較高。這里我測試發現這兩個so使用IDA反匯編工具已經無法直接打開了,很顯然這個so是加過殼的。關于so的脫殼,我也是小白,就不班門弄斧了,這里樂固.so V2.8版本的脫殼方案(樂固目前最新是V2.10),可以證明上面我們的推測是正確的,大家有興趣可以看一下https://bbs.pediy.com/thread-217556.htm
補充: 這是一年前的文章,當時認為加固是比較安全的方案,畢竟逆向so成本非常高,后面發現使用像xposed框架這樣的hook技術,類似于降維打擊,可以繞過加固技術輕松獲取到dex文件。目前的樂固、360等大廠加固都可以繞過,從原理上看,加固技術對于這種hook技術獲取dex也沒有什么好辦法,作為APP的作者,需要加強hook方面的防御來提高加固技術的安全性,后面有時間的話我會寫一篇文章介紹這方面的攻防。
參考文章:
http://blog.csdn.net/luoshengyang/article/details/8744683
http://blog.csdn.net/jiangwei0910410003/article/details/50402000
http://www.cnblogs.com/LiuYanYGZ/p/5574602.html
http://www.cnblogs.com/EliteDci/p/5578901.html