Android代码入侵原理解析(一)
2017年初,在滴滴安全沙龍上,滴滴出行安全專家——付超紅,針對App的攻與防進行了分享。會后大家對這個議題反響熱烈,紛紛求詳情求關注。現在,付超紅詳細整理了《Android代碼入侵原理解析》,在滴滴安全應急響應中心的微信公眾號開始連載。技術干貨滿滿,敬請關注。
1.代碼入侵原理
代碼入侵,或者叫代碼注入,指的是讓目標應用/進程執行指定的代碼。代碼入侵,可以在應用進行運行過程中進行動態分析,也是對應用進行攻擊的一種常見方式。我把代碼入侵分為兩種類型:靜態和動態。靜態代碼入侵是直接修改相關代碼,在應用啟動和運行之前,指定代碼就已經和應用代碼關聯起來。動態代碼入侵是應用啟動之后,控制應用運行進程,動態加載和運行指定代碼。
2.靜態代碼入侵
靜態代碼入侵,有直接和間接的手段。
直接手段是修改應用本身代碼。修改應用本身代碼,在Android和iOS移動操作系統上,一般利用重打包的方式來完成。攻擊者需要對應用安裝包文件,完成解包、插入指定代碼、重打包的三個步驟?,F在用到的代碼插樁技術和這個比較類似,只不過是代碼注入的工作直接在編譯過程中完成了。
間接手段是修改應用運行環境。關于應用運行環境,可以是修改和替換關鍵系統文件,如xposed通過修改應用啟動的系統文件 /system/bin/app_process 實現代碼注入??梢栽斐鲆惶啄M的系統運行環境,如應用運行沙箱、應用雙開器等。對于Android系統,可以自行修改系統編譯rom。
3.動態代碼入侵
這里以Android系統為例,說明動態代碼入侵的整個過程(單指代碼注入,不包括后續控制邏輯的實現)。動態代碼入侵需要在應用進程運行過程中,控制進程加載和運行指定代碼。控制應用進程,我們需要用到ptrace。ptrace是類unix系統中的一個系統調用,通過ptrace我們可以查看和修改進程的內部狀態,能夠修改目標進程中的寄存器和內存,實現目標進程的斷點調試、監視和控制。常見的調試工具如:gdb, strace, ltrace等,這些調試工具都是依賴ptrace來工作的。
關于ptrace,可以參考維基百科的說明:https://en.wikipedia.org/wiki/Ptrace
long ptrace(int request, pid_t pid, void *addr, void *data);
pid:?? 目標進程
addr:? 目標地址
data: ?操作數據
request:
PTRACE_ATTACH
PTRACE_DETACH
PTRACE_CONT
PTRACE_GETREGS
PTRACE_SETREGS
PTRACE_POKETEXT
PTRACE_PEEKTEXT
ptrace的功能主要是以下:
1)進程掛載
2)進程脫離
3)進程運行
4)讀寄存器
5)寫寄存器
6)讀內存
7)寫內存
進程被掛載后處于跟蹤狀態(traced mode),這種狀態下運行的進程收到任何signal信號都會停止運行。利用這個特性,可以很方便地對進程持續性的操作:查看、修改、確認、繼續修改,直到滿足要求為止。進程脫離掛載后,會繼續以正常模式(untraced mode)運行。
3.1 動態代碼注入的步驟
1)掛載進程
2)備份進程現場
3)代碼注入
4)恢復現場
5)脫離掛載
其中,代碼注入的過程相對復雜,因為代碼注入過程和cpu架構強相關,需要先了解Android系統底層的ARM架構。
3.2 ARM架構簡介
ARM處理器在用戶模式和系統模式下有16個公共寄存器:r0~r15。
有特殊用途的通用寄存器(除了做通用寄存器,還有以下功能):
r0~r3: 函數調用時用來傳遞參數,最多4個參數,多于4個參數時使用堆棧傳遞多余的參數。其中,r0還用來存儲函數返回值。
r13:堆棧指針寄存器sp。
r14:鏈接寄存器lr,一般用來表示程序的返回地址。
r15:程序計數器pc,當前指令地址。
狀態寄存器cpsr:
N=1:負數或小于(negtive)
Z=1:等于零(zero)
C=1:有進位或借位擴展
V=1:有溢出
I=1:IRQ禁止interrupt
F=1:FIQ禁止fast
T=1/0:Thumb/ARM狀態位
其中,T位需要注意。程序計數器pc(r15)末位為1時T位置1,否則T位置0。
代碼動態注入過程中,前面的準備和后面的收尾工作比較簡單,較復雜的是中間的代碼注入。整體過程的基礎代碼如下:
3.3 代碼注入過程
代碼注入需要完成在目標進程內加載和運行指定代碼。指定代碼的一般形式是so文件。動態加載so需要使用到linker提供的相關方法。關于linker,請閱讀《程序員的自我修養-鏈接、裝載與庫》。具體來說,代碼注入過程分為三步,也就是三次函數調用:
1)dlopen加載so文件
2)dlsym獲取so的入口函數地址
3)調用so入口函數
和正常調用函數相比,通過ptrace在目標進程中調用函數是完全不同的,是通過直接修改寄存器和內存數據來實現函數調用。具體來說,有幾點需要注意:
1)獲取函數地址
調用函數首先要知道函數地址。因為ASLR(地址空間格局隨機化,Address Space Layout Randomization)的影響,父進程孵化子進程時,系統動態庫的基地址會隨機變化,具體表現為,相同的系統動態庫在不同子進程中的內存地址是不同的。我們可以利用下面的簡單公式來計算得到我們需要用到的相關函數在目標進程中的地址:address = base + offset
其中,base是函數實現所在動態庫的基地址,offset是函數在動態庫中的偏移地址。
在Linux系統中,可以通過/proc/<pid>/maps查看進程的虛擬地址空間(查看非當前進程需要root權限),包括進程的所有動態庫的base。通過動態庫文件名查詢虛擬地址空間獲取base。offset值是函數地址在動態庫中的偏移,可以直接靜態查看動態庫文件獲得函數偏移地址,也可以在其他應用運行時計算得出:offset = address - base。具體到代碼注入,需要用到的函數dlopen和dlsym,其實現代碼所在文件為 /system/bin/linker(為什么開發過程中使用dlopen、dlsym, 編譯時鏈接的是文件libdl.so,運行時鏈接的卻是另外一個文件 /system/bin/linker,這里不做詳述)。
2)函數調用參數傳遞和返回值獲取
對于ARM體系來說,函數調用遵循的是 ATPCS(ARM-Thumb Procedure Call Standard),ATPCS建議函數的形參不超過4個,如果形參個數少于或等于4,則形參由R0、R1、R2、R3四個寄存器進行傳遞;若形參個數大于4,大于4的部分必須通過堆棧進行傳遞。函數調用的返回值通過R0傳遞。
3) 內存分配/獲取
像字符串類型這樣的參數運行時需要占用內存。當然我們可以通過調用malloc來動態申請內存。但是,正如之前介紹的,通過ptrace進行函數調用的過程有些復雜。我們直接使用棧的內存空間更加方便。通過棧指針sp,我們將數據放到棧暫時不用的內存空間,也能省去釋放內存空間的繁瑣。
4)函數調用后重獲控制權
代碼注入需要多次函數調用,我們希望調用第一個函數之后,進程馬上停下來,等待后續其他的函數調用。這里,我們需要使用到lr寄存器。通過設置lr為非法地址(一般設為0),可以使得函數返回時出錯,觸發非法指令的signal信號,進程停止。然后,我們可以重設進程狀態,執行后面其他的函數調用。
總結
以上是生活随笔為你收集整理的Android代码入侵原理解析(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Activity到底是什么时候显示到屏幕
- 下一篇: Android libcutils库中整