### **概述**
Android應用程序是運行在一個沙箱中。這個沙箱是基于Linux內核提供的用戶ID(UID)和用戶組ID(GID)來實現的。Android應用程序在安裝的過程中,安裝服務PackageManagerService會為它們分配一個唯一的UID和GID,以及根據應用程序所申請的權限,賦予其它的GID。有了這些UID和GID之后,應用程序就只能限訪問特定的文件,一般就是只能訪問自己創建的文件。此外,Android應用程序在調用敏感的API時,系統檢查它在安裝的時候會沒有申請相應的權限。如果沒有申請的話,那么訪問也會被拒絕。對于有root權限的應用程序,則不受上述沙箱限制。此外,有root權限的應用程序,還可以通過Linux的ptrace注入到其它應用程序進程,以及系統進程,進行各種函數調用攔截。
本系列主要講代碼加殼、注入和攔截技術的,包括:
1. SO注入。也就是從一個進程向另外一個進程注入一個SO文件,通過該注入的SO文件就可以實現函數攔截功能。
2. SO加殼。加殼的目的自然就是加大別人對自己的C/C++代碼進行靜態逆向難度了,這個技術的關鍵是要實現一個能純內存操作的Linker了。也就是說,解密后的SO文件內容是保存在一個內存緩沖區的,然后再針對該內存緩沖區進行解析和鏈接,最終形成一段可執行的代碼。這個過程不會產生任何文件供別人做靜態分析。
3. C/C++函數GOT攔截。通過修改SO的GOT項來實現函數攔截。這個技術的特點是簡單和穩定,但是不足之處于它是針對函數的調用方進行攔截的,而不是針對函數本身的實現來進行攔截的。這樣當我們想對某一個函數進行攔截的時候,就必須要檢查進程內所有的模塊,然后對調用了目標函數的模塊的相關GOT 項進行修改。此外,如果某一個模塊是通過動態SO加載技術(dlopen、dlsym)來調用目標函數的話,GOT攔截就失效了,因為動態SO加載技術不會產生GOT項。
4. C/C++函數INLINE攔截。這種方法是直接對目標函數的前面幾條指令進行修改,用來實現攔截技術。INLINE攔截沒有上述GOT攔截的缺點,但是它的實現會復雜很多。由于絕大部分Android設備都是基于ARM架構,因此這里只討論ARM架構的C/C++函數INLINE攔截。ARMl架構主要分為ARM和THUMB兩種指令集,也就是在Android設備上運行的C/C++函數分為ARM和THUMB兩種類型。對于ARM指令集的函數,對它們進行攔截至少需要修改頭8個字節;對于THUMB指令集,對它們進行攔截至少需要修改頭12個字節。無論ARM指令還是THUMB指令函數,我們要修改的頭8個字節或者12個字節都很容易碰到跳轉或者PC相對尋址指令,這樣就需要對指令進行重定位。這個重定位工作相當于繁重和麻煩,得實現一個ARM和THUMB指令解析庫才行。不像X86的函數INLINE攔截,只需要函數的頭5個字節即可,而且這5個字節幾乎都是堆棧相關的操作,不會涉及到跳轉或者PC相對尋址指令。
5. DEX注入。在SO注入的基礎上,要對目標進程進行DEX注入是相當簡單的,通過DexClassLoader即可實現。
6. DEX加殼。DEX加殼與SO加殼一樣,都要求在解密之后,能夠進行純內存操作,中間不要產生任何和DEX或者ODEX文件,否則的話,就會給別提供靜態分析的機會,這樣就失去了加殼的目的。
7. Java函數攔截。與C/C++函數攔截相對,Java函數攔截要優雅得多,因為所有的Java函數都是通過虛擬機來執行的。Dalvik虛擬機執行的函數分為Java和Native兩種,它們都是使用Method結構體來描述。當一個Method結構體描述的是一個Java函數時,它有一個成員變量就指向該Java函數的方法區。而當一個Method結構體描述的是一個Native函數,它有一個成員變量指向該Native函數的地址。因此,主要我們能將一個用來描述Java函數的Method結構體修改為一個指向Native函數的Method結構體,就可以騙過Dalvik虛擬機來執行我們所指定的Native函數,從而實現攔截。
以上7個技術點涵蓋了Android安全的攻與防基礎。在這些基礎上不僅可以保護我們自己的代碼,還可以對別人的代碼進行攻擊。
* Android安全模型
* SO注入技術
* SO加殼技術
* C/C++函數攔截技術
* DEX注入技術
* DEX加殼技術
* Java函數攔截技術
#### **Android安全模型**


**用戶**
* 系統中可以存在多個用戶,每一個用戶都具有一個UID
* 用戶按組劃分形成用戶組,每一個用戶組都具有一個GID
* 一個用戶可以屬于多個用戶組
**文件**
* 每一個文件都具有三種權限
Read、Write、Execute
* 文件權限按用戶屬性分為三組
Owner、Group、Other

**進程**
* UID -- setuid
* GID -- setgid
* Supplementary GIDS – setgroups
* Capabilities -- capset

* 系統中的第一個進程Init的UID是root
* 子進程的UID默認與父進程相同,但可以通過setuid進行修改
* 子進程被fork之后exec了一個設置了SUID位的bin文件,那么子進程的UID變為該bin文件的Ower UID


**每一個APK在安裝的時候,PMS都會給它分配一個唯一的UID和GID**
* 如果兩個APK具有相同的簽名,那么可以通過android:sharedUserId申請分配相同的UID和GID
* 如果一個APK具有平臺簽名,那么可以通過android:sharedUserId=“android.uid.system”獲得System UID
> **備注**
> 通過Master Key漏洞獲得System UID
> http://drops.wooyun.org/papers/219
> http://safe.baidu.com/2013-10/android-masterkey-9695860.html
> http://safe.baidu.com/2013-11/masterkey-9950697.html
> 打開文件/data/local.prop,設置以下屬性:
> `ro.kernel.qemu=1`
> 即可使得adb具有root權限
**每一個APK都可以通過<uses-permission android:name=“android.permission.XXX”/>申請若干個Permission**
* 有些Permission需要具有平臺簽名才可以申請,如INSTALL_PACKAGES
**每一個Permission都對應于一個Supplementary GID,因此,給APK分配Permission即為APK分配Supplementary GID**
http://developer.android.com/reference/android/Manifest.permission.html
**APK進程是由UID為root的Zygote進程fork出來的,fork之后**:

http://man7.org/linux/man-pages/man2/capset.2.html
**PMS記錄有每一個APK所申請的Permission,當APK調用敏感API時,相應的模塊就會通過PMS會驗證調用APK是否申請有相應的Permission**

**突破沙箱**

* **突破沙箱:創建其它APK也能訪問的文件**

* **突破沙箱:Binder IPC**

* **突破沙箱:Content Provider**

* **突破沙箱:黑客技術**
* SO注入
- C/C++函數攔截
- DEX注入
- Java函數攔截
- ……
#### **SO注入技術**
**ptrace**
~~~
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
~~~
http://man7.org/linux/man-pages/man2/ptrace.2.html

* Step 1: PTRACE_ATTACH到目標進程,并且讓目標進程發生PTRACE_SYSCALL時停止
* Step 2: PTRACE_GETREGS保存目標進程的上下文
* Step 3: PTRACE_SETREGS改寫目標進程的PC寄存器,使得它指向函數mmap的地址
* Step 4: PTRACE_CONT讓目標進程恢復執行,這時候將會執行函數mmap
* Step 5: PTRACE_GETREGS獲得目標進程的R0寄存器值,即為函數mmap的返回值,指向在目標進程地址空間分配的一塊內存
* Step 6: PTRACE_POKETEXT往在目標進程分配的地址寫入以下一段SHELL CODE

* Step 7: PTRACE_SETREGS改寫目標進程的PC寄存器,使得它指向上述SHELL CODE的起始地址_inject_start_s
* Step 8: PTRACE_DETTACH目標進程,目標進程恢復執行后,就會執行注入的
* Step 9: 注入的SHELL CODE在目標進程加載一個SO,并且找到這個SO的指定入口函數,進行調用
#### **SO加殼技術**
**系統中的SO文件由一個叫Linker的加載器負責加載,即調用dlopen函數進行加載**:
~~~
void *dlopen(const char *filename, int?flag);
~~~
如果能將第一個參數改為一個內存地址,就可以實現從內存加載SO的功能,進而可以對該內存進行加密處理
**dlopen**

**find_library**

**load_library**

**Read Elf32_Ehdr**
Elf32_Ehdr header[1];
read(fd.fd, (void*)header, sizeof(header))
http://man7.org/linux/man-pages/man5/elf.5.html
**ReadElf32_Phdr**

**Reserve Enough Memory**

**Load Segments**

**What data we need?**
Elf32_Ehdr
Elf32_Phdr
Segments
**How to fill above data? **

**struct elfinfo**

**Open file and create elfinfo**

**Read Elf32_Ehdr**

**Read Elf32_Phdr**

**Read segments**

#### **C/C++函數攔截技術**
* Got Hook
* VTable Hook
* Inline Hook
**Got Hook**

* **Step 1: Find the address of eglSwapBuffers**
~~~
void * handle = dlopen(“/system/lib/libEGL.so”, RTLD_NOW)
void* addr = dlsym(handle, “eglSwapBuffers”);
~~~
* **Step 2: Find the .got section **

* **Step 3: Find the address of eglSwapBuffers in .got section**

* **Step 4: Replace it**

**VTable Hook**

* **Step 1: Find the address of Surface::unlockAndPost **
~~~
void * handle = dlopen(“/system/lib/libgui.so”, RTLD_NOW)
void* addr = dlsym(handle,“_ZN7android7Surface13unlockAndPostEv”);
~~~
* **Step 2: Find the .data.rel.ro section**

* **Step 3: Find the address of Surface::unlockAndPost in .data.rel.ro section **
、
* **Step 4: Replace it**

**Inline Hook**

* **Problem**
移動的指令可能包含:
**普通指令**
* PC相對尋址指令
* 跳轉指令
**對于PC相對尋址和跳轉指令**:
* 需要進行重定位
**因此,實現Inline Hook要求**:
* 動態的指令解析
* 動態的指令重定位
#### **DEX注入技術**


#### **DEX加殼技術**
* 通過DexClassLoader可以動態地加載DEX文件,但是它在加載DEX文件的過程會生成一個ODEX文件,給別人提供了靜態逆向的可能
* 通過分析DexClassLoader的實現可以知道,它是通過DexFile來實現動態加載DEX文件的
* 進一步分析DexFile的實現,發現它提供了兩個隱藏接口來實現加載內存DEX文件


* 但是,DexFile從Android 4.0開始才支持加載內存DEX文件,如何支持Android 4.0以下的版本呢?
* 通過分析DexFile加載內存DEX文件的實現可以發現,里面用到的關鍵函數都可以從libdex.a和libdvm.so獲得
* 于是,可以模仿Android 4.0,實現DexFile加載內存DEX文件的功能
* 輔助數據結構和函數

* **Step 1: custome_load_class_from_memory**

* **Step 2: open_dex_from_memory**

* **Step 3: raw_dex_file_open_array**

* **Step 4: prepare_dex_in_memory**

* **Step 5: rewrite_dex **

* **Step 6: dex_file_open_partial**

* **Step 7: allocate_aux_structures**

* **Step 8: add_to_dex_file_table**

* 上述過程用到的關鍵函數均可從libdex.a和libdvm.so獲得:
* dexSwapAndVeriry
* dexCreateClassLookup
* dexFileParse
* dvmAllocRegion
* dvmAllocAtomicCache
* dvmHashTableLock
* dvmHashTableLookup
* dvmHashTableUnlock
#### **Java函數攔截技術**
* 在Dalvik虛擬機中,無論是Java函數,還是Native函數,都是通過Method結構體來描述的

* Dalvik虛擬機通過dvmIsNativeMethod判斷一個函數是Java函數還是Native函數

* Dalvik虛擬調用一個函數之前,首先判斷它是Java函數還是Native函數

* Java函數攔截技術原理分析
* 對于Java函數,Davik虛擬機使用解釋器來執行
* 對于Native函數, Davik虛擬機找到它的函數指針nativeFunc,進行直接調用
* 如果我們能把一個Java函數修改為Native函數,并且將nativeFunc指針設置為自定義的函數,那么就可以實現攔截了
* 攔截完成之后,根據情況決定是否需要調用原來的Java函數,即可完成整個攔截過程
* libdvm導出了兩個函數dvmDecodeIndirectRef和dvmSlotToMethod,如果我們知道一個Java函數在它所屬的Class里面的位置Slot,那么就可以通過它們獲得該Java函數在Dalvik虛擬內部所對應的Method結構體:

* 得到一個Java函數在Dalvik虛擬內部所對應的Method結構體之后,就可以將它設置為Native函數:

**如何獲得一個Java函數所屬的Class,以及它在該Class的位置Slot呢?**
**假設我們知道:**
* Java函數的名稱—methodName
* Java函數的原型—prototype
* Java函數的類名稱—className
* 用來加載該Java類的ClassLoader--classLoader
**Step 1: 獲得Class對象**
~~~
Class<?> clazz = classLoader.loadClass(className);
~~~
**Step 2: 獲得Method對象**
~~~
Method method = clazz.getDeclaredMethod(methodName, prototype);
~~~
**Step 3: 獲得clazz的Slot域描述**
~~~
Field field = clazz.getDeclaredField(“Slot”);
~~~
**Step 4: 獲得method的slot**
~~~
int slot= field.getInt(method);
~~~
**Step 5: 將clazz和slot通過JNI傳遞到C/C++層,調用dvmDecodeIndirectRef和dvmSlotToMethod**
~~~
ClassObject* declared_classs = (ClassObject*) dvmDecodeIndirectRef(dvmThreadSelf(), clazz);
Method* method = dvmSlotToMethod(declared_class, slot);
~~~
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析