一、前言
今天我們開始apk破解的另外一種方式:動態代碼調試破解,之前其實已經在一篇文章中說到如何破解apk了:
Android中使用靜態方式破解Apk主要采用的是靜態方式,步驟也很簡單,首先使用apktool來反編譯apk,得到smail源碼,然后分析smail代碼,采用代碼注入技術來跟蹤代碼,然后找到關鍵方法進行修改,進而破解,同時還可以使用一些開源的hook框架,比如:Xposed和Cydia Substrate,來進行關鍵方法的hook。所以這里我們可以看到我們破解的第一步是使用apktool來進行成功的反編譯,然后是需要了解smali語法,不過關于smali語法其實很簡單,網上有很多教程。
二、知識概要分析
那么今天我們就用另外一種方式來破解apk:動態方式,關于動態方式其實很廣義的,因為動態方式相對于靜態方式來說,難度大一點,但是他比靜態方式高效點,能夠針對更過的破解范圍。當然動態方式很多,所以這里就分為三篇文章來講解這塊:
1、動態方式破解apk前奏篇(Eclipse動態調試smail源碼)
2、動態方式破解apk升級篇(IDA動態調試so源碼)
3、動態方式破解apk終極篇(應對加固的apk破解方法)
從這三篇文章能夠讓我們破解一般的apk沒有任何問題,不過不能代表能夠破解所有的apk,因為沒有絕對的安全,也是沒有絕對的破解,兩方都在進步,我們只能具體問題具體分析。好了,下面我們就來看第一篇文章,也是今天的重點:Eclipse動態調試smali源碼
首先需要解釋一下,這里為什么說是調試smali源碼,不是Java源碼,因為我們弄過反編譯的人知道,使用apktool反編譯apk之后,會有一個smali文件夾,這里就存放了apk對應的smali源碼,關于smali源碼這里不解釋了,網上有介紹。
三、案例分析
因為這一篇是一個教程篇,所以不能光說,那樣會很枯燥的,所以這里用一個例子來介紹一下:
我們就用阿里2014年安全挑戰賽的第一題:AliCrack_one.apk
看到這張圖了,阿里還挺會制造氛圍的,那么其實很簡單,我們輸入密碼就可以破解了,下面我們就來看看如何獲取這個密碼。
第一步:使用apktool來破解apk
java -jar apktool_2.0.0rc4.jar d -d AliCraceme_1.apk -o out
這里的命令不做解釋了,但是有一個參數必須帶上,那就是:-d
因為這個參數代表我們反編譯得到的smali是java文件,這里說的文件是后綴名是java,如果不帶這個參數的話,后綴名是smali的,但是Eclipse中是不會識別smali的,而是識別java文件的,所以這里一定要記得加上這個參數。
反編譯成功之后,我們得到了一個out目錄,如下:
源碼都放在smali文件夾中,我們進入查看一下文件:
看到了,這里全是Java文件的,其實只是后綴名為java了,內容還是smali的:
2、修改AndroidManifest.xml中的debug屬性和在入口代碼中添加waitDebug
上面我們反編譯成功了,下面我們為了后續的調試工作,所以還是需要做兩件事:
1》修改AndroidManifest.xml中的android:debuggable="true"
關于這個屬性,我們前面介紹run-as命令的時候,也提到了,他標識這個應用是否是debug版本,這個將會影響到這個應用是否可以被調試,所以這里必須設置成true。
2》在入口處添加waitForDebugger代碼進行調試等待。
這里說的入口處,就是程序啟動的地方,就是我們一般的入口Activity,查找這個Activity的話,方法太多了,比如我們這里直接從上面得到的AndroidManifest.xml中找到,因為入口Activity的action和category是固定的。
當然還有其他方式,比如aapt查看apk的內容方式,或者是安裝apk之后用adb dumpsys activity top命令查看都是可以的。
找到入口Activity之后,我們直接在他的onCreate方法的第一行加上waitForDebugger代碼即可,找到對應的MainActivity的smali源碼:
然后添加一行代碼:
invoke-static {}, Landroid/os/Debug;->waitForDebugger()V
這個是smali語法的,其實對應的Java代碼就是:android.os.Debug.waitForDebugger();
這里把Java語言翻譯成smali語法的,不難,網上有smali的語法解析,這里不想再解釋了。
第三步:回編譯apk并且進行簽名安裝
java -jar apktool_2.0.0rc4.jar b -d out -o debug.apk
還是使用apktool進行回編譯
編譯完成之后,將得到debug.apk文件,但是這個apk是沒有簽名的,所以是不能安裝的,那么下面我們需要在進行簽名,這里我們使用Android中的測試程序的簽名文件和sign.jar工具進行簽名:
關于簽名的相關知識,可以看這篇文章:Android中的簽名機制詳解
java -jar .\sign\signapk.jar .\sign\testkey.x509.pem .\sign\testkey.pk8 debug.apk debug.sig.apk
簽名之后,我們就可以進行安裝了。
第四步:在Eclipse中新建一個Java工程,導入smali源碼
這里我們新建一個Java工程,記住不是Android工程,因為我們最后調試其實是借助于Java的調試器,然后勾選掉Use default location選項,選擇我們的smali源碼目錄,也就是我們上面反編譯之后的out目錄,點擊完成
我們導入源碼之后的項目工程結構:
主要看MainActivity類:
第五步:找到關鍵點,然后打斷點
這一步我們看到,其實說的比較廣義了,這個要具體問題具體分析了,比如這個例子中,我們知道當我們輸入密碼之后,肯定要點擊按鈕,然后觸發密碼的校驗過程,那么這里我們知道找到這個button的定義的地方,然后進入他的點擊事件中就可以了。這里分為三步走:
1》使用Eclipse自帶的View分析工具找到Button的ResId
點擊之后,需要等待一會,分析View之后的結果:
看到了,這里我們能夠看到整個當前的頁面的全部布局,已經每個控件的屬性值,我們需要找到button的resource-id
這里我們看到定義是@+id/button這個值。
2》我們得到這個resId之后,能否在smali工程中全局搜索這個值,就可以定位到這個button的定義的地方呢?
然后我們看看搜到的結果:
這時候我們其實是在資源文件中搜到了這個id的定義,這個id值對應的是0x7F05003E。
當然除了這種方式,我們還有一種方式能快速找到這個id對應的整型值,那就是在反編譯之后的values/public.xml文件中:
這個文件很有用的,他是真個apk中所有資源文件定義的映射內容,比如drawable/string/anim/attr/id 等這些資源文件定義的值,名字和整型值對應的地方:
這個文件很重要,是我們在尋找突破口的重要關鍵,比如我們有時候需要通過字符串內容來定位到關鍵點,這里就可以通過string的定義來找到對應的整型值即可。
當我們找到了button對應的id值了之后,我們就可以用這個id值在一次全局搜索一下,因為我們知道,Android中編譯之后的apk,在代碼中用到的resId都是用一個整型值代替的,這個整型值就是在R文件中做了定義,將資源的id和一個值對應起來,然后代碼里面一般使用R.id.button這樣的值,在編譯出apk的時候,這個值就會被替換成對應的整型值,所以在全局搜索0x7F05003E
搜索的結果如下:
看到了,這里就定位到了代碼中用到的這個button,我們進入代碼看看:
在這里,看到了,使用了findViewById的方式定義Button,我們在往下面簡單分析一下smali語法,下面是給button添加一個按鈕事件,這里用的是內部類MainActivity$1,我們到這個類看看,他肯定實現了OnClickListener接口,那么直接搜onClick方法:
在這里我們就可以下個斷點了,這里就是觸發密碼校驗過程。
第六步:運行程序,設置遠程調試工程
在第五步中,我們找到了關鍵點,然后打上斷點,下面我們就來運行程序,然后在Eclipse中設置遠程調試的工程
首先我們運行程序,因為我們加入了waitForDebug的代碼,所以啟動的時候會出現一個Wait debug的對話框。不過,我測試的時候,我的手機沒有出現這個對話框,而是一個白屏,不過這個不影響,程序運行起來之后,我們看看如何在Eclipse中設置遠程調試工程,首先我們找到需要調試的程序對應遠程調試服務端對應的端口:
這里我們看到有幾個點:
1》在程序等待遠程調試服務器的時候,前面會出現一個紅色的小蜘蛛
2》在調試服務端這里我們會看到兩個端口號:8600/8700,這里需要解釋一下,為什么會有兩個端口號呢?
首先在這里的端口號,代表的是,遠程調試服務器端的端口,下面在簡單來看一下,Java中的調試系統:
這里我們看到,這里有三個角色:
111》JDB Client端(被調試的客戶端),這里我們可以認為我們需要破解的程序就是客戶端,如果一個程序可以被調試,當啟動的時候,會有一個jdwp線程用來和遠程調試服務端進行通信
這里我們看到,我們需要破解的程序啟動了JDWP線程,注意這個線程也只有當程序是debug模式下才有的,也就是AndroidManifest.xml中的debug屬性值必須是true的時候,也就是一開始為什么我們要修改這個值的原因。
222》JDWP協議(用于傳輸調試信息的,比如調試的行號,當前的局部變量的信息等),這個就可以說明,為什么我們在一開始的時候,反編譯成java文件,因為為了Eclipse導入能夠識別的Java文件,然后為什么能夠調試呢?因為smali文件中有代碼的行號和局部變量等信息,所以可以進行調試的。
333》JDB Server端(遠程調試的服務端,一般是有JVM端),就是開啟一個JVM程序來監聽調試端,這里就可以認為是本地的PC機,當然這里必須有端口用來監聽,那么上面的8600端口就是這個作用,而且這里端口是從8600開始,后續的程序端口后都是依次加1的,比如其他調試程序:
那么有了8600端口,為什么還有一個8700端口呢?他是干什么的?
其實他的作用就是遠程調試端備用的基本端口,也就是說比如這里的破解程序,我們用8600端口可以連接調試,8700也是可以的,但是其他程序,比如demo.systemapi他的8607端口可以連接調試,8700也是可以的:
所以呀,可以把8700端口想象成大家都可以用于連接調試的一個端口,不過,在實際過程中,還是建議使用程序獨有的端口號8600,我們可以查看8600和8700端口在遠程調試端(本地pc機)的占用情況:
看到了,這里的8600端口和8700端口號都是對應的javaw程序,其實javaw程序就是啟動一個JVM來進行監聽的。
好了,到這里我們就弄清楚了,Java中的調試系統以及遠程調試的端口號。
注意:
其實我們可以使用adb jdwp命令查看,當前設備中可以被調試的程序的進程號信息:
下面繼續,我們知道了遠程調試服務端的端口:8600,以及ip地址,這里就是本地ip:localhost/127.0.0.1
我們可以在Eclipse中新建一個遠程調試項目,將我們的smali源碼工程和設備中需要調試的程序關聯起來:
右擊被調試的項目=》選擇Debug Configurations:
然后開始設置調試項目
選擇Romote Java Application,在Project中選擇被調試的smali項目,在Connection Type中選擇SocketAttach方式,其實還有一種方式是Listener的,關于這兩種方式其實很好理解:
Listner方式:是調試客戶端啟動就準備好一個端口,當調試服務端準備好了,就連接這個端口進行調試
Attach方式:是調試服務端開始就啟動一個端口,等待調試端來連接這個端口
我們一般都是選擇Attach方式來進行操作的。
好了,我們設置完遠程調試的工程之后,開始運行,擦發現,設備上的程序還是白屏,這是為什么呢?看看DDMS中調試程序的狀態:
擦,關聯到了這個進程,原因也很簡單,我們是上面使用的是8700端口號,這時候我們選中了這個進程,所以就把smali調試工程關聯到了這個進程,所以破解的進程沒反應了,我們立馬改一下,用8600端口:
好了,這下成功了,我們看到紅色的小蜘蛛變成綠色的了,說明調試端已經連接上遠程調試服務端了。
注意:
我們在設置遠程調試項目的時候,一定要注意端口號的設置,不然沒有將調試項目源碼和調試程序關聯起來,是沒有任何效果的
第七步:開始運行調試程序,進入調試
下面我們就開始操作了,在程序的文本框中輸入:gggg內容,點擊開始:
好了,到這里我們看到期待已久的調試界面出來了,到了我們開始的時候加的斷點處,這時候我們就可以開始調試了,使用F6單步調試,F5單步跳入,F7單步跳出進行操作:
看到了,這里使用v3變量保存了我們輸入的內容
這里有一個關鍵的地方,就是調用MainActivity的getTableFromPic方法,獲取一個String字符串,從變量的值來看,貌似不是規則的字符串內容,這里先不用管了,繼續往下走:
這里又遇到一個重要的方法:getPwdFromPic,從字面意義上看,應該是獲取正確的密碼,用于后面的密碼字符串比對。
查看一下密碼的內容,貌似也是一個不規則的字符串,但是我們可以看到和上面獲取的table字符串內容格式很像,接著往下走:
這里還有一個信息就是,調用了系統的Log打印,log的tag就是v6保存的值:lil
這時候,我們看到v3是保存的我們輸入的密碼內容,這里使用utf-8獲取他的字節數組,然后傳遞給access$0方法,我們使用F5進入這個方法:
在這個方法中,還有一個bytesToAliSmsCode方法,使用F5進入:
那么這個方法其實看上去還是很簡單的,就是把傳遞進來的字節數組,循環遍歷,取出字節值,然后轉化成int類型,然后在調用上面獲取到的table字符串的chatAt來獲取指定的字符,使用StringBuilder進行拼接,然后返回即可。
按F7跳出,查看,我們返回來加密的內容是:日日日日,也就是說gggg=>日日日日
最后再往下走,可以看到是進行代碼比對的工作了。
那么上面我們就分析完了所有的代碼邏輯,還不算復雜,我們來梳理一下流程:
A>調用MainActivity中的getTableFromPic方法,獲取一個table字符串
我們可以進入看看這個方法的實現:
這里可以大體了解了,他是讀取asset目錄下的一個logo.png圖片,然后獲取圖片的字節碼,在進行操作,得到一個字符串,那么我們從上面的分析可以知道,其實這里的table字符串類似于一個密鑰庫。
B>通過MainActivity中的getPwdFromPic方法,獲取正確的密碼內容
C>獲取我們輸入內容的utf-8的字節碼,然后調用access$0方法,獲取加密之后的內容
D>access$0方法中在調用bytesToAliSmsCode方法,獲取加密之后的內容
這個方法是最核心的,我們通過分析知道,他的邏輯是,通過傳遞進來的字節數組,循環遍歷數組,拿到字節轉化成int類型,然后在調用密鑰庫字符串table的charAt得到字符,使用StringBuilder進行拼接。
通過上面的分析之后,我們知道獲取加密之后的輸入內容和正確的密碼內容做比較,那么我們現在有的資源是:
密鑰庫字符串和正確的加密之后的密碼,以及加密的邏輯
那么我們的破解思路其實很簡單了,相當于,我們知道了密鑰庫字符串,也知道了,加密之后的字符組成的字符串,那么可以通過遍歷加密之后的字符串,循環遍歷,獲取字符,然后再去密鑰庫找到指定的index,然后在轉成byte,保存到字節數組,然后用utf-8獲取一個字符串,那么這個字符串就是我們要的密碼。
下面我們就用代碼來實現這個功能:
代碼邏輯,很簡單吧,其實這個函數相當于上面加密函數的bytesToAliSmsCode的反向實現,運行結果:
OK,得到了正確的密碼,下面來驗證一下:
哈哈,不要太激動,成功啦啦~~。破解成功。
補充:
剛剛我們在斷點調試的時候,看到了代碼中用了Log來打印日志,tag是lil,那么我們可以打印這個log看看結果:
看到了,這里table是密鑰庫,pw是正確的加密之后的密碼,enPassword是我們輸入之后加密的密碼。
所以從這里可以看到,這個例子,其實我們在破解apk的時候,有時候日志也是一個非常重要的信息。
破解需要的資料,我已經上傳了,下載地址:http://download.csdn.net/detail/jiangwei0910410003/9526113
四、思路整理
1、我們通過apktool工具進行apk的反編譯,得到smali源碼和AndroidManifest.xml,然后修改AndroidManifest.xml中的debug屬性為true,同時在入口處加上waitForDebug代碼,進行debug等待,一般入口都是先找到入口Activity,然后在onCreate方法中的第一行這里需要注意的是,apktool工具一定要加上-d參數,這樣反編譯得到的文件是java文件,這樣才能夠被Eclipse識別,進行調試。
2、修改完成AndroidManifest.xml和添加waitForDebug之后,我們需要在使用apktool進行回編譯,回編譯之后得到的是一個沒有簽名的apk,我們還需要使用signapk.jar來進行簽名,簽名文件直接使用測試程序的簽名文件就可以,最后在進行安裝。
3、然后我們將反編譯之后的smali源碼導入到Eclipse工程中,找到關鍵點,進行下斷點,這里的關鍵點,一般是我們先大致了解程序運行的結構,然后找到我們需要破解的地方,使用View分析工具,或者是使用jd-gui工具直接查看apk源碼(使用dex2jar將dex文件轉化成jar文件,然后用jd-gui進行查看),找到代碼的大體位置。然后下斷點,這里我們可以借助Eclipse的DDMS自帶的View分析工具找到對應控件的resid,然后在全局搜索這個控件的resid,或者直接在values/public.xml中查找,最終定位到這個控件位置,在查看他的點擊事件即可。
4、設置遠程調試工程,首先運行需要調試程序,然后在DDMS中找到對應的調試服務端的端口號,然后在Debug Configurations中設置遠程調試項目,設置對應的調試端口和ip地址(一般都是本機pc,那就是localhost),然后紅色小蜘蛛變成綠色的,表示我們的遠程調試項目連接關聯上了調試程序,這里需要注意的是,一定需要關聯正確,不然是沒有任何效果的,關聯成功之后,就可以進行操作。
5、操作的過程中,會進入到關鍵的斷點處,通過F6單步,F5單步進入,F7單步跳出,來進行調試,找到關鍵方法,然后通過分析smali語法,了解邏輯,如果邏輯復雜的,可以通過查看具體的環境變量的值來觀察,這里也是最重要的,也是最復雜的,同時這里也是沒有規章可尋的,這個和每個人的邏輯思維以及破解能力有關系,分析關鍵的加密方法是需要功底的,當然這里還需要注意一個信息,就是Log日志,有時候也是很重要的一個信息。
6、最后一般當我們知道了核心方法的邏輯,要想得到正確的密碼,還是需要自己用語言去實現邏輯的,比如本文中的加密方法,我們需要手動的code一下加密的逆向方法,才能得到正確的密碼。
五、遺留問題
1、使用apktool工具進行反編譯有時候并不是那么順利,比如像這樣的報錯:
這個一般都是apktool中解析出現了錯誤,其實這個都是現在apk為了抵抗apktool,做的apk加固策略,這個后面會寫一篇文章如何應對這些加固策略,如何進行apk修復,其實原理就是分析apktool源碼,找到指定的報錯位置,進行apktool代碼修復即可。
2、本文中說到了Java的調試系統,但是為了篇幅限制,沒有詳細的講解了整個內容,后面會寫一篇文章具體介紹Java中的調試系統以及Android的調試系統。
3、有時候我們還會遇到回編譯成功了,然后遇到運行不起來的錯誤,這個就需要使用靜態方式先去分析程序啟動的邏輯,看看是不是程序做了什么運行限制,比如我們在靜態分析那篇文章中,提到了應用為了防止反編譯在回編譯運行,在程序的入口處作了簽名校驗,如果校驗失敗,直接kill掉自己的進程,退出程序了,所以這時候我們還是需要使用靜態方式去分析apk。
4、如何做到不修改AndroidManifest.xml中的debug屬性就可以進行調試:
1》 修改boot.img,從而打開系統調試,這樣就可以省去給app添加android:debuggable="true",再重打包的步驟了。
2.》直接修改系統屬性,使用setpropex工具在已經root的設備上修改只讀的系統屬性。使用此工具來修改ro.secure和ro.debuggable的值。
這個也會在后面詳細介紹這兩種方法
六、總結
這篇文章我們就介紹了如何使用Eclipse去動態調試反編譯之后的smali源碼,這種方式比靜態方式高效很多的,比如本文中的這個例子,其實我們也可以使用靜態方式進行破解的,但是肯定效率沒有動態方式高效,所以以后我們又學會了一個技能,就是動態的調試smali源碼來跟蹤程序的核心點,但是現在市場上的大部分應用沒有這么簡單就破解了,比如核心的加密算法放到了native層去做,那么這時候就需要我們去動態調試so文件跟蹤,這個是我們下一篇文章的內容,也有的時候,apk進行加固了,直接在apktool進行反編譯就失敗了,這時候我們就需要先進行apk修復,然后才能后續的操作,這個是我們下下篇的文章,如何應對apk的加固策略。通過這篇文章我們可以看到動態方式破解比靜態方式高效的多,但是有時候我們還需要使用靜態方式先做一些準備工作,所以在破解apk的時候,動靜結合,才能做到完美的破解。
關注微信公眾號,最新Android技術實時推送