这次的 APP 还是 2015 年移动安全挑战赛(看雪&阿里主办)中的第二题,通用的解法参考这个系列的 上一篇文章,这篇文章再介绍一种取巧的解法,这种解法并不通用
其实在别的文章里也有提到过这种取巧的解法,但是同样不够详细,经过摸索并成功实践之后,我想再记录下来
直接跳到 IDA 载入 SO 的那一步,前面的步骤跟之前的都是一样的,留意到这里调用了 __android_log_print
该函数的原型是:
int __android_log_print(int prio, const char *tag, const char *fmt, ...)
第一个参数是 LOG 的优先级。取值可以是
typedef enum android_LogPriority { ANDROID_LOG_UNKNOWN = 0, ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ } android_LogPriority;
这个枚举类型里的任意一个
第二个参数是 LOG 的标签的指针,指针指向的标签通常用于过滤 LOG
第三个参数是 LOG 的格式的指针,该指针指向的通常就是 LOG 的内容
回忆一下 ARM 里调用函数时传参的规则,如果不是对象调用类的方法,在普通的函数调用里,caller 会把前四个参数分别放到 R0, R1, R2, R3 这四个寄存器中,这里也不例外。所以在调用 __andoid_log_print 的时候,R0 对应的是 LOG 的优先级,通过 MOV R0, #4
知道这里的优先级是 ANDROID_LOG_INFO;R1 对应的是 LOG 的标签,通过计算得出这里 R1 的地址为 0x6340,跳转过去发现是 bss 段,bss 段保存的是未初始化的全局变量,所以 R1 的值应该是程序执行过程中通过赋值符赋值的;同样,对应于 LOG 内容指针的 R2 也是如此。
来看看这里的这个 __android_log_print 会输出什么
运行 APP,打开 DDMS,随便输入一个密码,点击 “输入密码” 按钮,然后查看 logcat 选项卡输出的 LOG,发现输出的 LOG 太多了,过滤一下,得到如下 LOG
再来分析下函数的流程
通过对上图开始处的 R3 和 R1 值的比较分别进入不同的分支,具体为:如果 R3 和 R1 一直相等(一个字符一个字符比较),程序进入左边的分支,最后返回 1(true),如果 R3 和 R1 有一次不相等,直接进入右边的分支,返回 0(false)
而 R3 的值是 R2 保存的地址中的值,往上查看 R2 的地址中保存的值
就是我们之前看到的那个错误的答案
很巧,R2 保存的是既是我们要的答案的地址,也是 __android_log_print 函数的第三个参数,因此我们只需要把 __android_log_print 函数往下移,让它在将答案的地址赋给 R2 之后再调用就可以通过 LOG 来知道答案了。
具体的做法是:将之前的 __android_log_print 调用和之后的对 R0, R1, R2 的无关紧要的赋值 NOP 掉,然后在将 R2 的值赋为答案的之后再调用 __android_log_print。这里有几点是需要注意的:
- 答案的地址是受到 R1 的影响的,因此要把先前的 R1 保存下来,我的做法是把下面的 R1 改成 R3,然后把
LDR R2, [R1, R7]
改成LDR R2, [R3, R7]
,这样 R2 的值不受影响,也能保证 tag 依然是之前的那个 tag
- 如何才能重新调用 __android_log_print 呢? 首先要明确这里能用调用 __android_log_print 的方法来输出答案的原因是程序本身调用了这个函数,因此我们可以通过 PLT 来获得这个函数在这个程序中的偏移,该偏移是位置无关的。关于 GOT 和 PLT,这里引用一下 通过 GDB 调试理解 GOT/PLT
GOT(Global Offset Table):全局偏移表用于记录在 ELF 文件中所用到的共享库中符号的绝对地址。在程序刚开始运行时,GOT 表项是空的,当符号第一次被调用时会动态解析符号的绝对地址然后转去执行,并将被解析符号的绝对地址记录在 GOT 中,第二次调用同一符号时,由于 GOT 中已经记录了其绝对地址,直接转去执行即可(不用重新解析)。
PLT(Procedure Linkage Table):过程链接表的作用是将位置无关的符号转移到绝对地址。当一个外部符号被调用时,PLT 去引用 GOT 中的其符号对应的绝对地址,然后转入并执行。
双击 __android_log_print
回到我们要调用的地方,是在 R2 的值赋为答案的之后
最后修改的结果:
打包签名并重新运行程序,并查看 LOG
看到答案啦~