執行時動態修復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模擬器試驗成功!