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?addr
????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?*)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?addr
????uint64_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 这个程序运行到我们的进程中,并执行里面的功能,非常的神奇。