分享一下學習過程中的心得,主要獻給和我一樣起步晚又對安卓產生濃烈興趣的童鞋們!
一、代碼跟蹤的介紹&使用工具
代碼跟蹤常用於調試程序中,跟蹤並瞭解程序的執行軌跡和執行邏輯。這樣來說,對Java這樣的高級語言來說,我們容易理解也容易調試。但是像一些低級語言,例如ASM、Smali來說難度就很大了,因爲反彙編這樣的語言,我們需要一次性記住很多亂七八糟的關鍵詞才能成功,因而代碼跟蹤的技術就誕生了,並且在一些大程序中的運用更爲廣泛,同時也是現在逆向一些大而複雜的Android-app極爲重要的一種手段。
相信很多人都玩過PC端,對OD的動態調試也應該特別熟悉。OD很強大,其中的F2(斷點),F8(單步走)使得調試程序事半功倍,我相信用過得沒有說不好的吧。而Android-app與PC software的性質是不同的。在Android-app裏是把Apk文件Dump成字節碼,分析,修改,再重新編譯。有一個很大的不同點就是Android-app但是正是由於開源,使得Dump出來的字節碼是“死”的,這是Java的一個特性。幸運的是,Apktool很好的解決了這個問題,它的出現使得Android-app的調試得到了轉機,受益的是大家。
既然不能進行完全的調試,想到的辦法是用系統的log函數來呈現程序運行到關鍵時候的關鍵信息來達到相應的效果。比如說,你想知道程序運行到某個時候某個變量裏裝的是什麼,就可以用log的方式顯示。
獲取Android程序的log信息一共有幾種方式。最好的選擇當時是用DDMS,因爲它和Android SDK是一夥的!它可以顯示很多系統的log信息以及一些其他的亂七八糟的信息。同時,可以設置過濾器來進行標籤過濾、log等級(error,warning,info,debug,verbose)過濾、進程過濾,也能結束進程。相當強大。如圖:
二、跟蹤方法
1.Logging大法
因爲Apps沒有控制檯所以它們只能通過Android 的API中的log函數來進行輸出。
關於android/util/Log的介紹如下:
Log extends Object
一共有5個方法:Log.v() Log.d() Log.i()Log.w() and Log.e(),它們分別對應:
int |
Priority constant for the println method. |
|
int |
Priority constant for the println method; use Log.d. |
|
int |
Priority constant for the println method; use Log.e. |
|
int |
Priority constant for the println method; use Log.i. |
|
int |
Priority constant for the println method; use Log.v. |
|
int |
Priority constant for the println method; use Log.w |
Log.d一般來說是用的最多的。(本來log.v是最多的,但是log.v並不參加編譯)
寫一個測試類來分析Java代碼如下:
public class Log {
public Log() {}
public static void Log(String msg)
{
Log(msg, "Log");
}
public static void Log(Object XX)
{
Log("Log", XX.toString());
}
public static void Log(Object XX, String tag)
{
Log(tag, XX.toString());
}
public static void Log(String tag, String msg)
{
Log.d(tag, msg);
}
public static void ToastLog(Context contx, String msg, int duration)
{
Toast.makeText(contx, msg, duration).show();
}
}
反彙編成Smali代碼如下:
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 8
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static Log(Ljava/lang/Object;)V
.registers 3
.parameter "XX"
.prologue
.line 17
const-string v0, "Log"
invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Lcom/Ericky/Log;->Log(Ljava/lang/String;Ljava/lang/String;)V
.line 18
return-void
.end method
.method public static Log(Ljava/lang/Object;Ljava/lang/String;)V
.registers 3
.parameter "XX"
.parameter "tag"
.prologue
.line 22
invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v0
invoke-static {p1, v0}, Lcom/Ericky/Log;->Log(Ljava/lang/String;Ljava/lang/String;)V
.line 23
return-void
.end method
.method public static Log(Ljava/lang/String;)V
.registers 2
.parameter "msg"
.prologue
.line 12
const-string v0, "Log"
invoke-static {v0, p0}, Lcom/Ericky/Log;->Log(Ljava/lang/String;Ljava/lang/String;)V
.line 13
return-void
.end method
.method public static Log(Ljava/lang/String;Ljava/lang/String;)V
.registers 2
.parameter "tag"
.parameter "msg"
.prologue
.line 28
invoke-static {p0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 29
return-void
.end method
.method public static ToastLog(Landroid/content/Context;Ljava/lang/String;)V
.registers 3
.parameter "contx"
.parameter "msg"
.prologue
.line 33
const/4 v0, 0x5
invoke-static {p0, p1, v0}, Lcom/Ericky/Log;->ToastLog(Landroid/content/Context;Ljava/lang/String;I)V
.line 34
return-void
.end method
.method public static ToastLog(Landroid/content/Context;Ljava/lang/String;I)V
.registers 4
.parameter "contx"
.parameter "msg"
.parameter "duration"
.prologue
.line 40
invoke-virtual {p0}, Landroid/content/Context;->getApplicationContext()Landroid/content/Context;
.line 41
invoke-static {p0, p1, p2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 42
return-void
.end method
取出第一個方法:
.method public static Log(Ljava/lang/Object;)V
.registers 3
.parameter "XX"
.prologue
.line 17
const-string v0, "Log"
invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Lcom/Ericky/Log;->Log(Ljava/lang/String;Ljava/lang/String;)V
.line 18
return-void
.end method
在這個方法裏的第一個參數就是標籤,在DDMS中是可以過濾的。因此,可以利用這個特點插入我們要加的信息,從而輕鬆的定位到關鍵的地方。在實際操作的過程中,可以打開androidAPI的文檔,對着看。通常的做法是用Log.d,還要用到toString()這個API,同時需要2個變量,一個變量存放你的標籤以及另一個存放你的對象。值得注意的是,即使你根本不用這個2個變量,也需要將.locals和.registers的數量增加。修改完了之後記得檢查好代碼,至少我們自己能讀懂它。
還有一點需要我們注意,當你改完之後最好在根目錄下運行,否則記得還要改class的路徑。不然的話有可能出現ClassDefNotFound錯誤,程序強制退出。下面是修改的代碼:
const-string v0, "Ericky tag"
const-string v1, "important log message"
invoke-static {v0, v1}, Lcom/Ericky/Log;->Log(Ljava/lang/String;Ljava/lang/String;)V
這是一個簡單的例子,但是有的時候我們是需要顯示更多的字符串,那我們應該怎麼辦呢?需要用到一個StringBuilder類。StringBuilder類的用法在我之前的一篇文章中已經有用到了,很容易理解。http://bbs.pediy.com/showthread.php?t=194955
還有一個方法就是大家蒐集一些沒有加密的app的smali源碼,這就是我們自己的圖書館啊!誰用誰知道!
1.棧跟蹤法
有時候破解的時候,我們會找一些關鍵的註冊字符串,例如“Register、failed、limit、pro”等等,但是很多程序其實在剛啓動的時候就檢測完了用戶的合法性,所以可能造成定位不準確。一些混淆過的程序一般都是這樣的。這時候我們就可以用棧跟蹤法了,可以查看堆棧,能看到它所調用的方法,查看堆棧代碼:
invoke-static {},Ljava/lang/Thread;->dumpStack()V
它的標籤爲:System.err 實際效果:
java.lang.Throwable: stack dump
at java.lang.Thread.dumpStack(Thread.java:612)
at com.lohan.testbed.SomeClass.superFun(SomeClass.java:113)
at com.lohan.testbed.Main.onCreate(Main.java:48)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
at android.app.ActivityThread.access$2300(ActivityThread.java:125)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
從第三行可以知道dumpStack實際上是在SomeClass類中的superFun()函數裏。然後superFun()函數又是在onCreate()函數裏。以此類推。不過值得一提的是,如果app被混淆了,並且debugging 信息被刪除了或者是被重命名了,會出現“Unknownsource”,但是還是可以判斷的。
1.線程和處理器
很多時候app用了大量線程之間的通信導致了棧跟蹤法變得不是那麼有效了,這個時候如果你還是打印出堆棧的信息的話,只會追溯到最後的那個線程的消息處理器,而原始的那個線程就被隱藏了。當然可以通過手工來追溯,方法是根據代碼中的一些函數類似於sendMessage()等等。這裏有個更簡單的方式:
// 在handleMessage() 中的smali代碼
invoke-static {v0}, Lsample/thread/messaging/ThreadMessaging;->access$0(Lsample/thread/messaging/ThreadMessaging;)Landroid/os/Handler;
move-result-object v2
invoke-virtual {v2}, Landroid/os/Handler;->obtainMessage()Landroid/os/Message;
move-result-object v1
//假設v2是你的處理器
invoke-virtual {v2}, Landroid/os/Handler;->getLooper()Landroid/os/Looper;
move-result-object v2
invoke-virtual {v2}, Landroid/os/Looper;->getThread()Ljava/lang/Thread;
move-result-object v2
invoke-virtual {v2}, Ljava/lang/Thread;->getName()Ljava/lang/String;
move-result-object v2
但是2個線程如果同時在運行,這個方法就不好使了,原因就留給大家去思考了。
1.“遺留下的寶藏”
這個方法雖然說跟運氣脫不了干係,但在實際的運用中還是比較廣泛的。因爲程序員是人,是人就會有疏忽。在編寫程序的時候,程序員會用很多的log來調試他自己寫的程序是否正確,因此就給逆向人員留下了一筆寶貴的財富。當然,很有可能有一個Boolean類型調試變量來控制是否輸出log信息,類似下面:
public class MyClass {
private Context myContext;
private boolean DebugMode;
public MyClass(Context c)
{
myContext = c;
DebugMode = false;
}
public void someMethod()
{
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(myContext);
if ( DebugMode )
Log.d("someMethod", "running someMethod now!");
SharedPreferences.Editor editor = settings.edit();
try {
editor.putString("mobileID", getMobileID());
}
catch (Exception e) {
e.printStackTrace();
}
editor.commit();
}
}
那我們只需要找到DebugMode的位置,把它置成“1”即可。
5.SmaliDebugging(APKtools)
如果log信息都被程序員刪除了,那怎麼辦?此時此刻,我們不得不拿出神器Apktools來調試一段一段的smali語句了,根據官方介紹,還可以單步走。是不是很誘人呢?具體可以看:http://code.google.com/p/android-apktool/wiki/SmaliDebugging
6.JDB神器
這是一個很強大的工具,應該是類似GNU或者GDB調試器。打開DDMS獲取端口,然後連接調試器。
在Linux下的命令:
jdb -attach localhost:8616
在Windows下的命令:
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8616
出現這樣的文字說明成功了:
next toyour App and the console should display something like this:
Setuncaught java.lang.Throwable
Setdeferred uncaught java.lang.Throwable
Initializingjdb ...
三、總結
當然還有更多更好的方法,希望大牛們繼續補充。學習Android已經1個多星期了,菜鳥一枚。很多地方難免有疏漏之處或者錯誤,歡迎各位童鞋指正,也歡迎多多交流,共同學習進步。
ByEricky
2014.12.4