https://github.com/leviathansecurity/ftrace
http://blog.51cto.com/haidragon/2134709?tdsourcetag=s_pctim_aiomsg
1、解析elf,获取SHT_SYMTAB和SHT_DYNSYM符号的
st_name目标文件的符号字符串表的索引,其中包含符号名称的字符
st_value
- 在可重定位文件中,st_value 包含所定义符号的节偏移。st_value 表示从 st_shndx 所标识的节的起始位置的偏移。
- 在可执行文件和共享目标文件中,st_value 包含虚拟地址。为使这些文件的符号更适用于运行时链接程序,节偏移(文件解释)会替换为与节编号无关的虚拟地址(内存解释)
- 获取.plt节动态链接表 的地址,并替换对应动态链接地址
2、获取/proc/%d/maps堆栈信息包括(第一个基地址、[heap]、[stack]、其他的)地址与地址范围大小
3、ptrace单步运行程序,获取寄存器值(PTRACE_GETREGS),对eip指针进行追踪,并且在程序基址范围内
ptrace (PTRACE_SINGLESTEP, h->pid, NULL, NULL);
(1)对程序函数调用顺序的追踪,主要是查看跳转指令如下:
struct branch_instr branch_table[64] = {
{"jo", 0x70},
{"jno", 0x71}, {"jb", 0x72}, {"jnae", 0x72}, {"jc", 0x72}, {"jnb", 0x73},
{"jae", 0x73}, {"jnc", 0x73}, {"jz", 0x74}, {"je", 0x74}, {"jnz", 0x75},
{"jne", 0x75}, {"jbe", 0x76}, {"jna", 0x76}, {"jnbe", 0x77}, {"ja", 0x77},
{"js", 0x78}, {"jns", 0x79}, {"jp", 0x7a}, {"jpe", 0x7a}, {"jnp", 0x7b},
{"jpo", 0x7b}, {"jl", 0x7c}, {"jnge", 0x7c}, {"jnl", 0x7d}, {"jge", 0x7d},
{"jle", 0x7e}, {"jng", 0x7e}, {"jnle", 0x7f}, {"jg", 0x7f}, {"jmp", 0xeb},
{"jmp", 0xe9}, {"jmpf", 0xea}, {NULL, 0}
};
(2)控制流追踪: 判断eip是否为跳转指令(并且在程序基址范围,本程序函数跳转)->单步执行进入函数->
//获取地址所在节的节名
sh_src = get_section_by_range(h, eip);
//获取地址所在节的节名
sh_dst = get_section_by_range(h, current_ip);
//输出信息
printf("%s(CONTROL FLOW CHANGE [%s]):%s Jump from %s 0x%lx into %s 0x%lx\n", YELLOW, branch->mnemonic, WHITE,
!sh_src?"<unknown section>":sh_src, eip,
!sh_dst?"<unknown section>":sh_src, current_ip);
(3)函数返回值和参数的获取:判断函数调用call(e8)->计算函数地址(eip + offset + 5) ->判断函数地址是经过重定位的还是本地库的(重定位符号取值:
for (i = 0; i < ehdr64->e_shnum; i++) {
if (!strcmp(&h->elf64->StringTable[shdr64[i].sh_name], ".plt")) {
for (k = 0, j = 0; j < shdr64[i].sh_size; j += 16) {
if (j >= 16) {
h->dsyms[k++].value = shdr64[i].sh_addr + j;
}
}
break;
}
)->对函数汇编code进行追踪(根据获取的函数地址,读取堆栈的值)->根据函数调用约定(
/* X86Y64只支持这一点,基本上在这里解析这个调用约定:
mov %rsp,%rbp
mov $0x6,%r9d
mov $0x5,%r8d
mov $0x4,%ecx
mov $0x3,%edx
mov $0x2,%esi
mov $0x1,%edi
callq 400144 <func>
*/
)读取mov %rsp,%rbp向下的参数的值,并且转换为字符串->遍历读取寄存器(获取参数)->函数的返回地址就等于函数调用前的eip的下一个地址(calldata.retaddr = eip + 5;)->获取到参数后在返回地址手动下个断点->继续ptrace单步运行程序直到到返回地址的断点处断下->获取eax的值(返回值)(
if (calldp->retaddr == eip) {
snprintf(output, sizeof(output), "%s(RETURN VALUE) %s%s = %lx\n", RED, WHITE, calldp->string, eax);
)
(4)获取elf缺失的符号调用:只需要不判断符号地址就ok
(5)通过信号结束程序调用,如:ctrl+c