APK調試

源碼的情況下,對APK的動態調試主要分爲兩種:
smali彙編動態調試
arm彙編動態調試

Smali彙編動態調試

對smali彙編的動態調試主要分爲兩種:
使用ida進行調試
使用IDE + apktool進行調試
Eclipse + apktool
Android studio + apktool
Idea + apktool

使用jeb2.2以後版本調試

IDA 調試smali

步驟:

1.設置APP的debug選項
修改APP AndroidManifest.xml中Application標籤包含屬性android:debuggable=true,重新打包APP,這種動了簽名可能過不了簽名校驗
第二種使用setpropex設置
setpropex ro.secure 0
setpropex ro.debuggable 1
第三種修改/default.prop配置文件,重刷boot.img

2.設置調試選項

Dalvik debugger裏,設置adb路徑,包名,activity(minifest 裏的actibity)。

就是ida的Debugger的Debugger Options的Set specific options裏

注意包名那裏如果不是1級包,就要全包名。

然後就可以調試了

jeb2 遠程調試

jeb2.2.x版本支持apk調試,能夠同時對java層和native層彙編代碼進行調試
必須設置%ANDROID_SDK%
步驟:
1. Debugger菜單選擇啓動
2. 選擇設備和被調試進程
3. 下斷點調試

https://www.pnfsoftware.com/blog/jeb-android-debuggers/


 

名稱混淆是通過一個再res裏的proguard

一般拿到apk逆向,觀察apk按鈕彈窗,彈窗名字或者字符串。

然後找到關鍵函數Q(decompile)反編譯成java,比如這裏

然後爲了方便觀察吧一些變量重命名,另外findviewbyid的ID,想去找這個id,跟他關聯起來,那這個id一定存在與我們的解包裏的smali文件會用到。因爲id這個是可見的,所以可以將解包後的文件託到編輯器裏面做字符串搜索。數字可能變成16進制,如果沒找到就找16進制的。

另外可以在layout裏的activity_main.xml看到佈局裏有兩個check和掃描的button

 <public type="id" name="button" id="0x7f0d004a" />
    <public type="id" name="btnScan" id="0x7f0d004b" />

。從public.xml裏button和btnScan的id就可以確定jeb的id分別各是哪個button,從而而確定了setOnclickListener哪個是check的button的。然後修改名字爲checkListener,然後對於這個demo,就是要從確定按鈕,找到校驗用戶名密碼的邏輯。然後

然後觀察checkListener的邏輯。

class checkListener implements View$OnClickListener {
    checkListener(MainActivity arg1) {
        this.a = arg1;
        super();
    }

    public void onClick(View arg5) {
        String v0 = this.a.editText.getText().toString();
        String v1 = this.a.editText2.getText().toString();
        if(v0.length() < 6 || v0.length() > 20) {
            this.a.s.setMessage("Name too short(<6) or too long(>20)!");
        }
        else if(1 == this.a.NativeCheckRegister(v0, v1)) {
            this.a.s.setMessage("Check Success!");
        }
        else {
            this.a.s.setMessage("Check Fail!");
        }

        this.a.s.create().show();
    }
}

這裏差不多就看到了,真正校驗就在NativeCheckRegister

然後如果不知道邏輯,需要動態調試

步驟:
1. Debugger菜單選擇啓動
2. 選擇設備和被調試進程
3. 下斷點調試

在Jeb調試窗口中,比較重要的就是觀察變量的窗口,另外類型可以根據情況自己修改類型。

IDA遠程調試APK文件

調試步驟:
1. 上傳android_server到安卓手機
2. 開啓app調試
3. 切換到root用戶,啓動android_server
4. 端口轉發adb forward tcp:23946 tcp:23946
5. 打開ida,選擇Debugger,選擇attach,選擇調試器:
Remote ARM Linux/Android debugger
6. 連接配置
127.0.0.1 port:默認23946即可
.  選擇目標進程
8.  選擇目標地址,下斷點
9.  轉發jdb
adb forward tcp:8899 jdwp:pid
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8899

一般打開兩個ida端口。一個靜態觀察,一個動態調試。

注意第一次斷下是進程被掛起的斷下,並不是我們的斷點。

尋找下斷點的位置,可以開啓Debugger Windows的Module list ,查找到我們的模塊,然後找到符號,然後在想下斷點的位置下斷。

apk逆向流程

拿到apk,首先要做的事安裝體驗apk的功能。
獲取apk的一些基本信息:
1. 瀏覽apk安裝時所申請的權限,特別關注一些敏感權限(比如去看minifest)
打電話、收發短信、讀取通信錄等等
2. 熟悉apk的界面,界面的佈局,界面的切換(比如layout文件,xml裏等)
3. 關注界面的名稱,控件的名稱
4. 關注控件所觸發的事件,對apk的功能有初步認識
5. 關注logcat信息,是否存在一些敏感的debug信息
把玩的目的:
對apk的功能有初步認識,掌握apk的全局信息,便於模塊化分析,後續可通過資源文件來輔助定位關鍵信息

apk逆向分析

對apk分析的方式和其他平臺的bin分析方式類似,分爲靜態和動態分析
由於apk存在應用層和native層,故需要分層分析
靜態分析的目的:
對apk的功能架構有全面的認識
明確apk各個功能模塊

動態分析的目的:
對局部關注的目的進行調試,關注模塊的具體細節,關注執行流程、算法等等

根據apk文件的組成模塊,依次分析
AndroidMannifest.xml
class.dex
結合資源文件夾
lib目錄
確定Native庫
搜索raw目錄,一般存放一些重要文件,如可執行文件、加密數據等等

分析內容
Activity
Activity名稱、數量
啓動Activity
BroadcastReceive
靜態廣播類型名稱、數量、權限設置
Service
後臺任務進程名稱、數量、權限設置
Content Provider
數據共享權限設置、接口
確定apk申請的權限,過濾出一些敏感權限
確定入口點,確定是否使用了第三方加固技術
 

然後來看下demo的例子,着重看一下arm

class checklistener implements View$OnClickListener {
    checklistener(MainActivity arg1) {
        this.a = arg1;
        super();
    }

    public void onClick(View arg5) {
        String v0 = this.a.editText.getText().toString();
        String v1 = this.a.editText2.getText().toString();
        if(v0.length() < 6 || v0.length() > 20) {
            this.a.s.setMessage("Name too short(<6) or too long(>20)!");
        }
        else if(1 == this.a.NativeCheckRegister(v0, v1)) {
            this.a.s.setMessage("Check Success!");
        }
        else {
            this.a.s.setMessage("Check Fail!");
        }

        this.a.s.create().show();
    }
}

這裏看到nativeCheckRegister傳入了2個java的string。

因爲第一次參數是env那第二個參數是jobj,jstring,jstring所以分別對應r0,r1,r2,r3

另外在IDAoption->general->number of opcode bytes 可以改opcode分配大小,改成4,

.text:00001758 F7 B5                       PUSH    {R0-R2,R4-R7,LR}
.text:0000175A 01 68                       LDR     R1, [R0]        ; //*env
.text:0000175C 17 1C                       MOVS    R7, R2          ; 根據jeb那看出這裏r2是name
.text:0000175E A9 22 92 00                 MOVS    R2, #0x2A4
.text:00001762 1D 1C                       MOVS    R5, R3          ; 密碼
.text:00001764 8B 58                       LDR     R3, [R1,R2]     ; 從*env之後加個值給r3後面發現blx r3這裏其實就是(*env)->Funx(0x2A4)
.text:00001766 39 1C                       MOVS    R1, R7
.text:00001768 00 22                       MOVS    R2, #0
.text:0000176A 04 1C                       MOVS    R4, R0          ; env給r4

然後*env是個結構體,我們沒有把這個結構導入,可以在Structures界面,點Edit插入結構體。高版本IDA已經支持導入jni結構體

注意jni裏env結構體JNINativeInterface* C_JNIEnv;所以導入srearch的是

然後右鍵將其變成JNINativeInterface的偏移這裏就顯示是

.text:0000175E A9 22 92 00                 MOVS    R2, #JNINativeInterface.GetStringUTFChars

00001768 00 22                       MOVS    R2, #0
.text:0000176A 04 1C                       MOVS    R4, R0          ; env給r4
.text:0000176C 98 47                       BLX     R3              ; r1,r2根據上面r7,#0所以這裏函數就是(*env->)GetStringUTFChars(env,name,0)
.text:0000176E 21 68                       LDR     R1, [R4]
.text:00001770 A9 22 92 00                 MOVS    R2, #JNINativeInterface.GetStringUTFChars
.text:00001774 06 1C                       MOVS    R6, R0          ; r0是上面函數返回值,暫存r6,爲char* name
.text:00001776 8B 58                       LDR     R3, [R1,R2]
.text:00001778 20 1C                       MOVS    R0, R4
.text:0000177A 29 1C                       MOVS    R1, R5
.text:0000177C 00 22                       MOVS    R2, #0
.text:0000177E 98 47                       BLX     R3              ; GetStringUTFChars(env,passwd,0)
.text:00001780 00 90                       STR     R0, [SP,#0x20+p_passwd]
.text:00001782 00 99                       LDR     R1, [SP,#0x20+p_passwd]
.text:00001784 30 1C                       MOVS    R0, R6
.text:00001786 FF F7 55 FF                 BL      checkLogical    ; 參數至少char*,char*

然後進入checkLogical發現r3被賦值r4,說明參數有兩個沒多的。所以可以在函數流程裏右鍵settype設置類型

int checkLogical(char* name,char* passwd)

所以後面代碼,改type之類

00001786 FF F7 55 FF                 BL      checkLogical
.text:0000178A 21 68                       LDR     R1, [R4]
.text:0000178C AA 22 92 00                 MOVS    R2, #JNINativeInterface.ReleaseStringUTFChars
.text:00001790 8B 58                       LDR     R3, [R1,R2]
.text:00001792 01 90                       STR     R0, [SP,#0x20+var_1C]
.text:00001794 39 1C                       MOVS    R1, R7
.text:00001796 32 1C                       MOVS    R2, R6
.text:00001798 20 1C                       MOVS    R0, R4
.text:0000179A 98 47                       BLX     R3
.text:0000179C 21 68                       LDR     R1, [R4]
.text:0000179E AA 22 92 00                 MOVS    R2, #JNINativeInterface.ReleaseStringUTFChars
.text:000017A2 8B 58                       LDR     R3, [R1,R2]
.text:000017A4 20 1C                       MOVS    R0, R4
.text:000017A6 29 1C                       MOVS    R1, R5
.text:000017A8 00 9A                       LDR     R2, [SP,#0x20+p_passwd]
.text:000017AA 98 47                       BLX     R3
.text:000017AC 01 98                       LDR     R0, [SP,#0x20+var_1C]

發現F5之後跟實際差距較大,因爲結構體不對, 所以可以給這個驗證native代碼加入settype

int Java_com_tencent_tencent2016a_MainActivity_NativeCheckRegister(JNIEnv *,void *,jstring *,jstring *)

int __cdecl Java_com_tencent_tencent2016a_MainActivity_NativeCheckRegister(JNIEnv *a1, void *a2, jstring *a3, jstring *a4)
{
  jstring *v4; // r7@1
  jstring *v5; // r5@1
  JNIEnv *v6; // r4@1
  char *v7; // r6@1
  char *p_passwd; // ST00_4@1
  int v9; // ST04_4@1

  v4 = a3;
  v5 = a4;
  v6 = a1;
  v7 = (char *)(*a1)->GetStringUTFChars(a1, a3, 0);
  p_passwd = (char *)((int (__fastcall *)(_DWORD, _DWORD, _DWORD))(*v6)->GetStringUTFChars)(v6, v5, 0);
  v9 = checkLogical(v7, p_passwd);
  ((void (__fastcall *)(_DWORD, _DWORD, _DWORD))(*v6)->ReleaseStringUTFChars)(v6, v4, v7);
  ((void (__fastcall *)(_DWORD, _DWORD, _DWORD))(*v6)->ReleaseStringUTFChars)(v6, v5, p_passwd);
  return v9;
}

這樣發現就比較接近

下面我們來看下如果函數末尾沒識別如何處理,比如我們自己的函數,前面學習jni編寫的函數。

比如設置函數settype,bad declaration,這時候可以對着名字點右鍵p,create function,因爲這裏JavaVM也是JNI結構體所以也可以導入結構體。JNIInvokeInterface,還有根據源碼版本類型的枚舉也可以加入如圖

 

.text:000006C8 ; int __cdecl JNI_OnLoad(JavaVM *, void *)
.text:000006C8                 EXPORT JNI_OnLoad
.text:000006C8 JNI_OnLoad
.text:000006C8
.text:000006C8 env             = -0x18
.text:000006C8 var_14          = -0x14
.text:000006C8 var_10          = -0x10
.text:000006C8
.text:000006C8                 PUSH            {R4-R7,LR}
.text:000006CA                 ADD             R7, SP, #0xC
.text:000006CC                 STR.W           R11, [SP,#0xC+var_10]!
.text:000006D0                 SUB             SP, SP, #8
.text:000006D2                 LDR             R1, ='8
.text:000006D4                 LDR             R4, =JNI_VERSION_1_4 ; 版本,這裏是個枚舉,可以在enums添加
.text:000006D6                 ADD             R1, PC ; __stack_chk_guard_ptr
.text:000006D8                 LDR             R6, [R1] ; __stack_chk_guard
.text:000006DA                 MOV             R2, R4
.text:000006DC                 LDR             R1, [R6]
.text:000006DE                 STR             R1, [SP,#0x18+var_14]
.text:000006E0                 MOVS            R1, #0
.text:000006E2                 STR             R1, [SP,#0x18+env]
.text:000006E4                 LDR             R1, [R0]
.text:000006E6                 LDR             R3, [R1,#JNIInvokeInterface.GetEnv]
.text:000006E8                 MOV             R1, SP
.text:000006EA                 BLX             R3
.text:000006EC                 CBZ             R0, loc_708
.text:000006EE
.text:000006EE loc_6EE                                 ; CODE XREF: JNI_OnLoad+44j
.text:000006EE                                         ; JNI_OnLoad+56j ...
.text:000006EE                 MOV.W           R4, #0xFFFFFFFF
.text:000006F2
.text:000006F2 loc_6F2                                 ; CODE XREF: JNI_OnLoad+6Cj
.text:000006F2                 LDR             R0, [SP,#0x18+var_14]
.text:000006F4                 LDR             R1, [R6]
.text:000006F6                 SUBS            R0, R1, R0
.text:000006F8                 ITTTT EQ
.text:000006FA                 MOVEQ           R0, R4
.text:000006FC                 ADDEQ           SP, SP, #8
.text:000006FE                 LDREQ.W         R11, [SP+0x10+var_10],#4
.text:00000702                 POPEQ           {R4-R7,PC}
.text:00000704                 BLX             __stack_chk_fail
.text:00000708 ; ---------------------------------------------------------------------------
.text:00000708
.text:00000708 loc_708                                 ; CODE XREF: JNI_OnLoad+24j
.text:00000708                 LDR             R5, [SP,#0x18+env]
.text:0000070A                 CMP             R5, #0
.text:0000070C                 BEQ             loc_6EE
.text:0000070E                 LDR             R0, [R5]
.text:00000710                 LDR             R1, =(aComExampleAppl - 0x718)
.text:00000712                 LDR             R2, [R0,#JNINativeInterface.FindClass]
.text:00000714                 ADD             R1, PC  ; "com/example/applicationandjni/MainActiv"...
.text:00000716                 MOV             R0, R5
.text:00000718                 BLX             R2
.text:0000071A                 MOV             R1, R0
.text:0000071C                 CMP             R0, #0
.text:0000071E                 BEQ             loc_6EE
.text:00000720                 LDR             R0, [R5]
.text:00000722                 MOVS            R3, #1
.text:00000724                 LDR.W           R12, [R0,#JNINativeInterface.RegisterNatives]
.text:00000728                 MOV             R0, R5
.text:0000072A                 LDR             R2, =(off_4004 - 0x730)
.text:0000072C                 ADD             R2, PC ; off_4004
.text:0000072E                 BLX             R12
.text:00000730                 CMP.W           R0, #0xFFFFFFFF
.text:00000734                 BGT             loc_6F2
.text:00000736                 B               loc_6EE
.text:00000736 ; End of function JNI_OnLoad
.text:00000736
.text:00000736 ; ---------------------------------------------------------------------------
.text:00000738 dword_738       DCD JNI_VERSION_1_4     ; DATA XREF: JNI_OnLoad+Cr
.text:0000073C dword_73C       DCD '8                ; DATA XREF: JNI_OnLoad+Ar
.text:00000740 off_740         DCD aComExampleAppl - 0x718 ; DATA XREF: JNI_OnLoad+48r
.text:00000740                                         ; "com/example/applicationandjni/MainActiv"...
.text:00000744 off_744         DCD off_4004 - 0x730    ; DATA XREF: JNI_OnLoad+62r
.text:00000748 ; ---------------------------------------------------------------------------
.text:00000748

 

所以根據上面是registerNatives

.text:0000072A                 LDR             R2, =(off_4004 - 0x730)這裏是註冊函數,因爲是顯示註冊所以指針是沒有導出的,要逆這一塊。

static int registerNativeMethods(JNIEnv* env, const char* className,
        JNINativeMethod* gMethods, int numMethods)
{
	jclass clazz = env->FindClass(className);
	if (clazz == NULL) {
		return JNI_FALSE;
	}
	if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

 所以在RegisterNatives,env是r0,clazz是r1,gMethods是r2,所以這裏r2是個數組,參考

.text:0000072C                 ADD             R2, PC ; off_4004

r2是跟pc相關的一個偏移,雙擊off,發現是個三元組,函數名,參數,指針

.data:00004004 off_4004        DCD aTestc              ; DATA XREF: JNI_OnLoad+64o
.data:00004004                                         ; .text:off_744o
.data:00004004                                         ; "testC"//函數名
.data:00004008                 DCD aLjavaLangStr_0     ; "()Ljava/lang/String;"//參數
.data:0000400C                 DCD sub_8D4+1//指針
.data:0000400C ; .data         ends

所以雙擊sub_8D4,這裏是我們的test,改名test

.text:000008D4             test                                    ; DATA XREF: .data:0000400Co
.text:000008D4 02 68                       LDR             R2, [R0]
.text:000008D6 02 49                       LDR             R1, =(aHelloFromMetho - 0x8E0)
.text:000008D8 D2 F8 9C 22                 LDR.W           R2, [R2,#0x29C]
.text:000008DC 79 44                       ADD             R1, PC  ; "Hello from Method C"
.text:000008DE 10 47                       BX              R2
.text:000008DE             ; End of function test

所以如果我們下斷點,要下的是這裏。

所以我們通過靜態分析找到了他。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章