ELF解析06 - 入口参数与傀儡进程

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值