文件 偏移 基址_Dex文件格式解析(上)

前言:自己每天记录的学习笔记,可能不完善,初学逆向,请多多指教

上一篇文章的注册机简单的实现如下:

这里测试的被md5加密的值为:admin

#include

int main(){

// 输入的注册表首先进行md5加密

char szCode[] = "21232f297a57a5a743894a0e4a801fc3";

// 

for (int i = 0; i < strlen(szCode); i += 2){

//printf("%c", szCode[i] & 0xFF);

szCode[i] & 0xFF;

}

// 222275aa4840481c

return 0;

}

d5d6583711c772fb59d182e1f35ab455.png

--------------------------------------

class文件和dex文件之间的关系:

当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右

cf8ad3872eb1e01c5bdc20844f79dc8b.png

class文件生成dex文件,命令如下:

dx --dex --output TestMain.dex TestMain.class

在手机(这里测试的是dalvik虚拟机)中进行测试运行:

adb push TestMain.dex /storage/emulated/0

dalvikvm -cp /sdcard/TestMain.dex TestMain

接下来开始解析Dex文件格式:

1、DEX  Header文件头

如下图所示

ee1b3345cf64b23a5b1f9a847848a625.png

作用:记录了一些当前文件的信息以及其他数据结构在文件中的偏移量,比如可以在里面看到一些入 字符串列表 类型列表 原型列表 方法列表 类定义列表等的数据结构

用010editor编辑工具进行观察学习,显示如下,总共占0x70大小

4ed4342921a329a46f3fb62eea99f695.png

magic(魔数):

1、 一般是常量,用来标记 DEX 文件

2、字符串格式为 dex\n035\0,十六进制为 0x6465780A30333500,感觉跟PE的MZ标志0X5A4D一样,用来作为标识

checksum (校验码):

1、checksum是对去除 magic(DEX标识头) 、 checksum 以外的文件部分作 alder32 算法得到的校验值,用于判断 DEX 文件是否被篡改

2、 signature (签名)是对除去 magic 、 checksum 、 signature 以外的文件部分作 sha1算法 得到的文件哈希值。

endianTag(存储方式):

1、endianTag用于标记 DEX 文件是大端表示还是小端表示(在intel 8086CPU x86 也是用的小端存储,其实也就是低位字节在低地址 ),由于 DEX 文件是运行在 Android 系统中的,所以一般都是小端表示,这个值也是恒定值 0x12345678

其余部分分别标记了 DEX 文件中其他各个数据结构的个数和其在数据区的偏移量(跟PE寻找相应的表的地址的时候其实很相似)

用C来实现dex文件头的解析很简单如下代码实现:

9609d58808cfcdeb7bad66f5df98368c.png

16570a3c6796cc419c184f8708869b65.png

2、String_ids

接着就是字符串列表的数据结构解析,也就是dex文件头中的stringIdsSize和stringIdsOff这两个数据

注意:需要先知道的就是dex文件头中的这些数据一般都是偏移量,所以并不是真正的地址,还需要加上基址才是真正的对应的数据结构的位置

String_ids的数据结构:

struct DexStringId { 

    u4 stringDataOff; 

};

这个结构体占4个字节,结构体中只有一个成员,并且这个成员表示的是每个字符串在data区的偏移量。

根据偏移量能拿到对应字符串,如下可以看到0x70开始的前4个字节为 0x000001AA,那么偏移量为0x01AA,跟过去可以看到(右图)所示,但是可以看到红色标记的一共是9个字节啊,其实第一个字节代表的字符串的长度,这里是8,那么就往后8个字节,也就是

d1b155e35483f40752d794f80a294bf2.png

b42dd879a17e50e3b6d6adf221bd603f.png

C实现的代码如下:

6abd39bb69cea760024ad54a70c57c59.png

8b89e05e8157f8e5757eb0e82341c954.png

3、Type_ids

接着就是类型列表的数据结构解析,也就是dex文件头中的typeIdsSize和typeIdsOff这两个数据

struct DexTypeId {

    u4  descriptorIdx;

};

这个的数据结构如上,所以大家应该会猜到可能就是啥的索引值index,的确它这个就是一个索引值,并且是String_ids中的索引值,因为类型的名称也是存储在String_ids中的,因为大家可以看到上面的图中确实存储着类型的值,比如Ljava/lang/String等等的

那么思路其实也就是拿到这个索引值,然后再套回String_ids中进行查找,实现代码如下:

66cb60b8026cbad8b6262358c3b466db.png

1759584ef1479ce369e8d48c3984449e.png

4、Proto_ids

接着就是方法列表的数据结构解析,也就是dex文件头中的protoIdsSize和protoIdsOff这两个数据,这个数据结构描述了dex文件中所有的方法声明!

struct DexProtoId {

    u4  shortyIdx;          /* 指向 string_ids ,表示方法声明的字符串*/ 

    u4  returnTypeIdx;      /* 指向 type_ids ,表示方法的返回类型 

    u4  parametersOff;      /* 指向方法参数列表的偏移量,DexTypeList结构体

}

这个数据结构如上,简单的说下其实前两个的都是索引值,代表的是string_ids中的索引值(和上面的解析一样),第二个就是type_ids的索引值(和上面的解析一样),唯一不同的也就是第三个,第三个是一个偏移量,这个Dex文件的基址加上parametersOff偏移量 则是指向一个结构体DexTypeList,然后还会发现这个结构体中还有一个结构体也就是DexTypeItem,这是一个变长数组,根据size决定,也就是参数的个数

struct DexTypeList {

    u4  size;               //DexTypeItem的个数

    DexTypeItem list[1];    //DexTypeItem变长数组

};

struct DexTypeItem {

    u2  typeIdx;            //DexTypeId中的索引下标

};

解析思路就是 基址加上parametersOff的偏移量,然后拿到DexTypeList,然后根据size循环,把DexTypeList加上基址进行获取对应的参数,实现代码如下:

42526f525cebd079a452483a005a9e29.png

04f6e83dfe3b676be8f29d29b613505d.png

今天下午学习的时候就解析了这四个,每天继续肝完就可以了,谢谢大家看到这里了!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 获取模块.dll文件基址偏移量通常用于进行动态链接库注入或者修改内存数据等操作。以下是一种常见的方法: 1. 获取目标进程的句柄。 可以使用函数OpenProcess来打开目标进程,获取其句柄。传入参数为目标进程的ID和所需的访问权限。 2. 枚举模块并找到目标模块。 使用函数EnumProcessModules来枚举目标进程的模块,获取模块的句柄。传入参数为目标进程的句柄、模块句柄数组和数组的大小。如果函数执行成功,返回值为模块句柄的数量。 3. 获取模块的文件名。 使用函数GetModuleBaseName来获取模块的文件名。传入参数为目标进程的句柄和模块的句柄。 4. 获取模块的基址和大小。 使用函数GetModuleInformation来获取模块的基址和大小。传入参数为目标进程的句柄、模块的句柄和包含模块信息的结构体。 5. 获取模块的导出表。 使用函数GetProcAddress来获取模块的导出函数的地址。传入参数为模块的基址和导出函数的名称。 通过上述方法,就可以获取到目标模块的基址偏移量。不同的编程语言和环境会有相应的函数和方法来实现以上步骤,但基本思路是一致的。 ### 回答2: 要获取一个模块(.dll文件)的基址偏移,我们可以通过一些编程语言来实现。下面是一个使用C++编程语言的示例: 1. 首先,我们需要加载目标模块。可以使用`LoadLibrary`函数来加载.dll文件,并得到模块的句柄。例如: ```cpp HMODULE hModule = LoadLibrary(TEXT("target.dll")); ``` 2. 然后,我们可以使用`GetModuleInformation`函数来获取模块的基址偏移。该函数需要提供进程的句柄和模块的句柄,并返回一个`MODULEINFO`结构体,其中包含了基址和大小等信息。例如: ```cpp MODULEINFO moduleInfo; GetModuleInformation(GetCurrentProcess(), hModule, &moduleInfo, sizeof(moduleInfo)); ``` 3. 最后,可以通过计算地址偏移来得到具体的地址。例如,要获取某个函数的地址,可以将函数的偏移加上模块的基址。示例代码如下: ```cpp DWORD functionOffset = 0x1234; // 假设函数的偏移为0x1234 DWORD functionAddress = moduleInfo.lpBaseOfDll + functionOffset; ``` 总结:通过加载模块并获取模块信息,我们可以得到模块的基址偏移。然后,我们可以根据需要进行地址计算来获得具体的地址。需要注意的是,以上示例是使用C++编程语言的示例,其他编程语言也有类似的函数或方法可以实现相同的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值