Android一代加固

Android一代加固

—————————-
考研回来了,感觉考的一般般,打算补一下技术找实习~

Android一代加固,使用的dexLoader动态加载dex,是最原始的一种壳,主要原理就是把要保护的类以dex的形式抽取出来,保存在assets文件夹之下,运行时动态加载,保护性稍微需要好一点的话,可以提前在assets文件中将dex加密存储,在读取和释放dex落地的过程中进行解密,代码已经传到git上,其中想说下在看f8巨巨视频的一些不是很清楚的点。
先说一下大概的过程吧,首先,编译原始apk,提取出其中的dex

image_1dt00f0vnnnctt11jdp73715mq9.png-92.4kB

在f8巨巨的课程之中,f8首先删除了除了原本中所有带符号的派生出的smali,然后才进行了重打包,由于我写的demo比较长,里面涉及一些jni操作(本质上是一个小CrackMe),导致删除MainActivity$1这个smali之后,重新打包出来的dex文件代码和逻辑都不是很全,运行时会出错,不过后来直接把整个dex存到assets文件夹之下,问题就解决了,感觉删除派生的smali是多此一举~~~

image_1dt00ob2nhse1ugm1ksg1koqkeem.png-67.7kB
文件处理

在第一轮读取中改回文件头,自己抄抄改改总结一个反射工具类,一开始想偷懒,直接用的反射,导致代码很冗长,这只是一代壳,这几天把二三四代壳的复现都补一下,项目地址https://github.com/Dispa1r/DexLoader。

第一代壳的第二种形式,则是把在attachBaseContext的释放,注册的过程转移到jni层中,配合上so的混淆,安全性应该还是可以的,但是还是使用了libdvm.so中的函数,攻击者可以直接hook dvm里面几个加载dex的关键函数,在那里dump下来

auto libdvm = dlopen("libdvm.so",RTLD_NOW);
auto dvm_dalvik_system_DexFile = (JNINativeMethod*)dlsym(libdvm,"dvm_dalvik_system_DexFile");
void (*fnOpstrcmpenDexFileNative)(const u4* args,JValue* pResult) = nullptr;
for (auto p = dvm_dalvik_system_DexFile; p->fnPtr != nullptr ; p++) {
    if ((p->name,"openDexFileNative") == 0 && strcmp(p->signature ,"(Ljava/lang/String;Ljava/lang/String;I)I") == 0) {
        fnOpenDexFileNative = (void (*)(const u4* ,JValue*))p->fnPtr;
        break;
    }
}

DexOrJar* pDexOrJar = NULL;

if (fnOpenDexFileNative != nullptr){
    auto dvmCreateStringFromCst = (void* (*)(const char* utf8Str))dlsym(libdvm,"_Z23dvmCreateStringFromCstPKc");
    u4 args[2];
    args[0] = static_cast<u4>(dvmCreateStringFromCst(cachefileOptPath.c_str()));
  args[1] = static_cast<u4>(dvmCreateStringFromCst(cachefileOptPath.c_str()));
    JValue result;
    fnOpenDexFileNative(args,&result);
    pDexOrJar = (DexOrJar *) result.l;

}

auto dexFile_cla = env->FindClass("dalvik/system/DexFile");
auto dexFile_obj = env->AllocObject(dexFile_cla);
JniInfo::SetObjectFIELD(env,dexFile_obj,"mCookie","I",static_cast<jint>(reinterpret_cast<uintptr_t>(pDexOrJar)));
auto classLoader_obj =JniInfo::CallObjectMethod(env,context,"getClassLoader","()Ljava/lang/ClassLoader;");
std::vector<jobject > dexFile_objs;
dexFile_objs.push_back(dexFile_obj);
makeDexElements(env,classLoader_obj,dexFile_objs);

可以看到部分代码中,要去dvm中找到dvm_dalvik_system_DexFile这个函数,利用dlsym传入这个符号后会返回这个函数的地址,去源码里看下这个函数

image_1dt0imr5b164b15j387f1agh26b13.png-38.9kB

这是一个函数数组,第一个元素是函数的符号名称,第二个元素则是函数的参数,第三个元素则是更底层一级的真正实现的函数地址,在jni动态加载dex的过程中,我们在拿到这个数组之后,去遍历里面的元素,找到OpenDexFileNative这个函数的地址,找到这个地址之后,创建关于文件路径的字符串,最后去调用这个openDexFileNative这个函数打开并装载我们的dex文件,安卓源码中是这样实现的

static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
152    JValue* pResult)
153{
154    StringObject* sourceNameObj = (StringObject*) args[0];
155    StringObject* outputNameObj = (StringObject*) args[1];
156    DexOrJar* pDexOrJar = NULL;
157    JarFile* pJarFile;
158    RawDexFile* pRawDexFile;
159    char* sourceName;
160    char* outputName;
161
162    if (sourceNameObj == NULL) {
163        dvmThrowNullPointerException("sourceName == null");
164        RETURN_VOID();
165    }
166
167    sourceName = dvmCreateCstrFromString(sourceNameObj);
168    if (outputNameObj != NULL)
169        outputName = dvmCreateCstrFromString(outputNameObj);
170    else
171        outputName = NULL;
172
173    /*
174     * We have to deal with the possibility that somebody might try to
175     * open one of our bootstrap class DEX files.  The set of dependencies
176     * will be different, and hence the results of optimization might be
177     * different, which means we'd actually need to have two versions of
178     * the optimized DEX: one that only knows about part of the boot class
179     * path, and one that knows about everything in it.  The latter might
180     * optimize field/method accesses based on a class that appeared later
181     * in the class path.
182     *
183     * We can't let the user-defined class loader open it and start using
184     * the classes, since the optimized form of the code skips some of
185     * the method and field resolution that we would ordinarily do, and
186     * we'd have the wrong semantics.
187     *
188     * We have to reject attempts to manually open a DEX file from the boot
189     * class path.  The easiest way to do this is by filename, which works
190     * out because variations in name (e.g. "/system/framework/./ext.jar")
191     * result in us hitting a different dalvik-cache entry.  It's also fine
192     * if the caller specifies their own output file.
193     */
194    if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
195        ALOGW("Refusing to reopen boot DEX '%s'", sourceName);
196        dvmThrowIOException(
197            "Re-opening BOOTCLASSPATH DEX files is not allowed");
198        free(sourceName);
199        free(outputName);
200        RETURN_VOID();
201    }
202
203    /*
204     * Try to open it directly as a DEX if the name ends with ".dex".
205     * If that fails (or isn't tried in the first place), try it as a
206     * Zip with a "classes.dex" inside.
207     */
208    if (hasDexExtension(sourceName)
209            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
210        ALOGV("Opening DEX file '%s' (DEX)", sourceName);
211
212        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
213        pDexOrJar->isDex = true;
214        pDexOrJar->pRawDexFile = pRawDexFile;
215        pDexOrJar->pDexMemory = NULL;
216    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
217        ALOGV("Opening DEX file '%s' (Jar)", sourceName);
218
219        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
220        pDexOrJar->isDex = false;
221        pDexOrJar->pJarFile = pJarFile;
222        pDexOrJar->pDexMemory = NULL;
223    } else {
224        ALOGV("Unable to open DEX file '%s'", sourceName);
225        dvmThrowIOException("unable to open DEX file");
226    }
227
228    if (pDexOrJar != NULL) {
229        pDexOrJar->fileName = sourceName;
230        addToDexFileTable(pDexOrJar);
231    } else {
232        free(sourceName);
233    }
234
235    free(outputName);
236    RETURN_PTR(pDexOrJar);
237}

其中调用了dvmRawDexFileOpen这个函数,这个函数也经常被用来作为脱壳点,返回值是一个pDexOrJar结构体,其中Dalvik_dalvik_system_DexFile_openDexFileNative的上层函数openDexFileNative返回值是一个int,这个int就是mCookie,也就是内存中每个dexFile对象的唯一标识符,所以,我们需要为我们新open到的DexFile新建一个DexFIle结构体,并且填充他的mCookie,最后我们再调用jni的DexLoader的参数,最终加载完毕。因此,针对各种一代壳的攻击方式也特别多,主要还是针对openDexFile,dvmDexFileOpenPartial这几个关键点下断,找到存放最终解密的dex文件处,最后dump出dex文件即可,代表性的脱壳工具就是ZjDroid这一类工具,通过xp这个框架去hook系统中的一些类最后实现脱壳。