繼續
在《一》裡,我把基本思路描述了一遍,接下為我們先從注入開始入手。
注入
分類
我們平時所說的代碼注入,主要靜态和動态兩種方式
- 靜态注入,針對是可執行檔案,比如平時我們修改ELF,DEX檔案等等,相關的輔助工具也很多,比如IDA、JEB、ApkTool等等;
- 動态注入,針對是程序,比如修改程序的寄存器、記憶體值等等;
動态跟靜态最大的差別是,動态不需要改動源檔案,但需要高權限(通常是root權限),而且所需的技術含量更高。
本質
動态注入技術,本質上就是一種排程技術。想想平時我們調試一個程序時,可以做哪些功能? 一般有下列幾項:
- 檢視變量值
- 修改變量值
- 跟蹤程序跳轉
- 檢視程序調用堆棧
- 等等
動态注入相比于普通的調試,最大的差別就是動态注入是一個 ”自動化調試并達到加載自定義動态連結庫“的過程。所謂自動化,其實就是通過代碼實作,在Linux上通過Ptrace就可以完成上面所有功能,當然Ptrace功能是比較原始的,平時調試中的功能還需要很多高層邏輯封裝才可以實作。 在閱讀下面章節之前,強烈建議閱讀一下man文檔,見 這裡。
目的
一般而言,我們要對一個程序進行注入,主要有以下幾方面目的:
- 增強目标程序的功能;
- 修複目标程序缺陷;
- 劫持目标程序函數;
- 竊取目标程序資料;
- 篡改目标程序資料;
過程
如上圖所示,程序A注入到程序B後,通過修改寄存器和記憶體,讓程序B加載自定義的動态庫a,當a被加載後,a會嘗試加載其他子產品,比如加載dex檔案等等,具體的注入過程如下:
- ATTATCH,指定目标程序,開始調試;
- GETREGS,擷取目标程序的寄存器,儲存現場;
- SETREGS,修改PC等相關寄存器,使其指向mmap;
- POPETEXT,把so path寫入mmap申請的位址空間;
- SETRESG,修改PC等相關寄存器,使其指向dlopen;
- SETREGS,恢複現場;
- DETACH,解除調試,使其恢複;
上述是一個簡化的過程,整個注入的代碼,我已經上傳到github,位址https://github.com/boyliang/Poison 當so被dlopen加載到目标程序後,我們需要讓so中的邏輯被執行,比較複雜的做法是同樣使用ptrace修改寄存器的辦法,讓目标程序調用dlsym找到我們函數的位址。而比較簡單的做法有兩種,如下
- 使用gcc的預編譯指令__attribute__ ((__constructor__)),作用是讓so被加載後,函數被自動執行;
__attribute__ ((__constructor__))
void Main() {
LOGI(">>>>>>>>>>>>>I am in, I am a bad boy 1!!!!<<<<<<<<<<<<<<");
void* handle = dlopen("libinso.so", RTLD_NOW);
void (*setA_func)(int) = (void (*)(int))dlsym(handle, "setA");
if (setA_func) {
setA_func(999);
}
}
- 使用c++全局對象初始化,其構造函數會被自動執行;
void Main();
static void* _main(void*){
Main();
return NULL;
}
class EntryClass {
public:
EntryClass() {
pthread_t tid;
pthread_create(&tid, NULL, _main, NULL);
pthread_detach(tid);
}
} boy;
示例一
下面示例一個通過ptrace注入的示例,涉及到兩部分代碼,一部分是目标程序代碼記作host,另一部分是被我們注入的so代碼記作libmyso.so
Host代碼
包含三個源檔案,分别是demo1.c,inso.h, inso.c
/*
* inso.h
*
* Created on: 2014年6月24日
* Author: boyliang
*/
__attribute__ ((visibility ("default"))) void setA(int i);
__attribute__ ((visibility ("default"))) int getA();
/*
* inso.c
*
* Created on: 2014年6月24日
* Author: boyliang
*/
#include <stdio.h>
#include "inso.h"
static int gA = 1;
void setA(int i){
gA = i;
}
int getA(){
return gA;
}
/*
* demo1.c
*
* Created on: 2014年6月24日
* Author: boyliang
*/
#include <stdio.h>
#include <unistd.h>
#include "inso.h"
#include "log.h"
int main(){
LOGI("DEMO1 start.");
while(1){
LOGI("%d", getA());
setA(getA() + 1);
sleep(2);
}
return 0;
}
libmyso.so代碼
/*
* myso.c
*
* Created on: 2014年6月24日
* Author: boyliang
*/
#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stddef.h>
#include "log.h"
__attribute__ ((__constructor__))
void Main() {
LOGI(">>>>>>>>>>>>>I am in, I am a bad boy 1!!!!<<<<<<<<<<<<<<");
void* handle = dlopen("libinso.so", RTLD_NOW);
void (*setA_func)(int) = (void (*)(int))dlsym(handle, "setA");
if (setA_func) {
setA_func(999);
}
}
調用
注入程式,我将其命名為poison,使用方法是poison <so_path> <target_pit>。下面是示例的輸出顯示:
I/TTT ( 594): DEMO1 start.
I/TTT ( 594): 1
I/TTT ( 594): 2
I/TTT ( 594): 3
I/TTT ( 594): 4
I/TTT ( 594): 5
I/TTT ( 594): 6
I/TTT ( 594): 7
I/TTT ( 594): >>>>>>>>>>>>>I am in, I am a bad boy 1!!!!<<<<<<<<<<<<<<
I/TTT ( 594): 999
I/TTT ( 594): 1000
I/TTT ( 594): 1001
當執行./poison /data/local/tmp/libmyso.so 594後,輸出中馬上出現了特定字元串,并且列印的資料一下子變成了999,證明我們注入成功了。
示例代碼
上述示例所涉及到代碼,我都放釋出到github上了,大家如果想研究代碼,可以到https://github.com/boyliang/injection_by_ptrace 在 《三》,我會再介紹一種Android上特有的注入技術,敬請期待。