首页
社区
课程
招聘
[原创]DEX文件内存加载实现中的数据构造(C部分)
发表于: 2014-12-23 22:21 25832

[原创]DEX文件内存加载实现中的数据构造(C部分)

2014-12-23 22:21
25832

这里也是的
http://crazysprite.gitcafe.com/2014/12/23/dexloadinmemory/

0x00

Android提供了dex文件动态加载的接口。在加载未安装的dex文件是可以使用Dexclassloader函数来完成。但是这个函数存在一个问题,被加载的dex文件必须以明文的形式存放在文件系统中,这样dex文件很容易被获取到并被分析。

于是,在Android 4.0以上的系统中有了对字节流形式dex文件的加载。在4.4以前java层也有Dex文件按字节流方式加载的接口,但是4.4开始对应的java层接口传入参数变为文件路径而不是字节流。因此直接在JAVA层进行Dex文件内存加载的方式失效了(如果有大大知道其他方法可以一起交流下)。
可以参见Jack_Jia的帖子。
http://blog.csdn.net/androidsecurity/article/details/9674251
本文也是在对该贴C层的实现过程进行一些补充。
实现过程还参见了看雪帖Dex内存加载的Native实现过程中出现的问题中的讨论。
http://bbs.pediy.com/showthread.php?p=1256698
谢谢所有人的无私分享。

0x01

Jack_Jia的帖子已经给出了C层获取接口的方法,这里不在赘述。

本文主要叙述其中ArrayObject结构体的构造方法。
在Android源码中找到ArrayObject结构体的定义在 dalvik/vm/oo/Object.h

struct Object {
    /* ptr to class object */
    ClassObject*    clazz;
    /*
     * A word containing either a "thin" lock or a "fat" monitor.  See
     * the comments in Sync.c for a description of its layout.
     */
    u4              lock;
};
struct ArrayObject : Object {
    /* number of elements; immutable after init */
    u4              length;
    /*
     * Array contents; actual size is (length * sizeof(type)).  This is
     * declared as u8 so that the compiler inserts any necessary padding
     * (e.g. for EABI); the actual allocation may be smaller than 8 bytes.
     */
    u8              contents[1];
};
u4和u8是android中的定义,分别对应uint32_t和uint64_t。
Android的数据类型对应如下表:

类型        含义
u1        等同于uint8_t,表示1字节的无符号数
u2        等同于uint16_t,表示2字节的无符号数
u4        等同于uint32_t,表示4字节的无符号数
u8        等同于uint64_t,表示8字节的无符号数
sleb128        有符号LEB128,可变长度1~5字节
uleb128        无符号LEB128,可变长度1~5字节
uleb128p1        无符号LEB128值加1,可变长度1~5字节
实际使用中并没有用到Object结构体,下面解释下其他连个个字段的含义。

length 代表传入Dex文件流的长度
contents 代表存放Dex数据流的首位置
查看Dalvik_dalvik_system_DexFile_openDexFile_bytearray函数对ArrayObject的处理。源码位置在dalvik\vm\native\dalvik_system_DexFile.cpp

static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
    JValue* pResult)
{
    ArrayObject* fileContentsObj = (ArrayObject*) args[0];
    u4 length;
    u1* pBytes;
    RawDexFile* pRawDexFile;
    DexOrJar* pDexOrJar = NULL;
    if (fileContentsObj == NULL) {
        dvmThrowNullPointerException("fileContents == null");
        RETURN_VOID();
    }
    /* TODO: Avoid making a copy of the array. (note array *is* modified) */
    length = fileContentsObj->length;
    pBytes = (u1*) malloc(length); // 位置A
    if (pBytes == NULL) {
        dvmThrowRuntimeException("unable to allocate DEX memory");
        RETURN_VOID();
    }
    memcpy(pBytes, fileContentsObj->contents, length); //位置B
    if (dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile) != 0) {
        ALOGV("Unable to open in-memory DEX file");
        free(pBytes);
        dvmThrowRuntimeException("unable to open in-memory DEX file");
        RETURN_VOID();
    }
    ……………………
}
可以看到在位置A处分配了length大小的空间,位置B处从ArraryObject的contents的位置开始向分配内存空间中拷贝Dex流数据。
因此猜测ArrayObject结构体应该如下方式构造:

char* arr;
length =获取读出来的dex文件流的长度;
arr=(char*)malloc(16+length);
ArrayObject *ao=(ArrayObject*)arr;
ao->length=length;
memcpy(arr+16,dex文件流指针,length);
通过IDA调试来进行验证,跟进入系统SO代码处可以看到汇编代码如下

libdvm.so:41674A38 ADD.W           R1, R6, #0x10 //拷贝位置
libdvm.so:41674A3C MOV             R2, R4
libdvm.so:41674A3E BLX             unk_41621E14 //执行拷贝
libdvm.so:41674A42 MOV             R0, R5
libdvm.so:41674A44 MOV             R1, R4
libdvm.so:41674A46 ADD             R2, SP, #4
libdvm.so:41674A48 BL              _Z22dvmRawDexFileOpenArrayPhjPP10RawDexFile
libdvm.so:41674A4C CBZ             R0, loc_41674A5E
libdvm.so:41674A4E MOV             R0, R5
libdvm.so:41674A50 BLX             unk_41621DF0
libdvm.so:41674A54 LDR             R0, =(aUnableToOpenIn - 0x41674A5A)
libdvm.so:41674A56 ADD             R0, PC                  ; "unable to open in-memory DEX file"
拷贝是从偏移0x10开始的,也就是偏移16个字节。因此印证以上数据的构造方式。

(注: 在评论区有人讨论偏移是12, 可是我做出来始终是16.之前一直没搞懂这个问题,最近才发现,问题的出现的原因在于处理器平台不同. ARM处理器的偏移是16,x86处理器的偏移是12)

0x03

通过这种内存加载的方式,可以在文件系统中只存放一个加密Dex文件。首先用so将文件流读入内存,在内存中解密。然后用上面的方式将解密后的数据流传给系统函数加载。这样就不会在文件系统中存在明文的Dex文件。

最后,谢谢所有分享和提供过帮助的人。我的理解若不正确的地方,请大家原谅并帮助指出。谢谢。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 3
支持
分享
最新回复 (25)
雪    币: 36
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢楼主分享。。。。。。。。。。
2014-12-24 09:28
0
雪    币: 178
活跃值: (412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
赞楼主博客,希望能一直写下去。
2014-12-24 09:34
0
雪    币: 12
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
分析的不错,话说楼主研究出来为什么偏移是16了?我自己写了个示例,发现他并不会按照8字节对齐啊,偏移是12。
2014-12-24 10:01
0
雪    币: 4522
活跃值: (2146)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
先收藏了 LZ请继续
2014-12-24 11:36
0
雪    币: 212
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
恩 先谢谢你的帮助,我在文中写了验证过程的,我现在的代码就是以偏移16做的,测试版本是4.4。用12的时候会提示找不到dex文件标识。容我再去看看
2014-12-24 12:07
0
雪    币: 246
活跃值: (264)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
16是ArrayObject的大小,
struct Object {
    ClassObject*    clazz;//4字节
    u4              lock;//4字节
};
struct ArrayObject : Object {
    u4              length;//4字节
    u8              contents[1];//4字节。如果是12,这个变量存哪里?它的内容是指向dex的首地址。
};

后面才是dex文件
2014-12-24 22:46
0
雪    币: 204
活跃值: (1908)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
楼主能否 将你 Demo例子发出来分享一下
2014-12-25 10:08
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
感谢楼主分享 最近也在弄这个实现 用了楼主的代码确实跑通了 dex文件动态加载ok  指针确实需要好好研究下 不敢说自己曾经会过C了……
现在的问题是上层自定义的DexCloassLoader加载不出一些view类,虽然报的是xml解析失败,但是根本问题还在ClassLoder这儿,期待楼主的java部分。
2015-1-9 10:41
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
呃 我的问题搞定啦 自定义的DexClassLoder中的findClass方法没有使用super,父节点已经加载好的类找不到,修正后就搞定了,界面已弹出~  
下面的问题就是这个方法只支持4.0~4.4? 通用性略差…… 继续探索……
2015-1-9 11:37
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
我按照lz说的实现了下, 发现我的情况很独特, 一下没明白为什么? 我把代码贴出来, 帮忙看看。

 typedef uint32_t            u4;
 typedef uint64_t            u8;
 struct ClassObject;
 struct Object {
    struct ClassObject*    clazz;
    u4              lock;
 };
 struct ArrayObject : Object {
    u4              length;
    u8              contents[1];
 };

  unsigned char tmp[nArrLen]={........};//伪代码,dex数据
  ArrayObject *ao=(ArrayObject *)malloc(nArrLen+16);
  u4 args[] = { (u4)ao };
  ao->length=nArrLen;
  memcpy(ao->contents,tmp,nArrLen);   [COLOR="Red"]//这个地方我必须这么写, 按照lz的偏移量去写都会提示文件头验证错误[/COLOR]


这个偏移到底是怎么算呢?
2015-1-9 16:30
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
我还在寻找自己对c指针认知的回忆中…… 原理还没弄清楚,但是我的结构是这样定义的:
typedef struct   {
   void*    clazz;
   u4              lock;
    u4              length;
    u1*              contents;
}ArrayObject ;
2015-1-9 17:55
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
楼上,能否分享下你的java部分?
2015-1-9 18:03
0
雪    币: 212
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
你可以试试用偏移12 ,楼上有人说了这个问题,这个我也搞不明白了,我用你这个方法也可以,但是用12就会无法识别dex头。
2015-1-9 18:24
0
雪    币: 212
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
来晚了。。没帮上忙
2015-1-9 18:26
0
雪    币: 53
活跃值: (280)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
解密后在内存中,通过遍历该应用进程map表,在map表中相关heap区中搜索dex\n035标志也能找到,并拖出来
2015-1-9 19:09
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
我也是被领导赶鸭子上架才匆匆开始了解加壳的 好多地方细节还不明白 后面准备把这两个礼拜的内容整理成文章发出来 到时候共同讨论
2015-1-10 14:10
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
您能回复就很给面子啦  后面可能还有问题要请教~
2015-1-10 14:12
0
雪    币: 212
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
是的 这个方法不是不可解的 只是稍微增加分析难度
2015-1-12 09:29
0
雪    币: 212
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
你邮件说的那个传入参数 我使用的是dex文件 并不是odex的
2015-1-13 09:32
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
搞了好几天, 把代码看了大体都看了下, 终于实现了这个功能但是, 运行的时候, 会有些警告。 不知道为什么, 谁知道为啥?

01-13 19:13:51.524: E/dalvikvm(8297): Attempted to change 0x7590a76a; map is 0x0 - 0x0
01-13 19:13:51.524: D/dalvikvm(8297): NOTE: DEX page access change (->RW) failed
01-13 19:13:51.524: E/dalvikvm(8297): Attempted to change 0x7590a76a; map is 0x0 - 0x0
01-13 19:13:51.524: D/dalvikvm(8297): NOTE: DEX page access change (->RO) failed
01-13 19:13:51.524: E/dalvikvm(8297): Attempted to change 0x7590a71c; map is 0x0 - 0x0
01-13 19:13:51.524: D/dalvikvm(8297): NOTE: DEX page access change (->RW) failed
01-13 19:13:51.524: E/dalvikvm(8297): Attempted to change 0x7590a71c; map is 0x0 - 0x0
01-13 19:13:51.524: D/dalvikvm(8297): NOTE: DEX page access change (->RO) failed
01-13 19:13:51.524: E/dalvikvm(8297): Attempted to change 0x7590a756; map is 0x0 - 0x0
01-13 19:13:51.524: D/dalvikvm(8297): NOTE: DEX page access change (->RW) failed
01-13 19:13:51.524: E/dalvikvm(8297): Attempted to change 0x7590a756; map is 0x0 - 0x0
01-13 19:13:51.524: D/dalvikvm(8297): NOTE: DEX page access change (->RO) failed

2015-1-13 19:17
0
雪    币: 7
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
最近正好在研究dex
2015-1-14 07:50
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
    JValue* pResult)
{
    ArrayObject* fileContentsObj = (ArrayObject*) args[0];
    .....
    memcpy(pBytes, fileContentsObj->contents, length);
    ...
}

为什么是16 看看源码里面是怎么使用的应该会有答案
2015-1-15 19:53
0
雪    币: 216
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
学习,期待DEMO
2015-1-19 09:50
0
雪    币: 212
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
偏移12与16的问题我最近找到相关讨论了,这两个偏移都是对的,只是平台不同,16是arm平台的偏移,12是x86平台的偏移.希望对你有帮助
2015-5-12 10:56
0
游客
登录 | 注册 方可回帖
返回
//