04 里面的重定位表没讲完,这里继续,我们说过重定位的类型有很多,04 里面基本只介绍了一种,就是 plt 表。重定位不仅仅发生在代码里面,还会发生在数据里面,比如so程序里面对一些全局变量的引用,它们的重定位信息是放在另外一个表里面:
对应 section 的:
里面的结构体与 rela.plt 的结构体是一样的。以第一项为例:
0x05D690 表示符号地址。
0x0403 表示符号重定位类型。由于它不属于符号表,所以索引为0。0x403 表示的重定位类型是:
#define R_GENERIC_RELATIVE R_AARCH64_RELATIVE
对应的逻辑:
case R_GENERIC_RELATIVE:*reinterpret_cast<ElfW(Addr)*>(reloc) = (load_bias + addend);
load_bias,就是第一个可加载段所在的位置,需要是页对齐的,否则:
load_bias = phdr0_load_address - page_start(phdr0->p_vaddr)
所以,我们只需要在这个位置按照上面的方式计算好地址填进去就行。
打印一下 ls 这个 elf 有哪些类型:
rela.dyn type = 403
rela.dyn type = 401
发现只有两种类型,非常nice。
0x401的逻辑如下(8.0 逻辑):
case R_GENERIC_GLOB_DAT:count_relocation(kRelocAbsolute);MARK(rel->r_offset);TRACE_TYPE(RELO, "RELO GLOB_DAT %16p <- %16p %s\n",reinterpret_cast<void*>(reloc),reinterpret_cast<void*>(sym_addr + addend), sym_name);*reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend);
8.0 相比 2.0 没啥变化,就是多了一个 addend。
傀儡进程
实验失败了,还没分析出问题,可以不用往下看
前面,我们分析了重定位表
-
rela.plt
-
rela.dyn
ls elf 文件里面的重定位类型一共有3种:
-
0x401
-
0x402
-
0x403
我们可以手动将其重定位:
if (rela_type == 0x402)
{uint64_t *sym_save_addr = BASE + rela_ptr->r_offset;uint64_t relocation_type = rela_ptr->r_info & 0xFFFFFFFF;uint64_t symbol_index = rela_ptr->r_info >> 32;uint32_t table_index = symtab_addr[symbol_index].st_name;char *symbol_name = strtab_addr + table_index;// find symbol addrfor (size_t i = 0; i < needed_so_count; i++){void *symbol_addr = dlsym(needed_so_handles[5], symbol_name);if (symbol_addr != NULL){*(uint64_t *)sym_save_addr = (uint64_t)symbol_addr + rela_ptr->r_addend;printf("so name = %s, symbol name = %s, symbol_addr = %10lx\n", needed_so_names[i], symbol_name, *sym_save_addr);break;}}
}if (rela_type == 0x0403)
{uint64_t *target_addr = BASE + p_rela->r_offset;*target_addr = p_rela->r_addend + BASE;p_rela++;
}
else if (rela_type == 0x0401)
{// 现找到符号表地址,再加上 r_addend// find symbol addruint64_t symbol_index = p_rela->r_info >> 32;uint32_t table_index = symtab_addr[symbol_index].st_name;char *symbol_name = strtab_addr + table_index;for (size_t i = 0; i < needed_so_count; i++){void *symbol_addr = dlsym(needed_so_handles[5], symbol_name);if (symbol_addr != NULL){uint64_t *target_addr = BASE + p_rela->r_offset;*target_addr = p_rela->r_addend + symbol_addr;printf("so name = %s, symbol name = %s, symbol_addr = %10lx\n", needed_so_names[i], symbol_name, *target_addr);break;}}/* code */
}
主要是利用 dlopen 与 dlsym 这两个函数,从依赖的 so 文件里面定位到符号地址。拿到地址之后,就根据重定位类型回填符号地址就行。
做完之后,我们就可以主动调用入口地址,然后执行这个 elf 文件:
typedef int (*START)(int x0, int x1, int x2, int x3, int x4, int x5, int x6, int x7, int argc, char *argv, ...);START fun = (START)(ehdr->e_entry + BASE);fun(0, 0, 0, 0, 0, 0, 0, 0,1, cmd,0,0);
由于ls在执行的时候,是使用的栈来传递参数,所以我们使用额外的8个参数来占用寄存器,后面真正的参数 argc,argv 放在第9 与 10 位置,这样这两个参数就分配在了栈中。
我们将 ls 入口地址改成死循环,看一下栈中的数据,好模仿参数传递:
栈中,第一个参数是 1,第二个参数是一个地址,去地址看看:
第二个参数是字符串:/data/local/tmp/ls
后面是一些环境变量。也是放在栈中的。
参数都模仿好之后,调用入口地址,发现报了Segmentation fault
,用 IDA 调试发现是一个空指针错误,寄存器访问了一个 0 地址,搞到晚上1点了都没找到原因先放着了。
如果能成功运行,我们就可以将 ls 这个程序运行到我们的进程中,并执行里面的功能,非常的神奇。