首页
论坛
专栏
课程

[原创]如何写一个Android inline hook框架_新增功能或修复bug_浮点相关的问题

2020-1-8 12:24 1352

[原创]如何写一个Android inline hook框架_新增功能或修复bug_浮点相关的问题

2020-1-8 12:24
1352

之前的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和多参与完善吧,一个人实在没太多时间,感谢



2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最新回复 (3)
卓桐 4 2020-1-8 12:33
2
0
我对arm64指令集的了解不如arm32,如果还有哪些错误,请大佬们多多提出
addhaloka 2020-1-8 14:48
3
0
感谢分享
Editor 2020-1-11 15:55
4
0
感谢分享!
游客
登录 | 注册 方可回帖
返回