type
status
date
slug
summary
tags
category
icon
password
0x01 引言0x02 Dump内存中动态链接库内容0x03 Linux动态链接库文件结构1. ELF头elf_header 2. 程序头表Program header table3. 节头表Section header table4. 动态符号表Dynamic symbol table5. 节与段0x04 修复0x05 参考
以腾讯安全2023安卓初赛赛题为例
0x01 引言
腾讯游戏安全技术竞赛2023 安卓客户端初赛安卓题目是由
Unity
写的游戏,其中又有il2cpp
,一般情况下都是直接使用global-metadata.dat
和libil2cpp.so
拿到il2cpp相关数据以此来辅助逆向工程,但是在这个题目中libil2cpp.so
被加密,在游戏运行初始化时由其他代码进行解密后加载入内存。所以直接使用Il2CppDumper
会出现问题无法正常获取数据。实际上如果设备已经获取ROOT权限并且安装有面具,则可以使用,与上面提到的
Zygisk-Il2CppDumper
Perfare • Updated May 17, 2024
Il2CppDumper
为同一作者,该项目直接安装模块,在游戏运行过程中Dump出逆向所需要的信息,经测试是可以无视保护措施进行dump。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2F821c0f78-15ea-4fb3-91c5-5de16471a2f5%2FUntitled.png?table=block&id=95b89665-0a35-44a8-9dbc-8df8630942cc&t=95b89665-0a35-44a8-9dbc-8df8630942cc&width=1171&cache=v2)
但是无论如何要想进一步进行逆向分析少不了拿到解密后的动态链接库文件,然后进行修复以更好地进行静态分析。由此引出这篇文章,主要学习参考了下面提到的几篇文章,在此记录学习。
0x02 Dump内存中动态链接库内容
dump后的so文件拖入IDA进行分析会提示存在坏的节并且发现坏的文件结构,本质是因为dump下的文件中部分地址是在设备虚拟内存中的地址,即虚拟地址VA,而静态分析所需要的动态链接库文件需要虚拟相对地址即VRA。因此如果使用IDA查看dump之后的so的导入表会发现是空的,因为这里的地址是VA地址,而非VRA地址,因此IDA无法识别。例如下图所示,静态分析会发现很多0x7E开头的地址数据,其实就是VA地址。而实际在正常视角下是
got
表中的偏移信息,而不是在虚拟内存中的地址。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2Fa1b429cf-b0a2-40ab-990a-82d80b536ee6%2FUntitled.png?table=block&id=7b06bbcb-a13f-4924-ac97-3d55e59c32a5&t=7b06bbcb-a13f-4924-ac97-3d55e59c32a5&width=1061&cache=v2)
![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2Fa1666636-f9f1-4c7d-a8d4-92a5311b5fc8%2FUntitled.png?table=block&id=127f9508-1479-4e74-9f09-5699558b49e1&t=127f9508-1479-4e74-9f09-5699558b49e1&width=1243&cache=v2)
0x03 Linux动态链接库文件结构
观测一个正常的Linux动态链接库so文件的文件结构,可以发现这个so由四部分组成,分别是ELF头
elf_header
、程序头表program_header_table
、节头表section_header_table
以及动态符号表dynamic_symbol_table
。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2F1846bc25-17b0-4412-912f-2275863d4b19%2FUntitled.png?table=block&id=1527e9e1-5c3b-4c38-92ad-5fbf0e5a7a59&t=1527e9e1-5c3b-4c38-92ad-5fbf0e5a7a59&width=3060&cache=v2)
下面的分析和说明以64位动态链接库文件结构为例,32位文件结构类似
1. ELF头elf_header
下面结构体为64位二进制
ELF
文件elf_header
的结构体。下图为针对实例的动态链接库文件的
elf_header
进行分析。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2F1c70f830-21ab-424b-82b4-aedbf76c0b23%2FUntitled.png?table=block&id=b38164e0-b0aa-4288-a0cb-b8ddd26e7b6c&t=b38164e0-b0aa-4288-a0cb-b8ddd26e7b6c&width=2982&cache=v2)
其中
e_type
为ET_DYN (3)
,即指明该ELF文件类型为动态链接库类型。e_phoff = 64
指明了在文件中偏移0x40
位置开始为程序头表Program header table
。e_shoff = 18528120
指明了在文件中偏移0x11AB778
位置开始为节头表Section header table
。2. 程序头表Program header table
下面结构体为64位二进制
ELF
文件Program header table
的结构体。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2F80f7f0fa-9ee8-42f5-ac96-bcf320dc34f8%2FUntitled.png?table=block&id=9c62ede7-bb27-4a9c-9444-ca13f8e974f1&t=9c62ede7-bb27-4a9c-9444-ca13f8e974f1&width=3050&cache=v2)
ELF程序头
Program header
是对二进制文件中段的描述,是程序装载必需的一部分。段(segment)
在内核装载时被解析,描述了磁盘上可执行文件的内存布局以及如何映射到内存中。其中p_type
指明了段的类型,下面列出了具体的全部类型。- 其中
PT_LOAD
描述的是可装载的段,也就是说,这种类型的段将被装载或者映射到内存中。通常程序的代码段text段和数据段data段都需要是该种类型,因为必须要被加载到内存中。通常代码段权限为可读可执行(RX)而数据段为读写(RW),这两个段都有0x1000
(4096
)的对齐标识,刚好是 32 位可执行文件一页的大小,该标识用于在程序装载时对齐。
- 类型为
PT_DYNAMIC
的段是动态链接可执行文件所特有的,包含了动态链接器所必需的一些信息,例如运行时需要链接的共享库列表、GOT的地址以及重定位信息。
3. 节头表Section header table
下面结构体为64位二进制
ELF
文件Section header table
的结构体。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2Fb33a5adb-786f-455c-a6e8-c0a036d4e2e5%2FUntitled.png?table=block&id=da69ec69-fa11-4a19-9d72-56e034c80829&t=da69ec69-fa11-4a19-9d72-56e034c80829&width=2292&cache=v2)
节头表是对这些节的位置和大小的描述,主要用于链接和调试。节头对于程序的执行来说不是必需的,没有节头表,程序仍可以正常执行,因为节头表没有对程序的内存布局进行描述,对程序内存布局的描述是程序头表的任务。节头是对程序头的补充。
readelf -l
命令可以显示一个段对应有哪些节,可以很直观地看到节和段之间的关系。如果二进制文件中缺少节头,并不意味着节就不存在。只是没有办法通过节头来引用节,对于调试器或者反编译程序来说,只是可以参考的信息变少了而已。段是程序执行的必要组成部分,在每个段中,会有代码或者数据被划分为不同的节。节头表是对这些节的位置和大小的描述,主要用于链接和调试。节头对于程序的执行来说不是必需的,没有节头表,程序仍可以正常执行,因为节头表没有对程序的内存布局进行描述,对程序内存布局的描述是程序头表的任务。节头是对程序头的补充。例如上面提到的程序段表的第一个段为一个可加载的段,其从偏移0开始到
17173324 = 0x1060B4C
结束,正好对应着如IDA所示的蓝色区域的12个节,其中包含il2cpp节,该节的数据被加密。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2Fcc5b9107-07e3-438f-af3b-929af3defd5a%2FUntitled.png?table=block&id=10bbe2aa-0494-4f11-830c-0556c0593a4c&t=10bbe2aa-0494-4f11-830c-0556c0593a4c&width=4880&cache=v2)
每一个节都保存了某种类型的代码或者数据。数据可以是程序中的全局变量,也可以是链接器所需要的动态链接信息。正如前面提到的,每个ELF目标文件都有节,但是不一定有节头,尤其是有人故意将节头从节头表中删除了之后。
4. 动态符号表Dynamic symbol table
符号项保存在
.symtab
和.dynsym
节中,因此节头项的大小与 ElfN_Sym
的大小相等。.dynsym
保存了引用来自外部文件符号的全局符号,如printf
这样的库函数,.dynsym
保存的符号是.symtab
所保存符号的子集,.symtab
中还保存了可执行文件的本地符号,如全局变量,或者代码中定义的本地函数等。因此,.symtab
保存了所有的符号,而.dynsym
只保存动态/全局符号。.dynsym
符号表对于动态链接可执行文件的执行来说是必需的,而.symtab
符号表只是用来进行调试和链接的,有时候为了节省空间,会将.symtab
符号表从生产二进制文件中删掉。下面结构体为64位二进制
ELF
文件Elf64_Sym
的结构体。st_name
保存了指向符号表中字符串表(位于.dynstr
或者.strtab
) 的偏移地址,偏移地址存放着符号的名称
st_info
指定符号类型及绑定属性
st_other
变量定义了符号的可见性
st_shndx
变量保存了相关节头表的索引,每个符号表条目的定义都与某些节对应。
st_value
存放符号的值(可能是地址或者位置偏移量)
st_size
存放了一个符号的大小,如全局函数指针的大小
5. 节与段
如下图,左边是ELF的链接视图,可以理解为是目标代码文件的内容布局。右边是ELF的执行视图,可以理解为可执行文件的内容布局。
![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2Fc867f564-156a-4ad3-9122-2f5f5acb8d37%2FUntitled.png?table=block&id=d9f6a6e6-ad89-4aa6-9b4d-62b8fd3921dc&t=d9f6a6e6-ad89-4aa6-9b4d-62b8fd3921dc&width=1126&cache=v2)
Section
称为节,是指在汇编源码中经由关键字section或segment修饰、逻辑划分的指令或数据区域,汇编器会将这两个关键字修饰的区域在目标文件中编译成节,也就是说"节"最初诞生于目标文件中。Segment
称为段,是链接器根据目标文件中属性相同的多个Section合并后的Section集合,这个集合称为Segment,也就是段,链接器把目标文件链接成可执行文件,因此段最终诞生于可执行文件中。我们平时所说的可执行程序内存空间中的代码段和数据段就是指的Segment。从概念上来说,段和节的区别在于它们所代表的内存区域的不同。段代表的是可执行代码和数据等内存区域,而节则更加抽象,它代表的是文件中的一组相关数据。
在ELF文件中,节是按照功能和目的来划分的,比如代码节、数据节、符号表节等等,而段则是按照内存区域来划分的,比如代码段、数据段、BSS段等等。总的来说,段和节在ELF文件中都是重要的概念,它们分别代表了可执行代码和数据在内存中的组织方式和文件中的逻辑组织方式。
0x04 修复
通过对原本的动态链接库文件和从内存中dump出来的文件分析可以发现有三个关键的节进行了加密处理,即
.text
和il2cpp
节以及.rodata
明显经过加密。有多种修复方法,可以直接将解密后的三个节的数据覆盖到原本的so文件,完成后就可以成功使用il2cppDump导出关键数据和json文件,并且能够使用IDA脚本恢复符号。也可以通过原文件修复从内存导出的文件的节表头和动态符号表,修复完成后也可以进行正常dump和分析。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2F887f5579-9f71-43d1-b537-748556171507%2FUntitled.png?table=block&id=50b385b8-3e5c-4e77-86b5-ecc45dbf0e55&t=50b385b8-3e5c-4e77-86b5-ecc45dbf0e55&width=1566&cache=v2)
前一种方法较为简单,仅需覆盖三个节区的数据即可进行分析。可以对比原动态链接库和内存dump出的文件
.rodata
节数据显然是前后不一致,且内存中的具有可读性,则直接将内存dump文件中对应区域(0xC4E430到0xC4E430 + 0x1BBAA5)
覆盖回原本的动态链接库。同理其他两节数据进行同样的操作即可进行正常分析,简单恢复.text
和il2cpp
节会出现问题,因为switch跳转表信息位于.rodata
。![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2Fe8619c70-8e3c-4ac4-b9e1-92ffededdb37%2FUntitled.png?table=block&id=22a2968f-ebe2-40e0-97a9-de4d45037685&t=22a2968f-ebe2-40e0-97a9-de4d45037685&width=1974&cache=v2)
关于符号恢复:点击File->Script file...
运行il2cppdumper
中的ida_with_struct_py3.py
,需要注意的这个脚本需要运行两次,第一次选择script.json
,第二次选择il2cpp.h
![notion image](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F3d9d581b-1cbe-449e-9723-f2162662aa76%2F254e8fc3-ea80-4f60-9834-e20493b382aa%2FUntitled.png?table=block&id=05ee106d-9329-4aaa-a660-968207ebd97c&t=05ee106d-9329-4aaa-a660-968207ebd97c&width=3111&cache=v2)
0x05 参考
- 作者:LLeaves
- 链接:https://lleavesg.top//article/%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E4%B8%8E%E5%86%85%E5%AD%98Dump%E7%BB%93%E6%9E%9C%E4%BF%AE%E5%A4%8D
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。