avatar

Catalog
Android逆向中的Canary机制

前言

前段时间在做逆向时,看到一个奇怪的指令MRS X23, #3, c13, c0, #2,查了资料后,解释的也比较模糊。尽管不影响主体程序的分析,但没弄明白总让人感觉不舒服。逆到后头发现,这其实是AArch64状态下对于Canary机制的实现。本篇介绍在进行Android逆向时如何识别Canary机制。

什么是Canary机制

先说Canary,Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。

Canary机制是一种栈保护机制,在函数开始执行的时候,会先在栈底插入cookie信息,在函数真正返回的时候,会验证cookie的值是否合法(栈帧销毁前测试该值是否被改变),如果不合法就停止程序运行 (防止栈溢出发生)。这个cookie信息也被称为Canary。由于Canary机制的实现简单高效,它被普遍用于Linux系统上预防栈溢出的第一道防线。而Android基于Linux微内核,因而也采用这一机制对栈溢出进行检验。本篇主要介绍Canary机制的特征码,其它知识点不再展开,更多关于Canary机制及其绕过策略的,可以参考这篇文章

当然除了Linux上的Canary机制,Windows系统上也有类似的栈保护机制:GS机制,关于GS机制相关的内容可以参考此篇

AArch32状态下的Canary机制

先说明一点,这里我改掉了往常的叫法,不再称作AArch32/64架构。经查阅后发现,AArch64是Armv8-A架构中引入的64位状态;向后兼容Armv7-A和先前32位Arm架构的32位状态称为AArch32。因此,AArch32/64不能称作架构,而是基于架构下的一种运行状态,Armv7/v8-A才被称作是Arm架构。

先来看AArch32状态下的,在程序的开头,穿插着保存现场与参数赋值等处理,这里我们只关心Canary机制的实现部分,即图中用橙色方框框出部分。

这里的指令不难理解,主要说明一下PC存储的值,在Arm体系结构中,由于采用了三级流水线运行,PC指向的值并不是当前执行的指令的地址,而是指向正取指的指令地址,参考下图。

PC真正指向的值为PC+4/PC+8(由指令长度决定)。因此,当执行完ADD R0,PC这条指令后,此时R0存的值即为_stack_chk_guard_ptr。最后会通过该指针取到Canary的值,并将其存储到[SP+0x5C](注:var_1C的值为-0x1C)。

来到结束位置处,橙色方框处与开头时一样,取Canary的值,并将其存放到R1;蓝色方框则是取先前存在栈中的Canary的值到R2,并比较两个寄存器中的Canary值是否相等。若不等,说明这个值被修改过,可能发生了栈溢出,然后会跳转到loc_9566并执行stack_chk_fail

AArch64状态下的Canary机制

接下来,来看AArch64状态下的Canary机制。

首先就是这个在开头提到的MRS X23, #3, c13, c0, #2这条指令,MRS/MSR指令一般是读写CPSR,用来切状态或者设置中断。然而这里的MRS指令则比较奇怪,它并不是去读写CPSR,直观上也很难看明白它到底做了什么,后来在ARM InfoCenter找到MRS指令对应的解释,也没太看明白;通过查阅资料又找到一篇文章,这篇文章对这条指令解释的较为完善了,大概意思就是“以读写权限访问非调试系统寄存器TPIDR_EL0,并将其中的值保存到X23寄存器中”。

这回,弄明白了这条指令的作用后,又有了新的问题,这个TPIDR_EL0寄存器是干什么的?为什么要取它保存的值?文章中对TPIDR_EL0的描述是,软件运行在EL0级别下,可被读写的线程ID寄存器。换句话说,软件在EL0运行级别下,这个寄存器可以存储线程身份信息以便于管理,其作用相当于TLS(Thread Local Storage)。

这里不去讨论TLS的具体内容,但需要知道一点,在PC端(Linux系统)TLS就是存储Canary值的地方。一种常规的绕过Canary机制的手段就是通过覆盖TLS来实现的。因此,在AArch64状态下,起到与TLS类似作用的TPIDR_EL0寄存器,就很可能是存储Canary值的地方。之后经过分析,发现确实是通过TPIDR_EL0寄存器去取Canary值的。

接着回到代码,橙色方框圈住的3行指令可以看出,从TPIDR_EL0+0x28处取出一个值,并将其存入栈中[FP-0x38]处。

再来看程序返回前的代码,和AArch32状态下的执行逻辑几乎一样,分别从原先获取Canary值的位置栈中保存的位置取出Canary的值,并进行比较,若不等,则跳转执行__stack_chk_fail函数进行处理。

Windows系统下的栈保护机制

前面提到了Windows下也有类似的栈保护机制:GS机制。其原理与Canary类似,这里也简单看下。

X86指令集中GS机制的实现

在程序开头,先获取到__security_cookie的值,与ebp进行异或运算后(异或运算的逆运算是其本身),存到[ebp-0x4]的位置处。

在程序返回之前,从[ebp-0x4]中将值取出,再与ebp进行异或运算后,调用函数__security_check_cookie(这里没有push参数进去,显然是fastcall),该函数内部会对异或后的值进行校验,若与__security_cookie的值不符,说明栈中的值被修改了,可能发生了栈溢出,则跳转进行处理,否则正常返回。

经过分析,可以看出Windows下的GS机制,与Linux下的Canary机制的原理完全一样。

X64指令集中GS机制的实现

了解了x86指令集中的GS机制实现,再看x64的那是轻而易举。流程几乎是一样的,唯一不同的在于_security_check_cookie函数中,多了一个对栈中取到的__security_cookie值的高16位进行的校验,若校验失败(值为0),则同样会执行失败,进行对应的处理。

参考资料

  1. Bilibili专栏-Canary机制及绕过策略
  2. CSDN-Windows安全机制-栈保护:GS机制
  3. 博客园-Arm64和AArch64指令集是同一回事吗
  4. AArch64中的线程ID寄存器
  5. ARM InfoCenter-MRS指令
  6. 语雀-线程栈|TLS绕过Canary
Author: cataLoc
Link: http://cataloc.gitee.io/blog/2021/04/24/Android%E9%80%86%E5%90%91%E4%B8%AD%E7%9A%84Canary%E6%9C%BA%E5%88%B6/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶