1. 程式人生 > >執行時動態修復dex

執行時動態修復dex

    0x00

    ForceApkObj:用於動態載入的apk。類似於Android中的Apk的加固(加殼)原理解析和實現一文中的ForceApkObj工程。

    FixDex:用於分離ForceApkObj裡面的classes.dex,把他分離為classes_fix.dex和data.so,具體情況我們後來介紹。

    DynamicDex:用於動態載入ForceApkObj工程生成的ForceApkObj.apk,也就是脫殼程式,類似於Android中的Apk的加固(加殼)原理解析和實現一文中的ReforceApk工程。

    0x01

    使用步驟:

    1、將ForceApkObj工程生成的classes.dex拷貝到/sdcard/payload/目錄下。

    2、Run FixDex工程,點選Button按鈕,此時在/sdcard/payload/目錄下生成了classes_fix.dex和data.so。

    3、把classes_fix.dex更名為classes.dex,然後替換ForceApkObj.apk裡面的classes.dex,然後重新簽名,生成新簽名的ForceApkObj.apk。

    4、把ForceApkObj.apk和data.so放入/sdcard/payload/目錄中,執行DynamicDex工程。

    0x02

    ForceApkObj工程很簡單,主MainActivity介面點選螢幕會開啟SubActivity,SubActivity的程式碼如下:

public class SubActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		handleException();
	}

	public void handleException() {
		Toast.makeText(this, "成功對映", Toast.LENGTH_LONG).show();
	}

}
    0x03

    FixDex用於用於分離ForceApkObj裡面的classes.dex,把他分離為classes_fix.dex和data.so。程式碼如下:

			public void onClick(View arg0) {
				int codeoff = FindCode.findCode("Lcom/example/forceapkobj/SubActivity;", "handleException");
				Log.d("jltxgcy", "codeoff:" + codeoff);
				try {
					File file = new File("/sdcard/payload/classes.dex");
					byte[] dexByte = readFileBytes(file);
					String strso = "/sdcard/payload/data.so";
					writeFile(strso, dexByte, codeoff - 16, exceptionCode.length + 16);
					System.arraycopy(exceptionCode, 0, dexByte, codeoff, exceptionCode.length);
					//修改DEX file size檔案頭
					fixFileSizeHeader(dexByte);
					//修改DEX SHA1 檔案頭
					fixSHA1Header(dexByte);
					//修改DEX CheckSum檔案頭
					fixCheckSumHeader(dexByte);
					String str = "/sdcard/payload/classes_fix.dex";
					writeFile(str, dexByte, 0, dexByte.length);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
    首先找到SubActivity類的handleException方法的偏移,這個方法是一個native方法,具體的請參考原始碼,看原始碼前,請先了解apk自我保護的一種實現方式——執行時自篡改dalvik指令。核心的原理我解釋下:
const DexCode  *code =
    	dexFindClassMethod(&gDexFile, className, methodName);
    if(code == NULL){
    	ALOGE("Error can not found setScoreHidden");
    	return 0;
    }
    position = (u4 *)code - (u4 *)dexBase;
    ALOGD("codeoff:%d", ((u4 *)code - (u4 *)dexBase));
    return position * 4 + 16;
    找到handleException在記憶體中的偏移,然後再減去dex頭部的基地址,得到handleException在dex中本地偏移,那為什麼要加上16呢?我們先看一個DexCode的結構。
struct DexCode {
    u2  registersSize;
    u2  insSize;
    u2  outsSize;
    u2  triesSize;
    u4  debugInfoOff;       /* file offset to debug info stream */
    u4  insnsSize;          /* size of the insns array, in u2 units */
    u2  insns[1];
    /* followed by optional u2 padding */
    /* followed by try_item[triesSize] */
    /* followed by uleb128 handlersSize */
    /* followed by catch_handler_item[handlersSize] */
};
    加上16其實就是insns[1]的偏移,也就是真正執行的指令的偏移。

    找到了真正執行的指令的偏移,然後我們把這個指令整個的DexCode拷貝到data.so中。

writeFile(strso, dexByte, codeoff - 16, exceptionCode.length + 16);
    codeoff-16其實就是DexCode的偏移。exceptionCode.length是insns[1]長度(指令的長度),16是registersSize+insSize+outsSize+triesSize+debugInfoOff+insnsSize長度和。

    然後把原來classes.dex中handleException的DexCode中insns[1],也就是真正執行的指令全部置為exceptionCode,也就是全0。這樣如果不動態修復,當執行到這個方法時,就會報錯。

System.arraycopy(exceptionCode, 0, dexByte, codeoff, exceptionCode.length);
    最後把修復後內容寫入到classes_fix.dex中。
					String str = "/sdcard/payload/classes_fix.dex";
					writeFile(str, dexByte, 0, dexByte.length);


    0x03

    把classes_fix.dex更名為classes.dex,然後替換ForceApkObj.apk裡面的classes.dex,然後重新簽名,生成新簽名的ForceApkObj.apk。

    然後把ForceApkObj.apk和data.so放入/sdcard/payload/目錄中,執行DynamicDex工程。

    在Android加殼native實現一文中,我們講述瞭如何在native載入ForceApkObj.apk,並替換原Application,執行ForceApkObj中的主MainActivity。在這裡我們也可以用同樣的方式載入ForceApkObj,執行ForceApkObj中的主MainActivity,但是執行到SubActivity時,由於在SubActivity類的onCreate方法中呼叫handleException方法,而這個方法指令在0x02步已經設定為全零。那麼就會報錯。

    如果想要執行正確,我們有一個思路,也就是程式碼中的實現,如下:

static void nativeParserDex(const char *className, const char *methodName)
{
	void *base = NULL;
	int module_size = 0;
	char filename[512];
	char path[512];
	int fd=-1;
	int r=-1;
	int len=0;
	struct stat st;
	u4 *addr;
	int newCodeOffPosition;
	u1 *store = (u1 *) malloc(2);


	// simple test code  here!
	for(int i=0; i<2; i++){
		sprintf(filename, "/mnt/sdcard/payload_odex/ForceApkObj.dex");

		base = get_module_base(-1, filename);
		if(base != NULL){
			break;
		}
	}

    if(base == NULL){
        ALOGE("Can not found module: %s", filename);
        return ;
    }

    module_size = get_module_size(-1, filename);

	// search dex from odex
	void *dexBase = searchDexStart(base);
	ALOGD("found dex start[%p]", dexBase);
	if(checkDexMagic(dexBase) == false){
		ALOGE("Error! invalid dex format at: %p", dexBase);
		return ;
	}

	DexHeader *dexHeader = (DexHeader *)dexBase;

    gDexFile.baseAddr   = (u1*)dexBase;
    gDexFile.pHeader    = dexHeader;
    gDexFile.pStringIds = (DexStringId*)((u4)dexBase+dexHeader->stringIdsOff);
    gDexFile.pTypeIds   = (DexTypeId*)((u4)dexBase+dexHeader->typeIdsOff);
    gDexFile.pMethodIds = (DexMethodId*)((u4)dexBase+dexHeader->methodIdsOff);
    gDexFile.pFieldIds  = (DexFieldId*)((u4)dexBase+dexHeader->fieldIdsOff);
    gDexFile.pClassDefs = (DexClassDef*)((u4)dexBase+dexHeader->classDefsOff);
    gDexFile.pProtoIds  = (DexProtoId*)((u4)dexBase+dexHeader->protoIdsOff);


    //dumpDexHeader(dexHeader);
    //dumpDexStrings(&gDexFile);
    //dumpDexTypeIds(&gDexFile);
    //dumpDexProtos(&gDexFile);
    //dumpFieldIds(&gDexFile);
    //dumpClassDefines(&gDexFile);


    // 2. found Dex Class!
    const DexCode  *code =
    	dexFindClassMethod(&gDexFile, className, methodName);
    if(code == NULL){
    	ALOGE("Error can not found setScoreHidden");
    	return ;
    }
    codeOffPosition = ((u4 *)code - (u4 *)dexBase) * 4;
    ALOGD("codeOffPosition:%d", codeOffPosition);
    dexFindClassData(&gDexFile, className);
    u1 *codeData = (u1 *)codeOffPoint;
    ALOGD("codeOffPoint:%p,codeOffPointByte:%d,codeOffPointData:%d,%d", codeOffPoint, codeOffPointByte, *codeData,*(codeData + 1));
    sprintf(path, "/mnt/sdcard/payload_odex/data.so");
    fd = open(path,O_RDONLY,0666);
	if (fd==-1) {
		return ;
	}

	r=fstat(fd,&st);
	if(r==-1){
		close(fd);
		return ;
	}

	len=st.st_size;
	addr=(u4 *)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
	ALOGD("addr:%p", addr);
	newCodeOffPosition = ((u4 *)addr - (u4 *)dexBase) * 4;
	writeLeb128(store, newCodeOffPosition);
	ALOGD("newCodeOffPosition:%d, store:%d,%d", newCodeOffPosition, *store,*(store + 1));
	ALOGD("mprotect in");
	*codeData = *store;
	codeData++;
	*codeData = *(store + 1);
	ALOGD("codeOffPointData:%d,%d", *(codeData-1),*(codeData));
}
    還記得我們在0x02步提取的data.so,實際上就是原來的handleException方法的DexCode結構體內容。我們只要找到目前指向DexCode結構體(當前方法指令置為全零)指標codeOff,我們再把data.so對映到記憶體,讓codeOff指向這個地址,就完成了動態修復。因為這時候還沒有loadClass,所以在此之前修復,loadClass就能形成正確的ClassObject,從而正確的執行指令。

    講的是大概的原理,大家參考原始碼多看看就能理解,如果有不懂,請留言。我在Android 2.3模擬器試驗成功!