天天看點

進擊的Android注入術《二》繼續注入示例代碼

繼續

在《一》裡,我把基本思路描述了一遍,接下為我們先從注入開始入手。

注入

分類

我們平時所說的代碼注入,主要靜态和動态兩種方式

  • 靜态注入,針對是可執行檔案,比如平時我們修改ELF,DEX檔案等等,相關的輔助工具也很多,比如IDA、JEB、ApkTool等等;
  • 動态注入,針對是程序,比如修改程序的寄存器、記憶體值等等;

動态跟靜态最大的差別是,動态不需要改動源檔案,但需要高權限(通常是root權限),而且所需的技術含量更高。

本質

動态注入技術,本質上就是一種排程技術。想想平時我們調試一個程序時,可以做哪些功能? 一般有下列幾項:

  • 檢視變量值
  • 修改變量值
  • 跟蹤程序跳轉
  • 檢視程序調用堆棧
  • 等等

動态注入相比于普通的調試,最大的差別就是動态注入是一個 ”自動化調試并達到加載自定義動态連結庫“的過程。所謂自動化,其實就是通過代碼實作,在Linux上通過Ptrace就可以完成上面所有功能,當然Ptrace功能是比較原始的,平時調試中的功能還需要很多高層邏輯封裝才可以實作。 在閱讀下面章節之前,強烈建議閱讀一下man文檔,見 這裡。

目的

一般而言,我們要對一個程序進行注入,主要有以下幾方面目的:

  • 增強目标程序的功能;
  • 修複目标程序缺陷;
  • 劫持目标程序函數;
  • 竊取目标程序資料;
  • 篡改目标程序資料;
進擊的Android注入術《二》繼續注入示例代碼

過程

如上圖所示,程序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上特有的注入技術,敬請期待。