学起来~
修复的方法简单点可以直接替换回去,如果复杂点可以在dex文件的范围外new一块区域,然后将原本指向这部分指令的指针指向new出来的区域(也就是在dex外部),这样即便你通过动态分析对dvmDexFileOpenPartial这样的API下断能dump出dex文件,还是没有办法正确反编译,因为正确的指令还是没有加入进来。
这个系列本来题目想写对抗反编译,可是想想对抗反编译的这个范围有点大,总结如下
先来看灵魂作图~
废话少说,开始吧,最近又把脱壳拿出来看了,就从这里开始吧
整个过程分为两个阶段
第一阶段:提取、替换正确的dex中的指令
第二阶段:运行时修复替换之后错误的dex文件
1、定位——提取——替换
首先,在dex中定位code的位置,我们看看android源码是怎么写的
源码位置dalvik\libdex\dexFile.h
* Direct-mapped “code_item”.
*
* The “catches” table is used when throwing an exception,
* “debugInfo” is used when displaying an exception stack trace or
* debugging. An offset of zero indicates that there are no entries.
*/
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] */
};
~~~~~~~~~~~~~~
该函数寄存器的数目
传入的参数个数
调用其他函数时需要的参数个数
try结构的数目
debug信息的偏移
指令列表大小
该函数的指令
~~~~~~~~~~~~~~
看注释,这个结构我们一般不叫它DexCode,而是code_item(关于各种item,在深入Androguard源码中有解释)
而指向它的指针,位于encoded_method结构中的codeoff(源码位置dalvik\libdex\DexClass.h)
struct DexMethod {
u4 methodIdx; /* index to a method_id_item */
u4 accessFlags;
u4 codeOff; /* file offset to a code_item */
};
* absent (e.g., no static fields), then the corresponding pointer
* is set to NULL. */
struct DexClassData {
DexClassDataHeader header;
DexField* staticFields;
DexField* instanceFields;
DexMethod* directMethods;
DexMethod* virtualMethods;
};
* Direct-mapped “class_def_item”.
*/
struct DexClassDef {
u4 classIdx; /* index into typeIds for this class */
u4 accessFlags;
u4 superclassIdx; /* index into typeIds for superclass */
u4 interfacesOff; /* file offset to DexTypeList */
u4 sourceFileIdx; /* index into stringIds for source file name */
u4 annotationsOff; /* file offset to annotations_directory_item */
u4 classDataOff; /* file offset to class_data_item */
u4 staticValuesOff; /* file offset to DexEncodedArray */
};
我们通过dexClassLoader将源APK加载到内存中,通过cat /proc/self/maps 可以查看当前进程的内存结构,找到源APK的dex文件在内存中的起始位置和终止位置。不过这个内存中的dex是并不是初始的dex文件了,是优化后的odex文件(安利一下,优化过程可见我的博客优化过程分析 和加载过程分析,对于dex和odex结构的区别,可见dex和odex的比较实验,安利完成)。适当偏移之后,才是dex文件的开头位置。然后根据前述的结构,找到指定函数的指令位置,将指令拷贝到一个其他文件中,然后将这部分指令修改,比如改为0x23 0x33 0x33……
2、修复
修复时,壳程序同样先通过dexClassLoader加载APK(此时源APK已被修改,是无法正确执行的),按之前的步骤找到目标函数指令。再将保存正确指令的文件mmap到内存中。然后将指令直接替换;或者将指向错误DexCode 的codeOff改为mmap返回的地址位置
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
TextView context = new TextView(this);
context.setText(EncryptString.encrypt(“Hello,World”));
setContentView(context);
}
}
DexClassLoader dexClassLoader = new DexClassLoader(“/data/local/tmp/TestApk.apk”,
path, null, getClassLoader());
public class FindCode {
static {
System.loadLibrary(“findcode”);
}
public FindCode() {
}
public native int findCode(String dexName, String className, String methodName);
}
(JNIEnv *env, jobject clazz, jstring dexName, jstring className, jstring methodName) {
const char *dexNameLocal;
const char *classNameLocal;
const char *methodNameLocal;
dexNameLocal = env->GetStringUTFChars(dexName, JNI_FALSE);
classNameLocal = env->GetStringUTFChars(className, JNI_FALSE);
methodNameLocal = env->GetStringUTFChars(methodName, JNI_FALSE);
return nativeParserDex(dexNameLocal, classNameLocal, methodNameLocal);
}
首先要定位内存中TestApk.dex的位置
{
FILE *fp;
long addr = 0;
char *pch;
char filename[32];
char line[1024];
if ( pid < 0 ){
/* self process */
strcpy(filename, “/proc/self/maps”);
}else{
snprintf( filename, sizeof(filename), “/proc/%d/maps”, pid);
}
fp = fopen( filename, “r” );
if ( fp != NULL ){
while ( fgets( line, sizeof(line), fp ) ){
if ( strstr( line, module_name) ){
pch = strtok( line, “-” );
addr = strtoul( pch, NULL, 16 );
if ( addr == 0x8000 ) addr = 0;
break;
}
}
fclose( fp ) ;
}
return (void *)addr;
}
ALOGD(“found dex start[%p]”, dexBase);
if(checkDexMagic(dexBase) == false){
ALOGE(“Error! invalid dex format at: %p”, dexBase);
return 0;
}
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;
下面是dexFindClassMethod函数的代码
{
ALOGD(“finding: %s->%s”, clazz, method);
DexClassData* classData = dexFindClassData(dexFile, clazz);
if(classData == NULL) return NULL;
const DexCode* code = dexFindMethodInsns(dexFile, classData, method);
if(code != NULL) {
dumpDexCode(code);
}
dumpDexClassDataMethod(dexFile, classData);
ALOGD(“found[%p]: %s->%s”, code, clazz, method);
return code;
}
首先找到对应的DexClassData,也就是EncryptString的DexClassData,利用DexClassData中的一个成员classIdx到TypeId池中找到descriptorIdx,这是一个字符串池的索引,这个字符串就是class的名称。具体实现如下
{
const DexClassDef* classdef;
u4 count = dexFile->pHeader->classDefsSize;
const u1* pEncodedData = NULL;
DexClassData* pClassData = NULL;
const char *descriptor = NULL;
ALOGD(“total count: %ld search:%s”, count, clazz);
for(u4 i=0; i<count; i++){
classdef = dexGetClassDef(dexFile, i);
descriptor = getTpyeIdString(dexFile, classdef->classIdx);
ALOGD(“%s”, descriptor);
pEncodedData = dexFile->baseAddr + classdef->classDataOff;
pClassData = dexReadAndVerifyClassData(&pEncodedData, NULL);
ALOGD(“[%p] [%p]”, pEncodedData, pClassData);
if(strcmp(descriptor, clazz) == 0){
ALOGD(“found %s”, clazz);
return pClassData;
}
if (pClassData == NULL) {
ALOGE(“Trouble reading class data (#%ld) for %s\n”, i, descriptor);
continue;
}
free(pClassData);
}
return NULL;
}
{
int idx = 0;
DexMethod *method = NULL;
const DexMethodId* methodId = NULL;
DexCode* code = NULL;
method = classData->directMethods;
methodId = dexFile->pMethodIds;
for (int i = 0; i < (int) classData->header.directMethodsSize; i++) {
idx = classData->directMethods[i].methodIdx;
ALOGD(“:idx-%d [%06lx]: %s->%s”, idx, classData->directMethods[i].codeOff,
getTpyeIdString(dexFile, methodId[idx].classIdx), getString(dexFile, methodId[idx].nameIdx));
const DexCode* pCode = dexGetCode(dexFile, &classData->directMethods[i]);
if(strcmp(getString(dexFile, methodId[idx].nameIdx), methodName) == 0){
return pCode;
}
}
for (int i = 0; i < (int) classData->header.virtualMethodsSize; i++) {
idx = classData->virtualMethods[i].methodIdx;
ALOGD(“idx-%d [%06lx]: %s->%s”, idx, classData->virtualMethods[i].codeOff,
getTpyeIdString(dexFile, methodId[idx].classIdx), getString(dexFile, methodId[idx].nameIdx));
const DexCode* pCode = dexGetCode(dexFile, &classData->virtualMethods[i]);
if(strcmp(getString(dexFile, methodId[idx].nameIdx), methodName) == 0){
return pCode;
}
}
return code;
}
int codeoff = fd.findCode(“TestApk.dex”,
“Lcom/example/testapk/EncryptString;”, “encrypt”);
try {
File file = new File(“/data/local/tmp/classes.dex”);
byte[] dexByte = readFileBytes(file);
byte[] lengthHex = new byte[4];
System.arraycopy(dexByte, codeoff – 4, lengthHex, 0, 4);
int length = byteToInt(lengthHex, 0);
String strso = path + “/data.so”;
writeFile(strso, dexByte, codeoff – 16, length + 16);
System.arraycopy(exceptionCode, 0, dexByte, codeoff,
length > exceptionCode.length ? exceptionCode.length : length);
//修改DEX file size文件头
fixFileSizeHeader(dexByte);
//修改DEX SHA1 文件头
fixSHA1Header(dexByte);
//修改DEX CheckSum文件头
fixCheckSumHeader(dexByte);
String str = path + “/classes_fix.dex”;
writeFile(str, dexByte, 0, dexByte.length);
将data.so和classes_fix.dex拷贝出来,看一下
data.so
所以红色下划线的31个字节被替换为exceptionCode的前31字节。