【Android学习】一代壳实现与脱壳研究

工具学习

Posted by Corax on May 16, 2024

Dalvik下 一代壳dump脱壳方案

核心总是在内存当中把dex文件dump下来

首先还是dalvik下 dexclassloader加载dex文件的流程 我们看源码

既然是dalvik就看4.4及以前版本的,

可以看到就一个父类的构造函数,我们进去看

image-20240527171828683

可以看到这里面也还是把parent作为节点给父classloader构造函数

image-20240527172425614

跟,这个时候可以看到俩模拟器 进入第二个

image-20240527172453573

功能也简单,就是设置好parent

image-20240527172546939

再回到上一级

image-20240527172651105

这条 跟进去 可以看到

5    public DexPathList(ClassLoader definingContext, String dexPath,
86            String libraryPath, File optimizedDirectory) {
87        if (definingContext == null) {
88            throw new NullPointerException("definingContext == null");
89        }
90
91        if (dexPath == null) {
92            throw new NullPointerException("dexPath == null");
93        }
94
95        if (optimizedDirectory != null) {
96            if (!optimizedDirectory.exists())  {
97                throw new IllegalArgumentException(
98                        "optimizedDirectory doesn't exist: "
99                        + optimizedDirectory);
100            }
101
102            if (!(optimizedDirectory.canRead()
103                            && optimizedDirectory.canWrite())) {
104                throw new IllegalArgumentException(
105                        "optimizedDirectory not readable/writable: "
106                        + optimizedDirectory);
107            }
108        }
109
110        this.definingContext = definingContext;
111        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
112        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
113                                           suppressedExceptions);
114        if (suppressedExceptions.size() > 0) {
115            this.dexElementsSuppressedExceptions =
116                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
117        } else {
118            dexElementsSuppressedExceptions = null;
119        }
120        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
121    }
122

这条 跟进去

image-20240527174231602

如果是dex文件 就loaddexfile 继续跟

image-20240527174311500

可以看到调用了loaddex

image-20240527174346840

继续 loaddex

image-20240527174435122

应该改是这个 跟opendexfile 这里还给了一个cookie

image-20240527174504623

继续跟 我们可以看到这是一个native函数

image-20240527174540782

找到这个 来到Cpp了

image-20240527174741066

以下代码

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}

如果是dex的

image-20240527180142296

跟这个dvmrawdexfileopen

这个里面我们可以看到先open了这个函数,对魔数,大小做了判断

image-20240527180252939

我们要知道的是在dalvik虚拟机里面会变成这个dex会被优化成为odex文件

这个里面也可以找到这个优化过程

image-20240527180419701

接着跟

可以看到里面这 先fork出一个子进程 使用dexopt 进行优化

image-20240527180511492

我们看看这个所谓的opt是怎么写的呢 接着看

是一个有main的cpp

image-20240527180725927

如果是dex就在这

image-20240527180756395

接着跟

里面两个,一个做初始化,一个做优化

image-20240527180913898

继续往里面看

image-20240527181216764

这里把dex 映射到了一个地址

image-20240527181730816

还可以进去下面的rewrite看看

image-20240527185630344

这个里面的

网上公开的比较多的脱壳点就是这dvmdexfileopenpartial,和上面的这个dexfileparse进行hook 这俩都是优化过后的

image-20240527185647360

都是有起始地址传入的,可以看到其实这些都可以作为hook点的

frida,xposed都可以进行hook,第一个地址,第二个文件大小,dump下来就好

我们就可以直接在这个源码当中修改,通过内存dump

注意这边没用fopen用open是因为有些加壳厂商可能对fopen这些函数做了hook

image-20240527191307962

刷入之后 基本上都是正常操作

image-20240527192018614

回过头看第一次进行映射的是在什么时候

image-20240527192325373

我们进行也可以在这边,未优化的时候进行hook

image-20240527192939369

这个也可以用

image-20240527193318963

rewrite map dexswapandverify 还有上面俩都可以

总的来说 dalvik下脱壳点不少

实践中看到一个函数抽取壳 后面研究

image-20240527195907933

ART下dex加载流程和通用脱壳点

android8.0后首次引入 inmemorydexclassloader

下面就是inmemorydexclassloader实现加载的一个流程

可以看到第一个是多个dexfile,第二个为单个

image-20240527202850843

不管咋样都会调用basedexclassloader中的构造函数 我们看看

是这个

image-20240527203023628

进入dexpathlist 里面的这个 可以看到上面对context做了一些判断,然后后面重点是调用makeinmemorydexelements

image-20240527203148811

来到这里,里面会遍历这个dexfile,然后new一个dexfile出来

image-20240527203557642

是这个

image-20240527203645229

继续跟

可以看到有两种 格式

image-20240527203707419

但是这俩玩意都是native的 可以看到

image-20240527203730137

我们去搜一下

俩函数都在 dalvik_system_DexFile.cc

可以看到这里进行memcpy,有地址 也是可以hook进行dump的

image-20240527203843197

image-20240527203856825

我们可以看到最后都会调用下面这个个 可以看到进行了createdexfile

image-20240527203917774

这里的参数也有映射的地址

image-20240527204058067

里面还调用了这个 我们传入了loaction,信息

image-20240527204113562

来到这里 可以看到有路径有映射

image-20240527204256428

这个里面还调用了opencommon函数,里面就有地址,大小,等信息

里面调用了new dexfile 更有地址 大小等信息

image-20240527204341533

所以看到加载逻辑当中有很多函数有这些地址啊大小啊啥的

inmemorydexclassloader并没有生成oat 优化的东东

课程帮忙总结了一下可供hook的函数

好这个就是inmemorydexclassloader里面的

image-20240527210221238

还有一个dexclassloader

image-20240527210515729

往上跟 四个参数

image-20240527210559721

dexpathlist

是这个重载 关键是makedexelements

image-20240527210701212

继续跟

image-20240527210843340

继续跟

image-20240527210913050

image-20240527211016005

image-20240527211051329

这里的opendexfile是关键,再往下是native函数了 其实和inmemory一样都有这个

image-20240527211233918

这里就是jni层的一个实现了

image-20240527211445761

可以看到这里面第一次出现oat这个东西,这个东西后面经常遇到

image-20240527211508875

再往下翻下就可以看到这个,这个函数最终回调dex2oat 生成oat

image-20240528003253917

跟这opendexfilesfromoat,进去呢 检验有没有oat 必然没有 所以执行

image-20240528003537120

下面这个image-20240528003602900

继续跟 这里面会调用dex2oat完成oat的编译

image-20240528003744543

这里是关键

image-20240528003857656

进去 可以看到,通过pushback压入一些参数

image-20240528003913650

最后使用exec

image-20240528003938982

image-20240528004045683

跟,这里首次进行了子进程的hook

image-20240528004109781

在对dex2oat相关代码进行hook或者修改时,都会对dex2oat进行一个阻塞

要想art下抽取技术的实现,也要阻塞这个dex2oat

讲讲抽取

如果上面成功阻塞了这个dex2oat使得这个oat文件没有成功生成,在

image-20240528004443024

里面会走到这,就会去调用一个dex文件

image-20240528004456093

详细看看,和上面那个open还不一样

里面我们可以看到我们第一个openandreadmagic可以读到文件路径,不是最好的,但也可以用

image-20240528004625712

defcon已有研究

image-20240528004738522

仅需看那个open

image-20240528004848139

下面调用了opencommon

image-20240528004930702

同时还有mapfile

image-20240528004948496

所以

image-20240528005035520

我们尝试一些重合的

如opencommon和dexfile

还是一样的编译

image-20240528005243836

还是一个整体壳的脱壳方法

上面就是两种

一种是inmemorydexclassloader
一种是禁用掉dex2oat之后用dexclassloader

也可以看看没有禁用掉dex2oat之后 的脱壳流程

image-20240528010009913

有main啊,有点像那个opt、

image-20240528010151368

解析参数 打开配置文件啊等等

image-20240528010213555

里面这个调用要跟进去

image-20240528010305365

来到这里

image-20240528010436786

实际上里面的脱壳点还有不少

甚于说对class加载或者method列的时候,都可以,只要对内存中dex文件有操作,都可以尝试。