首页
社区
课程
招聘
一个易上手的函数抽取样本还原
发表于: 2020-11-9 18:38 8953

一个易上手的函数抽取样本还原

2020-11-9 18:38
8953

题目出自2W班7月第一题

 

题目要求:某些抽取壳在类被加载后便还原了被抽取的函数,请编写xposed插件完成对加壳app中所有类的加载,最终完成该类型抽取壳的修复

 

由题目其实可以很清楚的了解到,这个函数抽取壳,类被还原后就不会复原回去,那么解题思路就很明确了,只要遍历所有的类,再把Dex dump出来,即脱壳成功。

 

那么其实这道题考察的就是classloader的运用,无论壳如何变,为了能让上层应用能正常运用,必然逃不脱整个安卓框架层,那么既然框架层也是用的classloader去加载类的,那么就逃不脱classloader这个知识点。

 

下面开始解题过程。

 

使用环境:android8.1 ,edxp
图片描述

 

首先看到app的dex其实已经是一个脱壳状态了,只是函数体被抽取了
那么思路其实已经很清晰了,就是遍历所有的类。然后再把代码dump下来即可。

 

那么如何dump Dex呢。在8.0以下,我们可以参考Fdex2的代码,在遍历完成后,使用dex类中的getBytes把dex文件dump出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void initRefect() {
        try {
            Dex = Class.forName("com.android.dex.Dex");
            Dex_getBytes = Dex.getDeclaredMethod("getBytes", new Class[0]);
            getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex", new Class[0]);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
 
    }
 
    public  void writeByte(byte[] bArr, String str) {
        try {
            OutputStream outputStream = new FileOutputStream(str);
            outputStream.write(bArr);
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
            XposedBridge.log("文件写出失败");
        }
    }

以上代码节选自Fdex2中。

 

但是因为我的环境是android 8.1,而在8.1上Dex类中已经没有这个函数了。
但是参考目前的一些frida脚本,可以发现大多数都是hook OpenCommon这个函数,因为这个函数可以直接拿到basesize,就可以直接dump了。但是我们这里使用LoadMethod这个函数,因为在题目三中也需要用到这个函数,因此这里我就用这个函数把。

 

以下代码是so中的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
extern "C" jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
 
    LOGE("jni onload enter");
    //1.首先在jni_onload中hook我们想要的函数。这里用的sandhook
    hookLogic();
    LOGE("jni onload stop");
    return JNI_VERSION_1_6;
}
 
//2.然后我们分别在32位和64位中hook LoadMethod这个函数
void hookLogic() {
if (sizeof(void *) == 8) {
        const char *libartPath = "/system/lib64/libart.so";
        old_loadmethod = reinterpret_cast<void *(*)(void *, DexFile &,
                                                    art::ClassDataItemIterator &,
                                                    art::Handle *,
                                                    art::ArtMethod *)>( SandInlineHookSym(
                libartPath,
                "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE",
//                "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE",
                reinterpret_cast<void *>(new_loadmethod)));
    } else {
        const char *libartPath = "/system/lib/libart.so";
        old_loadmethod = reinterpret_cast<void *(*)(void *, DexFile &,
                                                    art::ClassDataItemIterator &,
                                                    art::Handle *,
                                                    art::ArtMethod *)>( SandInlineHookSym(
                libartPath,
                "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE",
                reinterpret_cast<void *>(new_loadmethod)));
    }
}
 
 
void *(*old_loadmethod)(void *, DexFile &,
                        art::ClassDataItemIterator &,
                        art::Handle *,
                        art::ArtMethod *) = nullptr;
//3.紧接着在hook函数中拿到base与size。这里使用的dexFile去获取
void *new_loadmethod(void *thiz, DexFile &dex_file,
                     art::ClassDataItemIterator &it,
                     art::Handle *klass,
                     art::ArtMethod *dst) {
 
    if (strcmp((char *) dex_file.pHeader->magic, "dex\n035") != 0) {
        return old_loadmethod(thiz, dex_file, it, klass, dst);
    }
 
//    const u1 *base = dex_file.baseAddr;
    const DexHeader *base = dex_file.pHeader;
    int size = dex_file.pHeader->fileSize;
    LOGE("new opheader::%p ", dex_file.pOptHeader);
    LOGE("new header::%p ", dex_file.pHeader);
    LOGE("new magic::%p ", dex_file.pHeader->magic);
    LOGE("new loadmehtod::%p  %i  %s", base, size, dex_file.pHeader->magic);
 
    int pid = getpid();
    char dexFilePath[100] = {0};
    sprintf(dexFilePath, "/sdcard/xxxxx/%p %d LoadMethod.dex", base, size);
    mkdir("/sdcard/xxxxx", 0777);
 
    int fd = open(dexFilePath, O_CREAT | O_RDWR, 666);
    if (fd > 0) {
        ssize_t i = write(fd, base, size);
        if (i > 0) {
            close(fd);
        }
    }
 
    return old_loadmethod(thiz, dex_file, it, klass, dst);
}

首先在jni_onload中hook我们自己需要的函数,这里我们hook loadMethod方法。
这这个方法中,我们可以拿到DexFile,然后进而可以拿到base和size,然后拿到这两个后,我们就可以dump dex出来了。

 

那么在so层中的dump代码就已经写完了。java层需要做的就是遍历类即可。

 

因为可能环境的问题,我在xp代码中新建thread没有生效,有点问题,所以我直接在application中的attach函数中拿classloader。
这里可以直接在attach中拿是因为这个例子没有壳代码,如果先经过壳,那么这个classloader将会是壳的classloader,因此要注意。
所以遍历类的重点就是classloader,没有classloader就没法loadClass。

 

在拿到classloader后我就直接加载so了,因为在7.1以后的版本,dlopen会有一些限制,所以我干脆就放到了lib64目录下了,因为双亲委派的原因,最终会到这个地方找so

1
2
3
4
5
6
7
8
9
10
11
12
13
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
 
                    mContext = (Context) param.args[0];
 
 
                    XposedHelpers.callMethod(Runtime.getRuntime(), "doLoad", "/system/lib64/libsandhook-native.so", mContext.getClassLoader());
 
                    GetClassLoaderClasslist(mContext.getClassLoader());
                }
            });

紧接着就是遍历class
这里拿到classList之后loadClass即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void GetClassLoaderClasslist(ClassLoader classLoader) {
        //private final DexPathList pathList;
        //public static java.lang.Object getObjectField(java.lang.Object obj, java.lang.String fieldName)
        XposedBridge.log("start dealwith classloader:" + classLoader);
        Object pathListObj = XposedHelpers.getObjectField(classLoader, "pathList");
        //private final Element[] dexElements;
        Object[] dexElementsObj = (Object[]) XposedHelpers.getObjectField(pathListObj, "dexElements");
        for (Object i : dexElementsObj) {
            //private final DexFile dexFile;
            Object dexFileObj = XposedHelpers.getObjectField(i, "dexFile");
            //private Object mCookie;
            Object mCookieObj = XposedHelpers.getObjectField(dexFileObj, "mCookie");
            //private static native String[] getClassNameList(Object cookie);
            //    public static java.lang.Object callStaticMethod(java.lang.Class<?> clazz, java.lang.String methodName, java.lang.Object... args) { /* compiled code */ }
            Class DexFileClass = XposedHelpers.findClass("dalvik.system.DexFile", classLoader);
 
            String[] classlist = (String[]) XposedHelpers.callStaticMethod(DexFileClass, "getClassNameList", mCookieObj);
            for (String classname : classlist) {
//                XposedBridge.log(dexFileObj + "---" + classname);
                try {
                    classLoader.loadClass(classname);
                    Log.e("Hook1", "loadclass:" + classname);
                } catch (ClassNotFoundException e) {
                    Log.e("Hook1", Log.getStackTraceString(e));
                }
            }
        }
        XposedBridge.log("end dealwith classloader:" + classLoader);
 
    }

之后就可以完整的dump出dex了。
但是这种方案的话会有点慢,因为每load一次,so层都会写文件覆盖一遍文件。
因此其实可以在load完之后设置一个flag,再去写文件,速度会提升很多。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 1
支持
分享
最新回复 (8)
雪    币: 8285
活跃值: (4710)
能力值: ( LV8,RANK:134 )
在线值:
发帖
回帖
粉丝
2
当前Android hook的两大阵营最有名的便是xposed和frida。xposed使用java语言开发模块,能够直接利用系统中丰富的api,与app进程融为一体,缺点是不能直接对so中函数进行处理,需要配合其它hook框架;frida使用javascript编写hook脚本,更轻量快捷,同时支持对java函数和so的处理。两者在app的脱壳这个典型应用场景都能够写出很棒的插件,文章使用xposed首先遍历加壳app中的所有java类并进行主动加载,便能够解决在类加载流程中还原被抽取的函数的抽取壳。
2020-11-13 17:55
0
雪    币: 37
活跃值: (126)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
so层dump的源代码能否开源???
2021-1-3 12:19
0
雪    币: 275
活跃值: (495)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
雪风完美 so层dump的源代码能否开源???
上面帖子的已经全部贴出来了哦 那个就是so的dump的代码
2021-1-20 14:27
0
雪    币: 37
活跃值: (126)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
DexFile DexHeader 这些结构找不到
2021-1-24 09:15
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
666
2021-1-29 14:54
0
雪    币: 275
活跃值: (495)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
雪风完美 DexFile DexHeader 这些结构找不到
typedef uint8_t             u1;
typedef uint16_t            u2;
typedef uint32_t            u4;
typedef uint64_t            u8;
typedef int8_t              s1;
typedef int16_t             s2;
typedef int32_t             s4;
typedef int64_t             s8;

enum { kSHA1DigestLen = 20,
       kSHA1DigestOutputLen = kSHA1DigestLen*2 +1 };

struct DexHeader {
    u1  magic[8];           /* includes version number */
    u4  checksum;           /* adler32 checksum */
    u1  signature[kSHA1DigestLen]; /* SHA-1 hash */
    u4  fileSize;           /* length of entire file */
    u4  headerSize;         /* offset to start of next section */
    u4  endianTag;
    u4  linkSize;
    u4  linkOff;
    u4  mapOff;
    u4  stringIdsSize;
    u4  stringIdsOff;
    u4  typeIdsSize;
    u4  typeIdsOff;
    u4  protoIdsSize;
    u4  protoIdsOff;
    u4  fieldIdsSize;
    u4  fieldIdsOff;
    u4  methodIdsSize;
    u4  methodIdsOff;
    u4  classDefsSize;
    u4  classDefsOff;
    u4  dataSize;
    u4  dataOff;
};

struct DexOptHeader {
    u1  magic[8];           /* includes version number */

    u4  dexOffset;          /* file offset of DEX header */
    u4  dexLength;
    u4  depsOffset;         /* offset of optimized DEX dependency table */
    u4  depsLength;
    u4  optOffset;          /* file offset of optimized data tables */
    u4  optLength;

    u4  flags;              /* some info flags */
    u4  checksum;           /* adler32 checksum covering deps/opt */

    /* pad for 64-bit alignment if necessary */
};


struct DexStringId {
     u4 stringDataOff;      /* file offset to string_data_item */
};

struct DexTypeId {
    u4  descriptorIdx;      /* index into stringIds list for type descriptor */
};

struct DexFieldId {
    u2  classIdx;           /* index into typeIds list for defining class */
    u2  typeIdx;            /* index into typeIds for field type */
    u4  nameIdx;            /* index into stringIds for field name */
};

struct DexMethodId {
    u2  classIdx;           /* index into typeIds list for defining class */
    u2  protoIdx;           /* index into protoIds for method prototype */
    u4  nameIdx;            /* index into stringIds for method name */
};

struct DexProtoId {
    u4  shortyIdx;          /* index into stringIds for shorty descriptor */
    u4  returnTypeIdx;      /* index into typeIds list for return type */
    u4  parametersOff;      /* file offset to type_list for parameter types */
};

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 */
};

struct DexLink {
    u1  bleargh;
};

struct DexClassLookup {
    int     size;                       // total size, including "size"
    int     numEntries;                 // size of table[]; always power of 2
    struct {
        u4      classDescriptorHash;    // class descriptor hash code
        int     classDescriptorOffset;  // in bytes, from start of DEX
        int     classDefOffset;         // in bytes, from start of DEX
    } table[1];
};

struct DexFile {
    /* directly-mapped "opt" header */
    const DexOptHeader* pOptHeader;

   /* pointers to directly-mapped structs and arrays in base DEX */
    const DexHeader*    pHeader;
    const DexStringId*  pStringIds;
    const DexTypeId*    pTypeIds;
    const DexFieldId*   pFieldIds;
    const DexMethodId*  pMethodIds;
    const DexProtoId*   pProtoIds;
    const DexClassDef*  pClassDefs;
    const DexLink*      pLinkData;

    /*
     * These are mapped out of the "auxillary" section, and may not be
     * included in the file.
     */
    const DexClassLookup* pClassLookup;
    const void*         pRegisterMapPool;       // RegisterMapClassPool

    /* points to start of DEX file data */
    const u1*           baseAddr;

    /* track memory overhead for auxillary structures */
    int                 overhead;

    /* additional app-specific data structures associated with the DEX */
    //void*               auxData;
};
2021-2-2 11:35
0
雪    币: 275
活跃值: (495)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
lemn typedef uint8_t u1; typedef uint16_t u2; typedef uint32_t u4; t ...
文件名DexFile.h
2021-2-2 11:36
0
雪    币: 0
活跃值: (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
art报错怎么办 :error: use of undeclared identifier 'art'
2022-12-19 18:02
0
游客
登录 | 注册 方可回帖
返回
//