如何写一个Android inline hook框架_新增功能或修复bug_浮点相关的问题
2020-01-18 18:57:00 Author: mp.weixin.qq.com(查看原文) 阅读量:75 收藏

本文为看雪论坛优秀文章

看雪论坛作者ID:卓桐

之前的hook框架已经可以满足绝大多数需求,但是设计之初未详细的看完arm64下浮点相关的部分,以为和arm一样也是通过通用寄存器、栈传递,现在修复这一部分,这个bug只对dump读写寄存器相关的部分有影响,对replace部分(采用定义一个和被hook函数原型一致的函数)无影响。

arm应该不用保存浮点相关的寄存器

目前来看arm浮点参数,float参数占用一个寄存器或者栈,double占用相邻的两个寄存器或栈;返回值float还是占用寄存器r0,double占用寄存器r0和r1。

所以可以不考虑保存浮点寄存器s0-s31/d0-d31/q0-q15。如果有例外情况请告诉我,这些寄存器应该是NEON相关的,所以armv7-a/r以下的应该是不存在这些寄存器和指令的(好像有的资料说armv6也有类似的浮点指令)?



如上图,传参通过r0、r1,返回通过r0。

arm64需要保存

但是arm64传参使用的是float从s0开始为第一个浮点参数,double从d0开始为第一个浮点参数,返回值float为s0,double为d0,所以应该保存浮点寄存器和提供接口。



如上图,传参通过D0、D1,返回通过D0。


如上图,传参通过S0、S1,返回通过S0。


如上图,传参通过W0、S0、S1,返回通过S0。

arm64代码实现

按说把浮点寄存器存储在栈的最底部比较好,但是代码改动稍微有点大(其实是偏移都要改一遍),所以放在最顶层吧。

.data
 
_dump_start: //用于读写寄存器/栈,需要自己解析参数,不能读写返回值,不能阻止原函数(被hook函数)的执行
                                //从行为上来我觉得更偏向dump,所以起名为dump。
    sub     sp, sp, #0x20; //跳板在栈上存储了x0、x1,但是未改变sp的值
 
    mrs     x0, NZCV
    str     x0, [sp, #0x10]; //覆盖跳板存储的x1,存储状态寄存器
    str     x30, [sp]; //存储x30
    add     x30, sp, #0x20
    str     x30, [sp, #0x8]; //存储真实的sp
    ldr     x0, [sp, #0x18]; //取出跳板存储的x0
 
save_x0_x29://保存寄存器x0-x29
    sub     sp, sp, #0xf0; //分配栈空间
    stp     X0, X1, [SP]; //存储x0-x29
    stp     X2, X3, [SP,#0x10]
    stp     X4, X5, [SP,#0x20]
    stp     X6, X7, [SP,#0x30]
    stp     X8, X9, [SP,#0x40]
    stp     X10, X11, [SP,#0x50]
    stp     X12, X13, [SP,#0x60]
    stp     X14, X15, [SP,#0x70]
    stp     X16, X17, [SP,#0x80]
    stp     X18, X19, [SP,#0x90]
    stp     X20, X21, [SP,#0xa0]
    stp     X22, X23, [SP,#0xb0]
    stp     X24, X25, [SP,#0xc0]
    stp     X26, X27, [SP,#0xd0]
    stp     X28, X29, [SP,#0xe0]
 
save_v0_v31:
    sub     sp, sp, #0x200; //分配栈空间
    #stp     D0, D1, [SP]; //理论上是不是只保存存储double的部分就可以?
    stp     Q0, Q1, [SP]; //不支持V0-V31,但是支持Q0-Q31,一致,每个寄存器占128位,低64位保存double,低32位float
    stp     Q2, Q3, [SP, #0x20];
    stp     Q4, Q5, [SP, #0x40];
    stp     Q6, Q7, [SP, #0x60];
    stp     Q8, Q9, [SP, #0x80];
    stp     Q10, Q11, [SP, #0xa0];
    stp     Q12, Q13, [SP, #0xc0];
    stp     Q14, Q15, [SP, #0xe0];
 
    stp     Q16, Q17, [SP, #0x100];
    stp     Q18, Q19, [SP, #0x120];
    stp     Q20, Q21, [SP, #0x140];
    stp     Q22, Q23, [SP, #0x160];
    stp     Q24, Q25, [SP, #0x180];
    stp     Q26, Q27, [SP, #0x1a0];
    stp     Q28, Q29, [SP, #0x1c0];
    stp     Q30, Q31, [SP, #0x1e0];
 
call_onPreCallBack://调用onPreCallBack函数,第一个参数是sp,第二个参数是STR_HK_INFO结构体指针
    mov     x0, sp; //x0作为第一个参数,那么操作x0=sp,即操作栈读写保存的寄存器
    ldr     x1, _hk_info;
    ldr     x3, [x1]; //onPreCallBack
    bl      get_lr_pc; //lr为下条指令
    add     lr, lr, #8; //lr为blr x3的地址
    str     lr, [sp, #0x308]; //lr当作pc,覆盖栈上的x0
    blr     x3
 
restore_regs://恢复寄存器
    #ldp     D0, D1, [SP];
    ldp     Q0, Q1, [SP];
    ldp     Q2, Q3, [SP, #0x20];
    ldp     Q4, Q5, [SP, #0x40];
    ldp     Q6, Q7, [SP, #0x60];
    ldp     Q8, Q9, [SP, #0x80];
    ldp     Q10, Q11, [SP, #0xa0];
    ldp     Q12, Q13, [SP, #0xc0];
    ldp     Q14, Q15, [SP, #0xe0];
 
    ldp     Q16, Q17, [SP, #0x100];
    ldp     Q18, Q19, [SP, #0x120];
    ldp     Q20, Q21, [SP, #0x140];
    ldp     Q22, Q23, [SP, #0x160];
    ldp     Q24, Q25, [SP, #0x180];
    ldp     Q26, Q27, [SP, #0x1a0];
    ldp     Q28, Q29, [SP, #0x1c0];
    ldp     Q30, Q31, [SP, #0x1e0];
    add     sp, sp, #0x200;
 
    ldr     x0, [sp, #0x100]; //取出状态寄存器
    msr     NZCV, x0
 
    ldp     X0, X1, [SP]; //恢复x0-x29寄存器
    ldp     X2, X3, [SP,#0x10]
    ldp     X4, X5, [SP,#0x20]
    ldp     X6, X7, [SP,#0x30]
    ldp     X8, X9, [SP,#0x40]
    ldp     X10, X11, [SP,#0x50]
    ldp     X12, X13, [SP,#0x60]
    ldp     X14, X15, [SP,#0x70]
    ldp     X16, X17, [SP,#0x80]
    ldp     X18, X19, [SP,#0x90]
    ldp     X20, X21, [SP,#0xa0]
    ldp     X22, X23, [SP,#0xb0]
    ldp     X24, X25, [SP,#0xc0]
    ldp     X26, X27, [SP,#0xd0]
    ldp     X28, X29, [SP,#0xe0]
    add     sp, sp, #0xf0
 
    ldr     x30, [sp]; //恢复x30
    add     sp, sp, #0x20; //恢复为真实sp
 
call_oriFun:
    stp     X1, X0, [SP, #-0x10]; //因为跳转还要占用一个寄存器,所以保存
    ldr     x0, _hk_info;
    ldr     x0, [x0, #8]; //pOriFuncAddr
    br      x0
 
get_lr_pc:
                                                    ret; //仅用于获取LR/PC
 
_hk_info: //结构体STR_HK_INFO
.double 0xffffffffffffffff
 
_dump_end:
 
.end

编译器貌似并不支持存储V0-V31,还是像armv7的neon一样使用Q0-Q31(armv7只有Q0-Q15,16个128位的寄存器)。
Q0-Q31可以拆成D0-D31,D0占Q0的低64位,D1占Q1的低64位;同时也可以拆成S0-S31,S0占Q0的低32位,S1占Q1的低32位。不清楚为什么这么设计,感觉有些浪费。
 
为了方便这里定义一个联合体,qregs就是128位的Q0-Q31,dregs为了方便解析double,同理fregs为了方便解析float。
#if defined(__aarch64__)
 
union my_neon_regs {
    long double qregs[32];
    double dregs[32][2];
// float fregs[64*2];
    float fregs[32][4];
};
 
#define dregs(i) dregs[i][0]
#define fregs(i) fregs[i][0]
 
struct my_pt_regs {
    union my_neon_regs neon;
    __u64 uregs[31];
    __u64 sp;
    __u64 pstate; //有时间应该修复,pc在前,但是涉及到栈和生成shellcode都要改,先这么用吧,和系统结构体有这点不同
    __u64 pc;
 
};
通过这样的方式操作浮点相关的寄存器,完成对double、float参数、返回值的读写。
else if (pInfo->pBeHookAddr == retDou) {
#if defined(__aarch64__)
// LE("d0=%.15f, d1=%.15f, d2=%.15f, d3=%.15f", regs->neon.dregs[0], regs->neon.dregs[1], regs->neon.dregs[2], regs->neon.dregs[3]);
            LE("d0=%.15f, d1=%.15f, d2=%.15f, d3=%.15f", regs->neon.dregs(0), regs->neon.dregs(1), regs->neon.dregs(2), regs->neon.dregs(3));
            LE("s0=%.15f, s1=%.15f, s2=%.15f, s3=%.15f", (float)regs->neon.fregs(0), regs->neon.fregs(1), regs->neon.fregs(2), regs->neon.fregs(3));
            LE("q0=%.15llf, q1=%.15llf, q2=%.15llf, q3=%.15llf", (long double)regs->neon.qregs[0], regs->neon.qregs[1], regs->neon.qregs[2], regs->neon.qregs[3]);
#endif
        } else if (pInfo->pBeHookAddr == retFlo) {
#if defined(__aarch64__)
            LE("d0=%.15f, d1=%.15f, d2=%.15f, d3=%.15f", regs->neon.dregs(0), regs->neon.dregs(1), regs->neon.dregs(2), regs->neon.dregs(3));
            LE("s0=%.15f, s1=%.15f, s2=%.15f, s3=%.15f", (float)regs->neon.fregs(0), regs->neon.fregs(1), regs->neon.fregs(2), regs->neon.fregs(3));
            LE("q0=%.15llf, q1=%.15llf, q2=%.15llf, q3=%.15llf", (long double)regs->neon.qregs[0], regs->neon.qregs[1], regs->neon.qregs[2], regs->neon.qregs[3]);
#endif
        }
例如这样操作。
 


部分inline hook的又一个检测方式

在arm64下很多框架都是采用X16、X17,多是X17寄存器完成跳转,那么简单分析下这个流程:

1、跳板/shellcode类似如下:
LDR    X17, 8;
BR    x17;
ADDR(64) //hook函数地址
2、hook函数内调用原函数,那么执行完备份/修复的几条指令后,还要使用X17跳回被hook的函数。
 
x16、x17一般只在plt中使用,那么x17一般都是保存的一个函数的地址。
 
但是上面的步骤2肯定不是跳到函数的起始地址,而是函数起始地址+16(或者更大,看修复指令的情况)。
那么根据自身的业务场景在容易被hook的函数内检测X17寄存器的值是不是就是当前函数的地址+(16到32),如果在这个范围内,那么就可以认为当前函数被hook了。
实现方式多种多样,例如内嵌汇编获取X17寄存器的值。
 
同样的arm也可以检测R12寄存器,但是比较少的inlineHook框架会在arm下使用R12寄存器,因为可以操作pc,但是不是完全没有,我见过一个。
 
当然其实要想检测hook还有更多方法。

各位大佬多提bug和多参与完善吧,一个人实在没太多时间,感谢。

- End -

看雪ID:卓桐

https://bbs.pediy.com/user-670707.htm 

*本文由看雪论坛  卓桐  原创,转载请注明来自看雪社区

推荐文章++++

新手熊猫烧香学习笔记

GandCrab v5.2 分析

定制Xposed框架

Android 4.4 从url.openConnection到DNS解析

通过一道pwn题详细分析retdlresolve技术

好书推荐


公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]
“阅读原文”一起来充电吧!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458302939&idx=1&sn=d18e1bde0d37d7c2a104ffaeeee223fa&chksm=b181895186f60047891d8d615e6e38cbd38d1ee1eba3635a36a190829d331ec9020c684bc81a#rd
如有侵权请联系:admin#unsafe.sh