DWARF 栈回溯 & xCrash 调包侠

admin 2025年5月26日11:23:31评论4 views字数 4140阅读13分48秒阅读模式

迫于 NDK 自带的 _Unwind_Backtrace 之前发生过莫名其妙的 Crash,又没有办法控制这部分的代码,以至于直接把栈回溯功能下掉了。听说 xCrash 里自主实现了一套 dwarf 的解析,迫于需求来研究一下这块原理,当一个调包侠。

DWARF

DWARF 实际上是一套用来存储调试信息的数据格式,到目前已经发布了 5 个版本,具体标准可以到 https://dwarfstd.org/Download.php 下载到。

具体的标准我不是很关心,因为乍一看 PDF 里的东西还是挺多的,包括各种宏、类型、行号、对象、调用等等的信息都会在里面。跟调用栈有关数据类型,可以看《AARCH64平台的栈回溯》,主要用到的是:

  1. FDE(Frame Description Entry): 存了每一帧覆盖的代码地址、以及 CFI,可以用来从 PC 找到当前帧(函数)
  2. CFI(Call Frame Instructions): 是一种抽象指令,用来表示开栈、压栈时的寄存器操作,栈回溯时要解释执行这些指令来还原每一帧的具体寄存器值。

获取 DWARF

xCrash 真正的回溯过程可以从 xcd_elf.c 的 xcd_elf_step 开始看,算起来有 5 种获取 DWARF 数据的途径,可以适配不同编译器,分别是从 ELF 的 .debug_frame.eh_frame 以及从 .gnu_debugdata 里面加载一个压缩的 ELF,里面也可能包含 .debug_frame.eh_frame,arm 下还会再尝试使用 .ARM.exidx

intxcd_elf_step(xcd_elf_t *self, uintptr_t rel_pc, uintptr_t step_pc, xcd_regs_t *regs, int *finished, int *sigreturn){    *finished = 0;    *sigreturn = 0;...//try DWARF (.debug_frame and .eh_frame)if (0 == xcd_elf_interface_dwarf_step(self->interface, step_pc, regs, finished)) return0;//create GNU interface (only once)if (NULL == self->gnu_interface && 0 == self->gnu_interface_created) {        self->gnu_interface_created = 1;        self->gnu_interface = xcd_elf_interface_gnu_create(self->interface);    }//try DWARF (.debug_frame and .eh_frame) in GNU interfaceif (NULL != self->gnu_interface)if (0 == xcd_elf_interface_dwarf_step(self->gnu_interface, step_pc, regs, finished))return0;//try .ARM.exidx#ifdef __arm__if(0 == xcd_elf_interface_arm_exidx_step(self->interface, step_pc, regs, finished)) return0;#endif...}

DWARF 回溯

回溯过程大概分为几步:

  1. 获取完整的寄存器信息(ucontext)
  2. 根据当前的 PC,去获取 FDE
  3. 将寄存器信息代入,解释执行 FDE 里的 CFI 指令
  4. 当每一帧的 CFI 指令执行结束时,代表此时 PC 已经到达了 caller
  5. 直到 PC 为 0 代表结束
intxcd_dwarf_step(xcd_dwarf_t *self, xcd_regs_t *regs, uintptr_t pc, int *finished){xcd_dwarf_fde_t *fde = NULL;xcd_dwarf_loc_t *loc = NULL;int              r   = XCC_ERRNO_NOTFND;//find FDE & CIE from PCif(NULL == (fde = xcd_dwarf_get_fde(self, pc)))    {#if XCD_DWARF_DEBUG        XCD_LOG_DEBUG("DWARF: get FDE failed, step_pc=%"PRIxPTR, pc);#endifgoto end;    }//find LOCATION in the FDE from PCif(NULL == (loc = xcd_dwarf_get_loc(self, fde, pc)))    {#if XCD_DWARF_DEBUG        XCD_LOG_DEBUG("DWARF: get LOC failed, step_pc=%"PRIxPTR, pc);#endifgoto end;    }//eval the actual registersif(0 != (r = xcd_dwarf_eval(self, fde, loc, regs, finished)))    {#if XCD_DWARF_DEBUG        XCD_LOG_DEBUG("DWARF: eval failed, step_pc=%"PRIxPTR, pc);#endifgoto end;    }

getcontext

由上可知,DWARF 回溯需要获取完整的寄存器信息,xCrash 里提供两种方法,一种是 PTRACE_GETREGS

voidxcd_thread_load_regs(xcd_thread_t *self){uintptr_t regs[64]; //big enough for all architecturessize_t    regs_len;#ifdef PTRACE_GETREGSif(0 != ptrace(PTRACE_GETREGS, self->tid, NULL, &regs))    {        XCD_LOG_ERROR("THREAD: ptrace GETREGS failed, errno=%d", errno);        self->status = XCD_THREAD_STATUS_REGS;return;    }    regs_len = XCD_REGS_USER_NUM;#elsestructioveciovec;    iovec.iov_base = &regs;    iovec.iov_len = sizeof(regs);if(0 != ptrace(PTRACE_GETREGSET, self->tid, (void *)NT_PRSTATUS, &iovec))    {        XCD_LOG_ERROR("THREAD: ptrace GETREGSET failed, errno=%d", errno);        self->status = XCD_THREAD_STATUS_REGS;return;    }    regs_len = iovec.iov_len / sizeof(uintptr_t);#endif    xcd_regs_load_from_ptregs(&(self->regs), regs, regs_len);}

不过 ptrace 还得 attach,我的场景跟 crash 不太一样,不适合去 attach。 另一种是从 ucontext 加载

voidxcd_thread_load_regs_from_ucontext(xcd_thread_t *self, ucontext_t *uc){    xcd_regs_load_from_ucontext(&(self->regs), uc);}

这块本意是留给 signal 用的,我们自己做一个 ucontext 也能用,不过 Android 并不原生支持 getcontext,需要借助 Google 的 breakpad,里面实现了一个 breakpad_getcontext,据说 Chrome 里用的也是这个,应该是比较靠谱的,直接嫖了。

调包侠

xCrash 的接口都是输出到一个 fd 里,并且访问不到 xcd_frame_t 的 fields,得先改造一下,把 struct 定义从 *.c 里挪到 *.h 里, 然后调包侠如下:

size_tbacktrace_xcrash(void **buffer, size_t max){xcd_thread_t t;xcd_maps_t * _map;ucontext_t ctx;pid_t pid = getpid();pid_t tid = gettid();    breakpad_getcontext(&ctx);    xcd_maps_create(&_map, pid);    xcd_thread_init(&t, pid, tid);    xcd_thread_load_regs_from_ucontext(&t, &ctx);    xcd_thread_load_frames(&t, _map);    xcd_maps_destroy(&_map);if(t.frames == nullptrreturn0;xcd_frame_t * frame = t.frames->frames.tqh_first;int idx = 0;while (frame != nullptr && idx < max) {        buffer[idx++] = (void*) frame->pc;        frame = frame->link.tqe_next;    }return idx;}

当然这个代码有点缺陷,每次都要 xcd_maps_create 刷新和解析一下 ELF 视图,没有什么缓存(不过性能瓶颈倒也不在这,不刷新的话大概快个 20%,还是在同一个速度量级上,暂时没有性能要求,就先不管了,不要放到高频函数里就可以了。

* [AARCH64平台的栈回溯](https://bbs.kanxue.com/thread-270936.htm)

原文始发于微信公众号(秃头的逆向痴想):DWARF 栈回溯 & xCrash 调包侠

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月26日11:23:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DWARF 栈回溯 & xCrash 调包侠https://cn-sec.com/archives/4095428.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息