【轉】Android逆向分析筆記

 原文 :https://lichao890427.github.io/wiki/android%20reverse%20engineering/php

概述

分析步驟

通用逆向分析步驟

  • 1.瞭解該模塊正向編程相關方法
  • 2.使用apktool解密apk,獲得資源、jni模塊等文件
  • 3.從apk提取出dex文件,使用dex2jar轉換成jar文件,再用java逆向工具獲得java源碼 dex->jar->java
  • 4.根據特徵(字符串、常量、包名類名方法名、manifest文件、佈局文件等方式)或調試手段定位到關鍵代碼
  • 5.分析變量含義類型、函數邏輯、模塊流程
  • 6.對變量、函數、類進行標註、恢復成高級語言 ->c

  Android程序的特色相比在於使用混淆方式打包,將包名、類名、函數名改爲不易看懂的字母,從而使生成的apk小不少(android studio提供了release編譯方式,使用proguard混淆),所以反編譯apk最多的工做在於重構這些名稱,這一點和pc上一致,對於android native程序(jni)則和pc上基本一致,不一樣之處在於常見的是arm彙編。html

安卓上APK調試步驟:

  • 1.Apk(debuggable)或系統(ro.debuggable=1)設置爲可調試
  • 2.在虛擬機中啓動服務端(adbd/android_server)
  • 3.在主機端鏈接客戶端調試器(IDA/jdb/adt),設置斷點

安卓上linux程序調試步驟:

  • 1.在虛擬機中啓動服務端(gdb_server/linux_server)
  • 2.在主機端鏈接客戶端調試器(IDA/gdb_for_windows),設置斷點

  對於apk的反編譯,因爲資源和xml都進行了編碼,所以反編譯時必然要解析相應的resource.arsc/AndroidManifest.xml等文件,對於作過保護處理的apk一般會在這裏作手腳干擾Apktool、dex2jar等反編譯工具所以頗有必要掌握編譯、調試這些工具源碼的方法(見「如何編譯、調試apktool和dex2jar」)java

分析工具

  • 集成IDE:APK改之理、JD-GUI、JEB(1.4破解 2.0)、jadx
  • 解壓(apk, jar):WinRar
  • 解析資源:apktool
  • 反編譯引擎(jar, class):dex2jar工具集、jd-core(JD-GUI,JD-Eclipse反編譯核心)、fernflower(Android Studio反編 、procyon
  • 回編譯:aapt、dex2jar工具集
  • 調試器:IDA、jdb、adt等
  • 輔助工具:DDMS 若是是虛擬機能夠看到全部進程

APK改之理

  • 整合&提供了全套解壓、反編譯代碼和資源、回編譯、簽名功能,強大的正則搜索,修改smali字節碼等功能
  • 集成ApkTool、Dex2jar、JD-GUI工具
  • 可視化操做,全自動的反編譯、回編譯、簽名Apk
  • 正則表達式搜索資源及源碼

JD-GUI

輕量級反編譯,反編譯jar/class等java字節碼文件(能力通常),提供簡單的搜索能力python

JEB

  • 反編譯apk/jar工具(能力較強)
  • 強大的正向、反向索引,必定程度重命名能力,必定搜索能力
  • 支持註釋、插件
  • 交互式可視化操做,全自動的反編譯
  • 支持重命名

Dex2jar工具集

  dex2jar是一個工具包,反編譯dex和jar,還提供了一些其它的功能,每一個功能使用一個bat批處理或 sh 腳原本包裝,只需在Windows 系統中調用 bat文件、在Linux 系統中調用 sh 腳本便可。在bat中調用相應的jar主類完成特定功能,例如d2j-dex2jar.bat中的內容是:@"%~dp0d2j_invoke.bat" com.googlecode.dex2jar.tools.Dex2jarCmd %*。經常使用的有dex2jar jar2dex dex2smali smali2dexlinux

  • d2j-apk-sign用來爲apk 文件簽名。命令格式:d2j-apk-sign xxx.apk 。
  • d2j-asm-verify 用來驗證jar 文件。命令格式:d2j-asm-verify -d xxx.jar。
  • d2j-dex2jar 用來將dex 文件轉換成jar 文件。命令格式:d2j-dex2jar xxx.apk
  • d2j-dex-asmifier 用來驗證dex 文件。命令格式:d2j-dex-asmifier xxx.dex。
  • d2j-dex-dump 用來轉存dex 文件的信息。命令格式:d2j-dex-dump xxx.apk out.jar 。
  • d2j-init-deobf 用來生成反混淆jar 文件時的初始化配置文件。
  • d2j-jar2dex 用來將jar 文件轉換成 dex 文件。命令格式:d2j-jar2dex xxx.apk。
  • d2j-jar2jasmin 用來將jar 文件轉換成jasmin 格式的文件。命令格式:d2j-jar2jasmin xxx.jar
  • d2j-jar-access 用來修改jar 文件中的類、方法以及字段的訪問權限。
  • d2j-jar-remap 用來重命名jar 文件中的包、類、方法以及字段的名稱。
  • d2j-jasmin2jar 用來將jasmin 格式的文件轉換成 jar 文件。命令格式:d2j-jasmin2jar dir dex2jar爲d2j-dex2jar 的副本。
  • dex-dump爲d2j-dex-dump 的副本

Apktool反編譯&打包工具

  • 反編譯apk:apktool d file.apk –o path
  • 回編譯apk:apktool b path –o file.apk

常見文件格式

Apk

  Android package,android安裝程序文件,本質上是壓縮包,解壓獲得classes.dex、resources.arsc、AndroidManifest.xml、so文件以及資源文件android

  • Resources.arsc資源描述文件
  • Classes.dex全部代碼編譯過得darvik字節碼文件,可能會有多個
  • AndroidManifest.xml 編譯過的AndroidManifest.xml文件

使用aapt解析xml

aapt d xmltree 1.apk AndroidManifest.xml
N: android=http://schemas.android.com/apk/res/android
  E: manifest (line=2)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="1.0" (Raw: "1.0")
    A: package="com.ibotpeaches.issue767" (Raw: "com.ibotpeaches.issue767")
    A: platformBuildVersionCode=(type 0x10)0x17 (Raw: "23")
    A: platformBuildVersionName="6.0-2438415" (Raw: "6.0-2438415")
    E: uses-sdk (line=0)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x16
      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x17
    E: application (line=3)
      A: android:theme(0x01010000)=@0x7f090083
      A: android:label(0x01010001)=@0x7f060015
      A: android:icon(0x01010002)=@0x7f030000
      A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
      A: android:allowBackup(0x01010280)=(type 0x12)0xffffffff
      A: android:supportsRtl(0x010103af)=(type 0x12)0xffffffff
      E: activity (line=4)
        A: android:theme(0x01010000)=@0x7f090030
        A: android:label(0x01010001)=@0x7f060015
        A: android:name(0x01010003)="com.ibotpeaches.issue767.MainActivity" (Raw
: "com.ibotpeaches.issue767.MainActivity")
        E: intent-filter (line=5)
          E: action (line=6)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "andr
oid.intent.action.MAIN")
          E: category (line=7)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw:
 "android.intent.category.LAUNCHER")
      E: meta-data (line=10)
        A: android:name(0x01010003)="large.int.value" (Raw: "large.int.value")
        A: android:value(0x01010024)="9999999999999999999999" (Raw: "99999999999
99999999999")

查看xml => aapt d xmltree 1.apk AndroidManifest.xml
查看resource => aapt d resources 1.apk (resource.arsc)ios

Dex

  Dalvik Executable,Dalvik可執行文件,從java class文件轉換而來的字節碼,Classes.Dex經過dex2jar轉換成java字節碼(有損),或者dex2smali轉換成darvik彙編(無損)——smali字節碼,其形式以下 c++

Jar

  Java Archive,java歸檔文件,能夠直接解壓獲得class文件git

Odex

dex轉odex:/system/bin/dexopt
dexopt-wrapper 1.apk 1.odexgithub

Aar

  Android歸檔文件,壓縮包格式,包含

  • /AndroidManifest.xml (強制) 未編譯的
  • /classes.jar (強制)
  • /res/ (強制)
  • /R.txt (強制)
  • /assets/ (可選)
  • /libs/*.jar (可選)
  • /jni//*.so (可選)
  • /proguard.txt (可選)
  • /lint.jar (可選)

So

  Linux動態連接庫文件,包含arm64 arm mips mips64 x86 x86-64幾個平臺

工具轉換圖

Android設備上重要目錄

  • /system/app/1.apk 系統應用
  • /data/app/1.apk 用戶應用
  • /data/data/[pkgname] 應用數據(so,database,…)
  • /data/dalvik-cache 存放dex

Java層

經常使用工具

adb

  設備通訊、調試工具,經常使用法:

adb devices 列出當前設備
adb –s d24eb3ab [命令]      指定設備執行命令
adb push 源 目標			 非root機器能夠設置路徑爲/data/local/tmp
adb pull 源 目標
adb shell 				    執行終端
adb logcat 				    查看日誌(/system/logcat爲服務器)
adb jdwp 				    查看遠程jdwp進程
adb forward tcp:主機端口     tcp:遠程端口 		把主機端口消息轉發手機端口(端口對應進程)	用於ida調試
adb forward tcp:主機端口     jdwp:遠程進程ID 	把主機端口消息轉發手機jdwp進程	用於jdb調試 
adb install [apkpath]		安裝apk
adb uninstall [packagename]	卸載apk 注意會完全清理,刪除/data/app下的備份apk
adb remount 				將/system從新映射爲讀寫,以便進行系統區文件操做
adb root                    使adb以root方式啓動,便於push/pull/remount

aapt

  APK資源管理工具,用於增刪查改APK中的文件、資源等,對於分析編譯後的Resource.arsc, AndroidManifest.xml格式較有價值,一般也能夠用winrar對apk/jar進行解壓

打印xml樹 aapt d xmltree 1.apk AndroidManifest.xml
打印資源	aapt d resources 1.apk
添加文件	aapt a 1.apk AndroidManifest.xml
刪除文件	aapt r 1.apk AndroidManifest.xml

am & pm

  Android遠程命令,am執行調試、運行功能,pm執行安裝、卸載功能

  • 啓動應用:am start -D -n "b.myapp/b.myapp.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
  • 啓動服務:am startservice -n com.android.music/com.android.music.MediaPlaybackService
  • 強制中止包:am force-stop com.example.administrator.myapplication
  • 強制結束包進程:am kill com.example.administrator.myapplication am kill all
  • 發送廣播:adb shell am broadcast -a com.android.test
  • 安裝應用:pm install –r 1.apk
  • 卸載應用:pm uninstall packagename
  • 列出全部安裝包:pm list package
  • 查看是否以指定名爲前綴的包存在:pm list package com.qihoo
  • 禁用應用:pm disable packagename (禁用後,圖標消失,對該應用的操做都無效)

有源碼調試APK

Android studio

  在android studio中能夠採用運行調試或進程附加方式調試,支持條件斷點、一次斷點、對單線程下斷,有6種斷點:

TypeCh TypEn Description
行斷點 Java Line Breakpoints 在(java/c)源碼某行下斷
Java類成員變量訪問斷點 Java Field Watchpoints 相似於內存訪問斷點,在讀和寫java類成員變量時斷下
Java類方法斷點 Java Method Breakpoints 在進入java層函數或退出函數時斷下
Java異常斷點 Java Exception Breakpoints 發生java層捕獲或未捕獲異常時斷下
異常斷點 Exception Breakpoints 拋異常或捕獲異常時斷下
符號斷點 Symbolic Breakpoints (c/java)符號斷點

Adb wifi

  應用市場有不少這種軟件,須要Root權限。解決沒有USB數據線的狀況下的調試

C:\Users\Administrator>adb connect 192.168.0.103:5555
connected to 192.168.0.103:5555
此時能夠用adt調試

無源碼調試apk

  不須要調試的通常過程 :使用反編譯工具獲得源代碼,修改調試標識,修改機器碼,最後回編譯簽名:

反編譯apk:apktool d file.apk –o path  
回編譯apk:apktool b path –o file.apk

使用AndroidStudio和Apktool工具調試

  • 第一步,反編譯獲得(佔行)僞源碼:java -jar apktool.jar d -d input.apk -o out,加上-d選項以後反編譯出的文件後綴爲.java,而不是.smali,每一個.java文件立馬都僞形成了一個類,語句全都是「a=0;」這一句,smali語句成爲註釋,作這些都是爲了後面欺騙idea、eclipse、android studio這些ide的
  • 第二步,修改資源或者源碼(smali),修改AndroidManifest.xml調試標識,反編譯之後能夠在dex中插入waitfordebugger或者Log.i的smali代碼來進行相應的控制
  • 第三步,回編譯(-d選項)+簽名
    • 回編譯:apktool b –d path –o input.apk
    • 簽名: java –jar signapk.jar testkey.x509.pem testkey.pk8 input.apk output.apk
  • 第四步,新建android studio工程 ,將反編譯獲得的smali文件夾中的源文件拷貝到源碼目錄(欺騙),回編譯的apk覆蓋目標apk位置 ,刪除Edit configuration的Before launch,下斷點調試

點評:這種方式只能夠用來分析加密很弱的App,前提是apktool能夠成功反編譯

使用jdb調試

  jdb是一個支持java代碼級調試的工具,它是由java jdk提供的,能夠設置斷點、查看堆棧、計算表達式、動態修改類字節碼、調試&跟蹤、修改變量值、線程操做,斷點包括:(源碼)行斷點、符號斷點、成員變量訪問斷點。每一個java程序(windows/ios/android)均可以用jdwp協議進行調試,Android Studio/Eclipse的調試也是創建在該協議基礎之上,下面以實例說明:

  • 第一步,開發demo
    public class MainActivity extends Activity {
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
    
          findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.b.com"));
                  intent.setClassName("com.android.browser","com.android.browser.BrowserActivity");
                  startActivity(intent);
              }
          });
      }
    }
  • 第二步,啓動jdb調試
    adb shell am start -D -n "b.myapp/b.myapp.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

  • 第三步,開始調試
    • 查看ddms中該進程端口號 8600
    • 使用jdb調試:jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600
    • 下斷點:函數斷點stop in android.app.Activity.startActivity(android.content.Intent)
      行斷點 stop at android.app.Activity:123,觸發斷點後顯示堆棧:
      <1> main[1] where
      [1] android.app.Activity.startActivity (Activity.java:3,490)
      [2] b.myapp.MainActivity$1.onClick (MainActivity.java:21)
      [3] android.view.View.performClick (View.java:4,084)
      [4] android.view.View$PerformClick.run (View.java:16,966)
      [5] android.os.Handler.handleCallback (Handler.java:615)
      [6] android.os.Handler.dispatchMessage (Handler.java:92)
      [7] android.os.Looper.loop (Looper.java:137)
      [8] android.app.ActivityThread.main (ActivityThread.java:4,745)
      [9] java.lang.reflect.Method.invokeNative (本機方法)

查看參數

<1> main[1] print intent
 intent = "Intent { act=android.intent.action.VIEW dat=http://www.b.com cmp=
com.android.browser/.BrowserActivity }"

設置源碼從而進行逐行調試

<1> main[1] use D:\Android\sdk\sources\android-18		//參考設備android版本
<1> main[1] use D:\test\MyApplication\app\src\main\java
<1> main[1] list
3,421         * @hide Implement to provide correct calling token.
3,422         */
3,423        public void startActivityAsUser(Intent intent, UserHandle user) {
3,424            startActivityAsUser(intent, null, user);
3,425 =>     }
3,426
3,427        /**
3,428         * @hide Implement to provide correct calling token.
3,429         */
3,430        public void startActivityAsUser(Intent intent, Bundle options, User
Handle user) {

行斷點:

> use D:\test\MyApplication\app\src\main\java
stop at b.myapp.MainActivity:18
正在延遲斷點b.myapp.MainActivity:18。
將在加載類後設置。
> resume
已恢復全部線程。
> 設置延遲的斷點b.myapp.MainActivity:18
斷點命中: "線程=<1> main", b.myapp.MainActivity.onCreate(), 行=18 bci=12
18            int j = 0;

初始斷點

  只要鏈接到jdb就會致使app運行起來,此時若是想斷在初始化這部分就沒有辦法了,不過jdb提供初始命令腳本

  • 暫停全部線程: echo suspend > jdb.ini
  • 執行調試:jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8601

  此時,app仍然處於等調試器狀態,而蟲子已經變綠,此時能夠下斷點,而後resume恢復全部線程 附加後會變綠色蟲子

> > stop in b.myapp.MainActivity.onCreate(android.os.Bundle)
正在延遲斷點b.myapp.MainActivity.onCreate(android.os.Bundle)。
將在加載類後設置。
>resume
已恢復全部線程。
斷點命中: "線程=<1> main", b.myapp.MainActivity.onCreate(), 行=13 bci=0

<1> main[1] where
  [1] b.myapp.MainActivity.onCreate (MainActivity.java:13)
  [2] android.app.Activity.performCreate (Activity.java:5,372)
  [3] android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1,1
04)
  [4] android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2,25
8)
  [5] android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2,350
)
  [6] android.app.ActivityThread.access$700 (ActivityThread.java:160)
  [7] android.app.ActivityThread$H.handleMessage (ActivityThread.java:1,317)
  [8] android.os.Handler.dispatchMessage (Handler.java:99)
  [9] android.os.Looper.loop (Looper.java:137)
  [10] android.app.ActivityThread.main (ActivityThread.java:5,454)

調試命令

stop in:斷點
	step:步入(源碼行)
	stepi:單入(指令)
	step up:執行到返回
	cont:恢復運行
	next:步過
	輸出表達式:print/eval

jdb最大缺點在於難用,因此有人用python封裝了一次,工具名AndBug

無源碼調試dex

    1. 使用ida分析apk或者從apk中提取出的dex
    1. 設置調試選項,包括包名和主類名,參考反編譯的AndroidManifest
    1. 啓動調試便可

Linux層

經常使用工具

Gdbserver

Usage:  gdbserver [OPTIONS] COMM PROG [ARGS ...]
        gdbserver [OPTIONS] --attach COMM PID
        gdbserver [OPTIONS] --multi COMM
		隱藏用法:gdbserver [OPTIONS] +SOCKETFILE --attach PID			會在本地創建socket文件通訊
Options:
  --debug               Enable general debugging output.
  --remote-debug        Enable remote protocol debugging output.
  --version             Display version information and exit.
  --wrapper WRAPPER --  Run WRAPPER to start new programs.
  --once                Exit after the first connection has closed.
使用方式:
啓動模式遠程調試:gdbserver --debug --remote-debug  :23946 /system/test.out [參數]		
附加模式遠程:gdbserver –debug –remote-debug –attach  :23946 1234
	Adb forward tcp:23946 tcp:23946 轉發端口
IDA中選擇Remote GDB Debugger附加便可

Strace

usage: strace [-CdDffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]
              [-p pid] ... [-s strsize] [-u username] [-E var=val] ...
              [command [arg ...]]
or: strace -c [-D] [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...
              [command [arg ...]]
-c --統計每一系統調用的所執行的時間,次數和出錯的次數等.
-C -- like -c but also print regular output while processes are running
-f --跟蹤由fork調用所產生的子進程.
-F --嘗試跟蹤vfork調用.在-f時,vfork不被跟蹤.
-i --輸出系統調用的入口指針
-q --禁止輸出關於脫離的消息
-r --打印出相對時間關於,,每個系統調用
-T --顯示每一調用所耗的時間
-v --輸出全部的系統調用.一些調用關於環境變量,狀態,輸入輸出等調用因爲使用頻繁,默認不輸出
-x --以十六進制形式輸出非標準字符串
-a設置返回值的輸出位置.默認 爲40.
-e expr -指定一個表達式,用來控制如何跟蹤.: option=[!]all or option=[!]val1[,val2]...
   options: trace, abbrev, verbose, raw, signal, read, or write
-o file --將strace的輸出寫入文件filename
-O overhead -- set overhead for tracing syscalls to OVERHEAD usecs
-p pid --跟蹤指定的進程pid.
-D -- run tracer process as a detached grandchild, not as parent
-s strsize --指定輸出的字符串的最大長度.默認爲32.文件名一直所有輸出
-S sortby -- sort syscall counts by: time, calls, name, nothing (default time)
-u username --以username 的UID和GID執行被跟蹤的命令
-E var=val -- put var=val in the environment for command
-E var -- remove var from the environment for command

使用方式:
Strace –f ProcessA		啓動跟蹤
Strace –f –p 234		附加跟蹤
	-e trace=file       -e trace=process    -e trace=network

有源碼so調試

Ndk-gdb

  該程序是一個shell腳本,執行過程以下:

adb shell am start -D -n com.example.hellojni/.HelloJni		啓動app並等待調試器
	ps | grep hellojni									獲得PID 3569
adb shell run-as com.example.hellojni /data/data/com.example.hellojni/lib/gdbserver +debug-socket --attach (3569)PID
	將PID與文件映射創建調試連接(c層)
adb forward tcp:5039 localfilesystem:/data/data/com.example.hellojni/debug-socket將調試連接和本地端口創建連接(c層)
adb forward tcp:65534 jdwp:(3569)PID										 將本地端口和進程創建鏈接(java層)
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=65534			 使用jdb調試java層
arm-linux-androideabi-gdb.exe											
	target remote :5039												 使用gdb調試c層
	set breakpoint pending on

  (使用前關掉騰訊的AndroidServer.exe,不然連不上!!!),在工程目錄下(有AndroidManifest.xml),命令行運行%NDK_ROOT%\ndk-gdb-py.cmd --start --verbose,輸出下面字符即爲成功:

Android NDK installation path: D:/Android/AndroidNDK/android-ndk-r10e
ADB version found: Android Debug Bridge version 1.0.32
Using ADB flags:
Using auto-detected project path: .
Found package name: com.example.hellojni
ABIs targetted by application: arm64-v8a armeabi armeabi-v7a armeabi-v7a-hard mips mips64 x86 x86_64
Device API Level: 19
Device CPU ABIs: armeabi-v7a armeabi
Compatible device ABI: armeabi-v7a
Using gdb setup init: ./libs/armeabi-v7a/gdb.setup
Using toolchain prefix: D:/Android/AndroidNDK/android-ndk-r10e/toolchains/arm-linux-androideabi-4.8/prebuilt/windows/bin/arm-linux-androideabi
Using app out directory: ./obj/local/armeabi-v7a
Found debuggable flag: true
Found device gdbserver: /data/data/com.example.hellojni/lib/gdbserver
Found data directory: '/data/data/com.example.hellojni'
Found first launchable activity: .HelloJni
Launching activity: com.example.hellojni/.HelloJni
## COMMAND: adb_cmd shell am start -D -n com.example.hellojni/.HelloJni
## COMMAND: adb_cmd shell sleep 2.000000
Found running PID: 9139
## COMMAND: adb_cmd shell run-as com.example.hellojni /data/data/com.example.hellojni/lib/gdbserver --attach +debug-socket 9139 [BACKGROUND]
Launched gdbserver succesfully.
Setup network redirection
## COMMAND: adb_cmd forward tcp:5039 localfilesystem:/data/data/com.example.hellojni/debug-socket
Attached; pid = 9139
Listening on Unix socket debug-socket
## COMMAND: adb_cmd pull /system/bin/app_process ./obj/local/armeabi-v7a/app_process
79 KB/s (9488 bytes in 0.117s)
Pulled app_process from device/emulator.
## COMMAND: adb_cmd pull /system/bin/linker ./obj/local/armeabi-v7a/linker
585 KB/s (63596 bytes in 0.106s)
Pulled linker from device/emulator.
## COMMAND: adb_cmd pull /system/lib/libc.so ./obj/local/armeabi-v7a/libc.so
1184 KB/s (310584 bytes in 0.256s)
Pulled /system/lib/libc.so from device/emulator.
Set up JDB connection, using jdb command: C:\Program Files\Java\jdk1.8.0_66\bin\jdb.exe
## COMMAND: adb_cmd forward tcp:65534 jdwp:9139
--------------------./obj/local/armeabi-v7a/gdb.setup---------------
GNU gdb (GDB) 7.7
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i586-pc-mingw32msvc --target=arm-linux-android".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://source.android.com/source/report-bugs.html>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Remote debugging from host 9.11.5.0
warning: Could not load shared library symbols for 112 libraries, e.g. libstdc++.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
0x400daa80 in __futex_syscall3 () from D:\Android\AndroidNDK\android-ndk-r10e\samples\hello-jni\obj\local\armeabi-v7a\libc.so
(gdb)

點評:該工具要求環境極爲苛刻且不穩定,不建議使用

Gdb-Gdbserver

  操做步驟:

  • Android studio導入jni工程,
  • 拷貝.so到搜索路徑,pull /system/lib到搜索路徑,pull /system/linker到搜索路徑
  • 啓動gdbserver (具體命令根據版本不一樣而變)
    gdbserver --attach *:111 1234
  • 轉發端口
    adb forward tcp:111 tcp:111
  • 鏈接本地調試器
    target remote 127.0.0.1:111
(gdb) set solib-search-path C:/Users/lichao/2/
Reading symbols from C:\Users\lichao\2\linker...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\linker
Reading symbols from C:\Users\lichao\2\libc.so...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\libc.so
Reading symbols from C:\Users\lichao\2\libstdc++.so...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\libstdc++.so
Reading symbols from C:\Users\lichao\2\libm.so...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\libm.so
Reading symbols from C:\Users\lichao\2\liblog.so...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\liblog.so
Reading symbols from C:\Users\lichao\2\libcutils.so...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\libcutils.so
Reading symbols from C:\Users\lichao\2\libgccdemangle.so...(no debugging symbols found)...done.
Loaded symbols for C:\Users\lichao\2\libgccdemangle.so
Reading symbols from C:\Users\lichao\2\libcorkscrew.so...(no debugging symbols found)...done.

(gdb) bt
#0  0x400e50e0 in fork () from C:\Users\lichao\2\libc.so
#1  0x76886ca0 in Java_com_example_hellojni_HelloJni_stringFromJNI () from C:\Users\lichao\2\libhello-jni.so
#2  0x416b8350 in dvmPlatformInvoke () from C:\Users\lichao\2\libdvm.so
#3  0x416e8fd2 in dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*) () from C:\Users\lichao\2\libdvm.so
#4  0x416ea9ba in dvmResolveNativeMethod(unsigned int const*, JValue*, Method const*, Thread*) () from C:\Users\lichao\2\libdvm.so
#5  0x416c1828 in dvmJitToInterpNoChain () from C:\Users\lichao\2\libdvm.so
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) list

無源碼調試So

使用Arm版Gdb在移動端直接調試

  • 獲取arm版gdb
  • 把gdb下載到移動端
    adb push gdb /data/bin
  • 執行gdb
    adb shell
    ./data/bin/gdb
GNU gdb 6.7
Copyright (C) 2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=arm-none-linux-gnueabi --target=".
(gdb)

點評:該方法速度快,但很差查看符號

IDA調試

  • 將android_server拷入/data/local/tmp/
    adb push android_server /data/local/tmp/
  • 修改可執行權限,運行
    cd /data/local/tmp/
    chmod 755 android_server
    ./android_server
  • 將模擬器端口轉發至pc端口 (另開啓命令行)
    adb forward tcp:23946 tcp:23946
  • IDA中選擇Remote ARMLinux/Android debugger,端口23946,調試便可,成功之後顯示
    Accepting connection from 127.0.0.1...

Gdb-Gdbserver

  • 啓動server
    ./gdbserver –attach :1234 [pid]
  • 轉發端口
    adb forward tcp:1234 tcp:1234
  • 啓動client
    arm-linux-androideabi-gdb.exe
  • 鏈接server
    target remote :1234
  • 設置單步調試
    set step-mode on
  • 設置反彙編模式
    set disassemble-next on
  • 設置加載so斷點
    catch load 1.so
0xb6cdf480 in __epoll_pwait () from E:\aaa\libc.so
=> 0xb6cdf480 <__epoll_pwait+28>:       1e ff 2f 91     bxls    lr
(gdb) bt
#0  0xb6cdf480 in __epoll_pwait () from E:\aaa\libc.so
#1  0xb6cb70ca in epoll_pwait () from E:\aaa\libc.so
#2  0xb6cb70d8 in epoll_wait () from E:\aaa\libc.so
#3  0xb6f06bd6 in android::Looper::pollInner(int) () from E:\aaa\libutils.so
#4  0xb6f06e52 in android::Looper::pollOnce(int, int*, int*, void**) () from E:\aaa\libutils.so
#5  0xb6e4d41c in android::NativeMessageQueue::pollOnce(_JNIEnv*, _jobject*, int) () from E:\aaa\libandroid_runtime.so
#6  0x732e056e in ?? ()

Gikdbg

  GikDbg 是一款移動平臺的彙編級調試器,它基於 OllyDbg ,GDB 以及 LLVM 實現而來。OllyDbg 現已普遍用於 PC 平臺軟件安全領域,GikDbg 是 OllyDbg 向移動平臺轉移的產物,它能夠協助您完成諸如應用調試分析,應用安全評估,應用漏洞挖掘等移動安全領域。What features can GikDbg support? http://gikir.com/product.php

  • ELF / Mach-O executable file static analysis;
  • Android / iOS App dynamic debugging;
  • Android / iOS remote console;
  • ARM assembler;
  • ARM disassembler;
  • Device file uploading and downloading;
  • Built-in GDB and LLDB;
  • Support for memory breakpoint, software breakpoint, conditional breakpoint;
  • Support for multi-threaded debugging;
  • Support for assembly code level file patching.

GikDbg for IOS

GikDbg for Android

  gikdbg.art-Gikir Debugger for Android RunTime, 是Android平臺的32位彙編級調試器。此處的Android RunTime既指DVM RunTime又指ART RunTime,所以無論是運行dalvik虛擬機仍是運行本地代碼的art都可以使用gikdbg.art進行程序的二進制調試分析。不一樣之處在於dalvik虛擬機的運行時只能調試so動態庫,而art運行時不只能調試so動態庫,還能調試系統鏡像oat,可執行程序dex這樣的文件。另外,gikdbg-Gikir Debugger for iPhone OS,是調試越獄蘋果設備的32位彙編級調試器,同窗們莫搞混淆了哈,它須要一些複雜點的服務端和客戶端的配置,而gikdbg.art在正常狀況下是不須要手工配置的,因此別去找android server了。對於靜態分析,能夠執行/ART Debug/View/ELF Data…,/ART Debug/View/ELF Code…兩個菜單打開本地so,oat,dex文件。

調試so

Step 0.前置說明
手機端:Android模擬器,Android 4.4.2 ART 運行時;(真機與DVM運行時是同樣的)
PC端:ParallelDesktop虛擬機,Windows 8.0,gikdbg.art v1.0.build140601.3;
PS:非root環境的設備因爲權限的緣由會有不少問題,不推薦使用!
Step 1.鏈接設備
運行模擬器,打開gikdbg.art.exe,執行/ART Debug/Device菜單,咱們就能夠來到以下界面:

若是模擬器已經運行了,可是設備列表中沒有,則等待一段時間後執行右鍵的Refresh菜單。而後雙擊或者右鍵Login就能夠登錄選中的設備了。對於第一次Login該設備,會詢問你是否上傳依賴的文件到/data/local,這一步若是否認了的話將不能使用調試功能。上傳文件這個步驟目前已知的問題是對於非root的設備,每每由於權限的緣由上傳不成功,通常狀況下/data/local/tmp目錄沒有問題,可是有些設備又沒有/data/local/tmp目錄,所以咱們只有設置/data/local爲目標路徑,這個問題目前還不知道好的解決辦法。概括一下就是:非root的機器沒法在其/data/local下建立咱們依賴的文件夾以及上傳文件,若是咱們將其遷移至/data/local/tmp這個目錄下,又有部分設備沒有這個文件夾,就更沒有辦法上傳了。
對於這類上傳失敗的同窗,能夠想辦法手工將$(GIKDBG.ART)/adb/android/gdb傳至/data/local/gikir_android-xxxx/gdb這個位置,其中xxxx是GUID。
若是尚未安裝該apk文件的,則能夠在ADB Shell中執行$install –r命令選擇gikdebugee.apk進行安裝.
Step 2.選擇進程
登錄成功後執行,確保模擬器的gikdebugee.apk運行正常,而後執行/ART Debug/File/Attach就能夠獲得以下進程列表,選中咱們的gikdebugee進程,雙擊或者執行Attach按鈕

以後咱們就會看到以下加載輸出:

等gdb加載完畢以後咱們就能夠進入熟悉的CPU主窗口了:

Step 3.選擇模塊
咱們的目的是調試apk裏面的so動態庫,所以執行/ART Debug/View/Module切換到模塊列表,選中咱們要調試的模塊,雙擊它

Step 4.擊中斷點
 本例中找到要調試的函數getNativeString,咱們能夠用CTRL+F查找到它,找到以後F2下斷點,F9運行它,而後在設備中操做按鈕則該方法將被斷點擊中,F8運行3步

調試Android上Linux程序

adb push %NDK%\prebuilt\android-arm\gdbserver\gdbserver /system/bin
chmod 777 /system/bin/gdbserver
adb push test.out /system/bin
chmod 777 /system/bin/test.out
gdbserver :2345 /system/bin/test.out(若附加調試則提供進程號)
adb forward tcp:2345 tcp:2345
gdb >
gdb > target remote :2345

技巧:如何在so入口下斷?

  用ida分析so,並在JNI_OnLoad下斷點,動態附加後,ida會自動rebase,使用gdb 的catch load命令捕獲

Java層/Linux層聯合調試

有源碼聯合調試

參照前幾節

無源碼聯合調試

操做步驟

adb shell am start -D -n com.example.hellojni/.HelloJni		啓動app並等待調試器
	ps | grep hellojni									獲得PID 3569
adb shell run-as com.example.hellojni /data/data/com.example.hellojni/lib/gdbserver +debug-socket --attach (3569)PID
	將PID與文件映射創建調試連接(c層)
adb forward tcp:5039 localfilesystem:/data/data/com.example.hellojni/debug-socket將調試連接和本地端口創建連接(c層)
adb forward tcp:65534 jdwp:(3569)PID										 將本地端口和進程創建鏈接(java層)
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=65534			 使用jdb調試java層
arm-linux-androideabi-gdb.exe											
	target remote :5039												 使用gdb調試c層
	set breakpoint pending on

最簡單的gdb中,斷在加載so時刻的方法

  • 1.以等待模式啓動
    am start -D -n com.example.hellojni/.HelloJni
  • 2.Gdbserver連接該進程(ps | grep hello)
    gdbserver --attach :1234 10863
  • 3.轉發端口
    adb forward tcp:1234 tcp:1234
  • 4.鏈接gdb
    arm-linux-androideabi-gdb Target remote :1234
    root@ja3gchnduos:/ # am start -D -n com.example.hellojni/.HelloJni
    Starting: Intent { cmp=com.example.hellojni/.HelloJni }
    root@ja3gchnduos:/ # ps | grep hello
    u0_a165   10863 3593  869292 16088 ffffffff 40077a08 S com.example.hellojni
    root@ja3gchnduos:/ # gdbserver --attach :1234 10863
    Attached; pid = 10863
    Listening on port 1234
  • 5.設置符號路徑(提早把/system/lib/*.so /system/bin/linker libhello-jni.so拷貝到目錄)
    set solib-search-path c:/1
  • 6.設置加載so斷點
    catch load libhello-jni.so
  • 7.執行continue,使用android studio的attach使程序繼續運行
  • 8.加載so時自動斷下:
    Catchpoint 1
    Inferior loaded C:\Users\lichao\sumsing\libhello-jni.so
    0x40036b8c in rtld_db_dlactivity () from C:\Users\lichao\sumsing\linker
  • 9.用ida分析出onload要下斷點的偏移,b *addr下斷

Android linux內核層調試

  Android底層爲linux層,gdb用於調試linux應用層,而kgdb用於調試linux內核層
  kgdb的android版本下載:http://github.com/dankex/kgdb-android

使用Hook

經常使用Hook/Inject工具簡介

  經常使用Hook框架:

  • Cydia Substrate
    • 支持Java層hook
    • 支持Jni層hook
    • 須要Root,且機型適配
    • 支持dalvik,不支持art
    • 閉源
  • Xposed https://github.com/rovo89/Xposed
    • 支持Java層hook
    • 須要Root,且機型適配
    • 支持dalvik/art
    • 開源
  • Frida https://github.com/frida/frida
    • 須要Root
    • 支持Java層hook
    • 支持Jni層hook
    • 支持dalvik/art
    • 開源
    • 任意時刻注入,簡單易用,遠程代碼即時編譯並注入運行
  • Adbi https://github.com/evilsocket/arminject
    • 須要Root
    • 支持Jni層hook
    • 任意時刻注入,手工
    • 開源

實例 360手機衛士卸載後彈窗分析過程

現象

  360手機衛士在非root狀況下卸載後彈出瀏覽器。因而有2種常見可能,一種是intent跳轉,一種是執行am命令,後者能夠在java層和jni層實現,若是是java層考慮進行hook,jni層考慮修改am.jar

文件注入

  將/system/bin/am更名,發現沒法彈窗,因而肯定是經過第二種方式實現,爲了肯定調用層級,嘗試修改(反編譯成smali->加入logcat輸出打印回溯棧和接收參數->回編譯)/system/framework/am.jar,(能夠經過本身再另外一個app中實現一樣的功能,經過反編譯獲得smali代碼)再次反編譯後內容以下:

public static void main(String[] args) {
        String v0 = "";
        int v3 = args.length;
        int v2;
        for(v2 = 0; v2 < v3; ++v2) {
            v0 = String.valueOf(v0) + " " + args[v2];
        }
        Log.d("my god", v0);
        Log.d("my god", Log.getStackTraceString(new Throwable()));
    }

分析日誌

  在卸載瞬間拿到輸出:

start -n com.android.browser/.BrowserActivity -a android.intent.action.VIEW -d http://shouji.360.cn/web/uninstall/uninstall.html?u=100&id=76bb84de8f53b53f57dd3cedfe966091&v=6.3.1.1048&s=1&model=SE0gTk9URSAxTFRF&sdk=19&ch=200222&wid=9fa298f35aec4232c26048442f36dc59 --user 0
java.lang.Throwable 
	at com.android.commands.am.Am.main(Am.java:30)
	at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
	at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:245)
	at dalvik.system.NativeStart.main(Native Method)

發現命令行是 am start –n com.android.browser/.BrowserActivity -a android.intent.action.VIEW

定位關鍵代碼

  經過字符串搜索,定位到java層關鍵代碼,使用android hook框架cydia substrate,掛鉤java.lang.Runtime類的exec函數,定位到調用棧:

content:/data/user/0/com.qihoo360.mobilesafe/files/so_libs/um.0.2 com.qihoo360.mobilesafe --execute am start -n com.android.browser/.BrowserActivity -a android.intent.action.VIEW -d http://shouji.360.cn/web/uninstall/uninstall.html?u=100\&id=7b55c26b779bd111dfed8b02bb00131c\&v=5.5.0.1041\&s=1\&model=TmV4dXMgUw\&sdk=19\&at=KTvooEkhHMzgJ13AXfMkJINnhrmyyNdu\&ch=200222 --user 0
java.lang.Throwable 
	at com.example.emptytest.Main$1$1.invoked(Main.java:68)
	at com.saurik.substrate.MS$2.invoked(MS.java:68)
	at java.lang.Runtime.exec(Native Method)
	at egv.a(360MobileSafe:257)
	at egv.a(360MobileSafe:66)
	at com.qihoo360.mobilesafe.ui.index.MobileSafeApplication.p(360MobileSafe:1223)
	at com.qihoo360.mobilesafe.ui.index.MobileSafeApplication.onCreate(360MobileSafe:799)

結論

  啓動不久,360啓動linux程序/data/data/com.qihoo360.mobilesafe/com.qihoo360.mobilesafe/files/so_libs/um.0.2,並將彈窗任務以參數形式傳遞給該程序,程序中對/data/data/com.qihoo360.mobilesafe文件夾的刪除操做進行掛鉤,以實現卸載後彈窗機制

GDB調試

反彙編一段地址

(gdb) disass /r 0x401148b8,0x40114900
Dump of assembler code from 0x401148b8 to 0x401148c8:
=> 0x401148b8:  0c 70 a0 e1     mov     r7, r12
   0x401148bc:  01 0a 70 e3     cmn     r0, #4096       ; 0x1000
   0x401148c0:  1e ff 2f 91     bxls    lr
   0x401148c4:  00 00 60 e2     rsb     r0, r0, #0
   0x401148c8:  0e 70 00 ea     b       0x40130908
End of assembler dump.

表達式計算

print expr
	print 」%d」 a
    
dprintf 動態插入printf函數
	dprintf location,format string,arg1,arg2,...

查看寄存器

info registers

查看棧參數

info args

查看局部變量

info locals

查看內存

x

修改內存

set *(unsigned int*)0x800000000=0x00000000

斷點

break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION] clear [LOCATION]

break *0x4000000  絕對地址
break 12          行號
break func1       函數
clear *0x40000000
clear 12
clear func1

一次斷點

tbreak [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION]

條件/線程斷點

break func1 thread1 if i==0

觀察斷點

watch/awatch/ rwatch [-l|-location] EXPRESSION		變化/讀寫/讀斷點
(若是EXPRESSION不是絕對地址,則須要用-l計算表達式)
watch *0x40000000==0x90909090
watch –l *$pc
watch i   (有源碼,變量i的值有變化時中止)

範圍斷點

break-range START-LOCATION, END-LOCATION
break-range 1.c:5, 1.c:10  在1.c的第5行和第10行之間下斷
break-range +5, +10	在當前行+5和當前行+10之間下斷

硬件斷點

普通硬斷 hbreak [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION]
臨時硬斷 thbreak [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION]

攔截當前函數退出

xbreak

捕獲斷點

普通補斷 catch [assert|catch|exception|exec|fork|load|rethrow|signal|syscall|throw|unload|vfork]
臨時捕斷 tcatch [assert|catch|exception|exec|fork|load|rethrow|signal|syscall|throw|unload|vfork]

如何使程序在so加載時刻斷下??(網上很多人用奇葩方式,仍是對gdb不瞭解)
catch load 1.so

跟蹤斷點

strace [LOCATION] [IF CONDITION]

查看調用棧

bt [N] 顯示N層調用棧
bt full 顯示所有調用棧

流程控制

行爲 命令
運行時中斷 Ctrl+C
結束程序 kill
單步步過 next
單步步入 step
單步步過(指令級) nexti
單步步入(指令級) stepi
繼續運行 continue
執行到當前函數指定位置 advance
分離進程 detach
強制跳轉 jump

反向調試

reverse-continue reverse-next reverse-search reverse-stepi reverse-finish reverse-next reverse-step

顯示當前加載模塊

info shared

強制加載模塊

能夠用於作二進制對比

(gdb) load C:/Users/lichao/2/libadnative.so 0x50000000
Loading section .interp, size 0x13 lma 0x50000134
Loading section .dynsym, size 0x1210 lma 0x50000148
Loading section .dynstr, size 0x2061 lma 0x50001358
Loading section .hash, size 0x8a8 lma 0x500033bc
Loading section .rel.dyn, size 0x10a0 lma 0x50003c64
Loading section .rel.plt, size 0x1b0 lma 0x50004d04
Loading section .plt, size 0x29c lma 0x50004eb4
Loading section .text, size 0xe7a0 lma 0x50005150
Loading section .ARM.extab, size 0x8e8 lma 0x500138f0
Loading section .ARM.exidx, size 0xd80 lma 0x500141d8
Loading section .rodata, size 0x11bc lma 0x50014f58
Loading section .data.rel.ro.local, size 0x738 lma 0x50018098
Loading section .fini_array, size 0x8 lma 0x500187d0
Loading section .init_array, size 0x14 lma 0x500187d8
Loading section .data.rel.ro, size 0x508 lma 0x500187f0
Loading section .dynamic, size 0xf8 lma 0x50018cf8
Loading section .got, size 0x210 lma 0x50018df0
Loading section .data, size 0x1c lma 0x50019000
Start address 0x0, load size 94044
Transfer rate: 188 KB/sec, 2541 bytes/write.

替換當前調試模塊

file c:/1.so

進程轉儲

gcore

進程空間

  進程空間inferior,用於調試多個進程,fork函數會自動添加進程空間

操做 指令
添加進程空間 add-inferior
複製進程空間 clone-inferior 1
刪除進程空間 remove-inferior 1
切換進程空間 inferior 2
分離進程空間 detach inferior 2

由地址獲對應的符號

maintenance translate-address [address]

查找符號

info functions [regex]   定位地址
info symbol address      定位文件
info variables [regex]   全局靜態符號

執行外部命令

目標系統:! [command]
主機系統:shell [command]

顯示線程

info threads

打印c++對象虛表

info vtbl

相關文章
相關標籤/搜索