安卓 App 跨进程折腾时, 有一个经常遇到的需求就是要拿到目标 App 的地址布局, 用于远程内存读写或者用于调用相关函数实现注入和执行等等.
要实现这个需求, 通常的做法是读取 /proc/pid/maps 这个文件描述了进程的内存地址布局. 但是在一些特殊情况下, 我们没法直接访问这个文件, 具体不细说.
这里介绍一个那么不访问 /proc/pid/maps 拿到目标进程动态连接库地址分布的方法.
简而言之, 就是通过 App 的父进程, 也就是 zygote, 拿到 soinfo 的首地址, 然后直接远程内存读取遍历这个链表达. 这样我们就能拿到所有 so 的首地址和其他相关的信息了. 因为在 soinfo 挖坑风险很大, 所以很少有 App 在soinfo 挖坑, 所以通常可以直接 process_vm_readv.
我们先看 soinfo 的结构, 以安卓 15 为例
struct soinfo {
private:
char old_name_[SOINFO_NAME_LEN];
public:
const ElfW(Phdr)* phdr;
size_t phnum;
ElfW(Addr) unused0; // DO NOT USE, maintained for compatibility.
ElfW(Addr) base;
size_t size;
uint32_t unused1; // DO NOT USE, maintained for compatibility.
ElfW(Dyn)* dynamic;
uint32_t unused2; // DO NOT USE, maintained for compatibility
uint32_t unused3; // DO NOT USE, maintained for compatibility
soinfo* next;
......
public:
link_map link_map_head;
bool constructors_called;
// When you read a virtual address from the ELF file, add this
// value to get the corresponding address in the process' address space.
ElfW(Addr) load_bias;
bool has_text_relocations;
bool has_DT_SYMBOLIC;
只要计算一下就可以拿到 soinfo->next 和 soinfo->load_bias 这两个关键信息了.
首先第一个问题是 soinfo 链表的 head在哪里? 同样以安卓15为例, head 其实是 linker 里的一个静态变量.
static uint8_t __libdl_info_buf[sizeof(soinfo)] __attribute__((aligned(8)));
static soinfo* __libdl_info = nullptr;
虽然我们不方便访问目标进程的 maps 但是我们可以安全访问 zygote 的 maps. 而 App 是从 zygote fork出来的, 继承了一样的地址空间. 所以我们直接读 zygote 的 maps 然后解析 bionic 的 elf 拿到 __libdl_info_buf 地址. 接着根据这个地址在目标 App 进程里直接 process_vm_readv 读取即可.
思路是对的, 但是实践的时候我们会遇到一个很大的问题, 就是在不同的安卓版本中, soinfo 的结构是有较大差异的. 如果不能抹平这个思路就毫无用处了.
这里再介绍一个暴力扫搜 soinfo 成员结构的方法.
首先另起一个检测进程, 在这个检测进程中调用 dl_iterate_phdr 拿到头两个 so 的 一些信息, 主要是
const char* strtab;
ElfW(Sym)* symtab;
因为这两个成员在所有的版本的soinfo中都是相邻的, 所以紧接着在这个检测进程中, 遍历 maps 中每个可读的区域, 直接搜
static
int get_strtab_symtab(struct dl_phdr_info* info, size_t size, void* ptr)
{
auto* data = reinterpret_cast<Data*>(ptr);
if (data->index != data->target) {
data->index += 1;
return 0; // continue
}
std::cout << "name " << info->dlpi_name << std::endl;
ElfW(Dyn)* dynamic{nullptr};
ElfW(Addr) bias{0};
bool first_phdr{true};
for(size_t i = 0; i < info->dlpi_phnum; ++i) {
if (info->dlpi_phdr[i].p_type == PT_LOAD and first_phdr) {
first_phdr = false;
bias = info->dlpi_addr - info->dlpi_phdr[i].p_vaddr;
}
if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) {
dynamic = reinterpret_cast<ElfW(Dyn)*>(info->dlpi_phdr[i].p_vaddr + bias);
}
}
for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {
switch(d->d_tag) {
case DT_SYMTAB:
data->symtab = reinterpret_cast<ElfW(Sym)*>(bias + d->d_un.d_ptr);
break;
case DT_STRTAB:
data->strtab = reinterpret_cast<const char*>(bias + d->d_un.d_ptr);
break;
}
}
data->base = info->dlpi_addr;
data->name = info->dlpi_name;
std::cout << "strtab " << (void*)data->strtab << std::endl;
std::cout << "symtab " << (void*)data->symtab << std::endl;
std::cout << "phdr " << info->dlpi_phdr << std::endl;
std::cout << "dynamic " << dynamic << std::endl;
std::cout << "bias " << (void*)bias << std::endl;
return 1;
}
static
Data get_soinfo_next_ptr(size_t target)
{
Data data { nullptr, nullptr, target, 0 };
dl_iterate_phdr(get_strtab_symtab, &data);
FILE* fp = fopen("/proc/self/maps", "re");
if (fp == nullptr) {
return { 0, 0, 0, 0, 0, {}, 0 };
}
char line[BUFSIZ];
while(fgets(line, sizeof(line), fp) != nullptr) {
uintptr_t begin;
uintptr_t end;
char r,w;
if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %c%c", &begin, &end, &r, &w) == 4) {
if (r == 'r') {
void* ptr = memmem(reinterpret_cast<void*>(begin), end - begin, &data, sizeof(void*) * 2);
if (ptr != nullptr and ptr != &data) {
printf("%s", line);
for (int i = 0; i < 8; ++i) {
std::cout << (reinterpret_cast<void**>(ptr)[-i+1]) << std::endl;
}
data.next_ptr = &reinterpret_cast<void**>(ptr)[-2];
break;
}
}
}
}
fclose(fp);
return data;
}
由此可以拿到两个 soinfo 的所在区域了. 实际上头两个 soinfo 在地址空间中是距离相当远的, 一个在静态数据区, 一个在堆里, 理论上直接根据地址范围在第一个soinfo 直接可以搜到第二个soinfo的首地址. 给出的代码里我是偷懒直接假设 next 成员是在strtab 前面两个指针处两,在较新的版本中这样假设是可以的.
· 今 日 推 荐 ·
扫描二维码下载学习视频
本文内容来自网络,如有侵权请联系删除
原文始发于微信公众号(逆向有你):安卓逆向 -- 不碰/proc/pid/maps拿到目标App地址分布思路分享
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论