GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- APK 虚拟内存布局

1. Android进程虚拟内存空间概览
Android基于Linux内核,每个应用进程都有独立的虚拟地址空间:

64位Android进程虚拟内存布局(简化版):

0x0000000000000000 - 0x0000555555554000  [用户空间下限]
    ├── 0x0000000000000000 - 0x0000000000001000  [NULL页,不可访问]
    └── 0x0000000000001000 - 0x0000555555554000  [用户代码段起始区域]

0x0000555555554000 - 0x00007fffffffffff  [主要用户空间]
    ├── 代码段 (.text)
    ├── 数据段 (.data, .bss)  
    ├── 堆 (Heap)
    ├── 内存映射区 (mmap)
    ├── 共享库区域
    └── 栈 (Stack)

0x8000000000000000 - 0xffffffffffffffff  [内核空间]
2. APK加载过程中的内存布局
2.1 应用启动时的内存分配
典型Android应用内存布局:

[低地址]
0x0000000000400000 - app_process64 (Zygote fork出来的进程)
0x0000000012c00000 - linker64 (动态链接器)
0x0000006f00000000 - [匿名内存区域]
0x0000007000000000 - [共享库加载区域开始]
    ├── 0x0000007000000000 - libc.so
    ├── 0x0000007001000000 - libm.so  
    ├── 0x0000007002000000 - libdl.so
    ├── 0x0000007003000000 - libart.so (ART虚拟机)
    ├── 0x0000007010000000 - libandroid_runtime.so
    └── 0x0000007020000000 - [应用特定的native库]

0x0000007100000000 - [ART堆区域]
    ├── Image Space (boot.art)
    ├── Zygote Space  
    ├── Allocation Space (应用对象)
    └── Large Object Space

0x0000007fff000000 - [栈区域]
[高地址]
2.2 DEX文件在内存中的布局
DEX文件内存映射:

/data/app/com.example.app/base.apk
├── classes.dex → 映射到内存
│   ├── 0x7030000000 - DEX Header
│   ├── 0x7030001000 - String Pool
│   ├── 0x7030010000 - Type Definitions  
│   ├── 0x7030020000 - Method Definitions
│   └── 0x7030100000 - Code Items

├── classes2.dex → 单独映射
└── lib/arm64-v8a/
    ├── libnative1.so → 0x7040000000
    └── libnative2.so → 0x7041000000
3. SO库的详细内存布局
3.1 ELF文件段映射
单个SO文件在内存中的布局:

libnative.so (基址: 0x7040000000)
├── ELF Header        [0x7040000000 - 0x7040000040]
├── Program Headers   [0x7040000040 - 0x7040000200]  
├── .text (代码段)    [0x7040001000 - 0x7040050000] (可执行)
├── .rodata (只读数据) [0x7040050000 - 0x7040060000] (只读)
├── .data (已初始化数据) [0x7040061000 - 0x7040062000] (读写)
├── .bss (未初始化数据)  [0x7040062000 - 0x7040063000] (读写)
├── .got (全局偏移表)   [0x7040063000 - 0x7040064000] (读写)
├── .plt (过程链接表)   [0x7040064000 - 0x7040065000] (可执行)
└── .dynamic (动态信息) [0x7040065000 - 0x7040066000] (只读)
3.2 内存权限设置
# 通过 /proc/[pid]/maps 可以看到实际布局
cat /proc/12345/maps

7040000000-7040001000 r--p 00000000 fd:00 12345 /data/app/.../lib/arm64/libnative.so
7040001000-7040050000 r-xp 00001000 fd:00 12345 /data/app/.../lib/arm64/libnative.so  
7040050000-7040060000 r--p 00050000 fd:00 12345 /data/app/.../lib/arm64/libnative.so
7040061000-7040063000 rw-p 00060000 fd:00 12345 /data/app/.../lib/arm64/libnative.so
7040063000-7040064000 rw-p 00000000 00:00 0     [anon:.bss]
4. ART虚拟机内存布局
4.1 Java堆结构
ART堆内存布局:

[Image Space] - 0x7100000000
├── boot.art (系统类)
└── boot-framework.art

[Zygote Space] - 0x7110000000  
├── 预加载的系统类
└── 字符串常量池

[Allocation Space] - 0x7120000000
├── Young Generation (新生代)
│   ├── Eden Space
│   └── Survivor Space  
└── Old Generation (老年代)

[Large Object Space] - 0x7200000000
└── 大对象存储区域

[Non-Moving Space] - 0x7300000000
└── 不可移动对象
4.2 方法编译和缓存
编译后的代码布局:

[OAT文件映射] - 0x7400000000
├── OAT Header
├── DEX文件数据  
├── 编译后的机器码
│   ├── Method 1 机器码
│   ├── Method 2 机器码
│   └── ...
└── GC Maps

[JIT编译缓存] - 0x7500000000
├── 热点方法机器码
└── 优化后的代码
5. 内存保护和隔离机制
5.1 ASLR (地址空间布局随机化)
每次启动时基址都会变化:

启动1: libnative.so → 0x7040000000
启动2: libnative.so → 0x7f8a000000  
启动3: libnative.so → 0x7c15000000
5.2 内存权限控制
权限标志说明:
r-- : 只读
r-x : 只读+可执行 (代码段)
rw- : 读写 (数据段)
--- : 不可访问 (保护页)
6. 动态加载和链接
6.1 延迟绑定机制
函数调用过程:

应用调用 native_function()
    ↓
PLT表中的跳转指令
    ↓  
GOT表查找实际地址
    ↓
如果未解析,调用动态链接器
    ↓
链接器解析符号并更新GOT
    ↓
跳转到实际函数地址
6.2 共享库依赖关系
依赖链示例:

app_process64
├── linker64
├── libc.so
├── libm.so  
├── libart.so
│   ├── libsigchain.so
│   └── libbacktrace.so
└── 应用SO库
    ├── libssl.so
    ├── libcrypto.so
    └── 其他依赖库
7. 实际内存布局查看方法
你可以通过以下方式查看实际的内存布局:

# 查看进程内存映射
adb shell cat /proc/$(pidof com.shizhuang.duapp)/maps

# 查看进程状态
adb shell cat /proc/$(pidof com.shizhuang.duapp)/status

# 使用Frida查看模块信息
Process.enumerateModules().forEach(function(module) {
    console.log(module.name + ": " + module.base + " - " + module.size);
});
这种内存布局设计确保了:

安全性:通过权限控制和ASLR防止攻击
效率性:共享库减少内存占用
隔离性:每个进程有独立的虚拟地址空间
灵活性:支持动态加载和卸载
这就是为什么你的dump工具能够通过Frida准确定位和提取SO文件的原理基础

 

posted on 2025-08-19 19:45  GKLBB  阅读(20)  评论(0)    收藏  举报