16 - Dobby框架

Dobby是一个全平台InlineHook框架,详情可查看官方文档

编译Dobby

  1. 将代码clone下来,可以将clone的代码存在自建的目录下。
git clone https://github.com/jmpews/Dobby.git --depth=1
  1. 从下载的Dobby源码可以看出,Dobby是跨平台框架,所以项⽬并不是⼀个Xcode⼯程,需要使⽤cmake将⼯程编译成为Xcode⼯程。

  2. 进行Dobby目录,创建一个新的目录用于存放编译后的文件。

cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
  1. 使用cmake进行编译
cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON

编译成功之后显示如下:


image.png

编译完成之后,会生成一个Xcode工程,如下图所示:


image.png
  1. 打开Dobby.xcodeproj,Xcode工程文件,编译工程生成Framework。注意,编译Framework时,需要开启bitcode

    image.png

  2. 编译完成后,⽣成DobbyX.framework

    image.png

  3. 接下来就可以使用DobbyX.framework进行HOOK了。

使用Dobby

  1. 创建工程InlineDemo,添加DobbyX.framework

注意DobbyX.framework拷⻉问题。
Framework⾸次拖⼊⼯程,Xcode不会⾃动帮你拷⻉。运⾏时会发现Framework没有打包进⼊App包,造成DYLD加载时找不到库的错误。

  1. 选择Xcode中的Build Phases,点击+,选择New Copy Files Phase
  2. Copy Files中,将Destination选择Frameworks
    image.png
  3. 点击+,选择DobbyX.framework,点击Add
    image.png
  1. 添加将要被HOOK的静态函数
int sum(int a,int b){
   return a + b;
}
  1. 定义函数指针,⽤于保存被替换函数的地址
static int (*sum_p)(int a,int b);
  1. 定义新函数,⽤此函数替换将要HOOK的函数,该函数的返回值及参数必须⼀致
int mySum(int a,int b) {
   NSLog(@"Sum:%d,",sum_p(a,b));
   return a - b;
}
  1. 在viewDidLoad方法中,调用DobbyHook进行函数的Hook

Dobby的核心的函数

int DobbyHook(void *address, void *replace_call, void **origin_call);

参数说明:

  • address:需要HOOK的函数地址
  • replace_call:新函数地址
  • origin_call:保留原始函数的指针的地址
- (void)viewDidLoad {
   [super viewDidLoad];

   DobbyHook((void *)sum, mySum, (void *)&sum_p);    
}
  1. 在touchesBegan中,调用sum函数
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Sum:%d",sum(10, 20));
}
  1. HOOK成功
[*] ================================
[*] Dobby
[*] ================================
[*] dobby in debug log mode, disable with cmake flag "-DDOBBY_DEBUG=OFF"
[*] [DobbyHook] Initialize at 0x1025ddd4c
[*] [trampoline] Generate trampoline buffer 0x1025ddd4c -> 0x1025ddd6c
[*] [insn relocate] origin 0x1025ddd4c - 12
[*] [insn relocate] relocated 0x104bd4000 - 32
[*] [intercept routing] Active patch 0x1025ddd4c
2021-05-21 10:01:37.712129+0800 InlineDemo[3741:3357493] Hook成功了,原函数结果:30
2021-05-21 10:01:37.712525+0800 InlineDemo[3741:3357493] Sum:-10

Dobby原理分析

1、在调用DobbyHook处打个断点,查看还未HOOK之前,sum函数的汇编。

image.png

我们知道,DobbyHook函数的第一个参数就是sum函数的地址,第二个参数是mySum函数的地址,因此,可以直接从x0、x1寄存器获取sum函数和mySum函数的汇编。

(lldb) register read x0
      x0 = 0x0000000100731d4c  InlineDemo`sum at ViewController.m:18
(lldb) dis -s 0x0000000100731d4c
InlineDemo`sum:
    0x100731d4c <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x100731d50 <+4>:  str    w0, [sp, #0xc]
    0x100731d54 <+8>:  str    w1, [sp, #0x8]
    0x100731d58 <+12>: ldr    w8, [sp, #0xc]
    0x100731d5c <+16>: ldr    w9, [sp, #0x8]
    0x100731d60 <+20>: add    w0, w8, w9
    0x100731d64 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x100731d68 <+28>: ret  

(lldb) register read x1
      x1 = 0x0000000100731d6c  InlineDemo`mySum at ViewController.m:24
(lldb) dis -s 0x0000000100731d6c
InlineDemo`mySum:
    0x100731d6c <+0>:  sub    sp, sp, #0x30             ; =0x30 
    0x100731d70 <+4>:  stp    x29, x30, [sp, #0x20]
    0x100731d74 <+8>:  add    x29, sp, #0x20            ; =0x20 
    0x100731d78 <+12>: stur   w0, [x29, #-0x4]
    0x100731d7c <+16>: stur   w1, [x29, #-0x8]
    0x100731d80 <+20>: adrp   x8, 8
    0x100731d84 <+24>: add    x8, x8, #0x660            ; =0x660 
    0x100731d88 <+28>: ldr    x8, [x8]

2、在调用sum函数处断点停下,查看汇编如下:

InlineDemo`-[ViewController touchesBegan:withEvent:]:
    0x100731e94 <+84>:  mov    w0, #0xa
    0x100731e98 <+88>:  mov    w1, #0x14
->  0x100731e9c <+92>:  bl     0x100731d4c               ; sum at ViewController.m:18
    0x100731ea0 <+96>:  adrp   x8, 3
    0x100731ea4 <+100>: add    x8, x8, #0x28             ; =0x28 

分析:

  • 将0xa(10)赋值给w0。
  • 将0x14(20)赋值给w1。
  • 跳转至0x100731d4c地址。(从第一步可以知道,该地址是sum函数的地址)

3、跳转至0x100731d4c地址,查看汇编

InlineDemo`sum:
->  0x100731d4c <+0>:  adrp   x17, 0
    0x100731d50 <+4>:  add    x17, x17, #0xd6c          ; =0xd6c 
    0x100731d54 <+8>:  br     x17
    0x100731d58 <+12>: ldr    w8, [sp, #0xc]
    0x100731d5c <+16>: ldr    w9, [sp, #0x8]
    0x100731d60 <+20>: add    w0, w8, w9
    0x100731d64 <+24>: add    sp, sp, #0x10             ; =0x10 
    0x100731d68 <+28>: ret    

分析:
此处得到的sum函数汇编与之前的汇编存在差异,具体表现为前面三行不同。
DobbyHook之前sum函数汇编的前三行为:

  • 拉伸栈空间
  • sum函数的参数存入栈空间。

DobbyHook之后sum函数汇编的前三行为:

  • 获取一个地址至x17寄存器
    • 将地址偏移至第0页的首地址:0x100731000
    • 将0x100731000 + 0xd6c = 0x0000000100731d6c
  • 跳转至x17寄存器的值,以下是直接查看x17寄存器的内容
(lldb) register read x17
     x17 = 0x0000000100731d6c  InlineDemo`mySum at ViewController.m:24
(lldb) dis -s 0x0000000100731d6c
InlineDemo`mySum:
    0x100731d6c <+0>:  sub    sp, sp, #0x30             ; =0x30 
    0x100731d70 <+4>:  stp    x29, x30, [sp, #0x20]
    0x100731d74 <+8>:  add    x29, sp, #0x20            ; =0x20 
    0x100731d78 <+12>: stur   w0, [x29, #-0x4]
    0x100731d7c <+16>: stur   w1, [x29, #-0x8]
    0x100731d80 <+20>: adrp   x8, 8
    0x100731d84 <+24>: add    x8, x8, #0x660            ; =0x660 
    0x100731d88 <+28>: ldr    x8, [x8]
    

4、跳转至x17之后,从汇编及函数地址可以看出,就是跳转至mySum函数执行。由于在sum函数的汇编中,只是进行了函数跳转,并未修改寄存器的值,寄存器x30仍然保存了sum函数调用完成之后需要回归的地址。因此,当mySum函数执行完ret之后,回到-[ViewController touchesBegan:withEvent:]:函数中执行。

InlineDemo`mySum:
->    0x100731d6c <+0>:   sub    sp, sp, #0x30             ; =0x30 
    0x100731d70 <+4>:   stp    x29, x30, [sp, #0x20]
    0x100731d74 <+8>:   add    x29, sp, #0x20            ; =0x20 
    0x100731d78 <+12>:  stur   w0, [x29, #-0x4]
    0x100731d7c <+16>:  stur   w1, [x29, #-0x8]
    0x100731d80 <+20>:  adrp   x8, 8
    0x100731d84 <+24>:  add    x8, x8, #0x660            ; =0x660 
    0x100731d88 <+28>:  ldr    x8, [x8]
    0x100731d8c <+32>:  ldur   w0, [x29, #-0x4]
    0x100731d90 <+36>:  ldur   w1, [x29, #-0x8]
    0x100731d94 <+40>:  blr    x8
    0x100731d98 <+44>:  adrp   x8, 3
    0x100731d9c <+48>:  add    x8, x8, #0x8              ; =0x8 
    0x100731da0 <+52>:  stur   w0, [x29, #-0xc]
    0x100731da4 <+56>:  mov    x0, x8
    0x100731da8 <+60>:  mov    x8, sp
    0x100731dac <+64>:  ldur   w9, [x29, #-0xc]
    0x100731db0 <+68>:  mov    x2, x9
    0x100731db4 <+72>:  str    x2, [x8]
    0x100731db8 <+76>:  bl     0x1007324b0               ; symbol stub for: NSLog
    0x100731dbc <+80>:  ldur   w9, [x29, #-0x4]
    0x100731dc0 <+84>:  ldur   w10, [x29, #-0x8]
    0x100731dc4 <+88>:  subs   w0, w9, w10
    0x100731dc8 <+92>:  ldp    x29, x30, [sp, #0x20]
    0x100731dcc <+96>:  add    sp, sp, #0x30             ; =0x30 
    0x100731dd0 <+100>: ret    

(lldb) register read x30
      lr = 0x0000000100731ea0  InlineDemo`-[ViewController touchesBegan:withEvent:] + 96 at ViewController.m

5、在mySum函数中,有通过orgin_sum调用原函数,接下来我们从汇编来看看是如何进行跳转的。
【第1步】获取orgin_sum函数指针

0x100731d80 <+20>:  adrp   x8, 8
0x100731d84 <+24>:  add    x8, x8, #0x660            ; =0x660

(lldb) register read x8
      x8 = 0x0000000100739660  InlineDemo`orgin_sum

【第2步】取出orgin_sum函数指针的内容

0x100731d88 <+28>:  ldr    x8, [x8]

(lldb) register read x8
      x8 = 0x0000000100e84000

【第3步】跳转至x8寄存器中的地址处开始执行

->  0x100e84000: sub    sp, sp, #0x10             ; =0x10 
    0x100e84004: str    w0, [sp, #0xc]
    0x100e84008: str    w1, [sp, #0x8]
    0x100e8400c: ldr    x17, #0x8
    0x100e84010: br     x17
    0x100e84014: .long  0x00731d58                ; unknown opcode
    0x100e84018: udf    #0x1
    0x100e8401c: udf    #0x0

分析:
以上代码的主要工作是:

  • 拉伸栈空间
  • 函数参数存入栈空间
  • 通过当前地址偏移0x8,并获取其内容至x17寄存器
//1. 当前地址偏移0x8
(lldb) p/x 0x100e8400c+0x8
(long) $2 = 0x0000000100e84014

//2. 获取0x0000000100e84014地址中的内容
(lldb) x/2g 0x0000000100e84014
0x100e84014: 0x0000000100731d58 0x0000000000000000

//3. 将0x0000000100731d58地址存入x17寄存器
(lldb) register read x17
     x17 = 0x0000000100731d58  InlineDemo`sum + 12 at ViewController.m:19:12
  • 跳转至x17寄存器的地址处执行

6、从x17寄存器的值可以看出,这个地址其实就是sum函数的第四行汇编,即此处又回到sum函数执行。

7、sum函数执行完成之后,会恢复栈平衡。

总结

  • Dobby原理:运行时对目标函数汇编代码进行前三行替换,注意:这修改的是内存中MachO的代码段__TEXT

  • 当使用Dobby进行HOOK,此时有两种情况:

    • 若不调用原函数,则原函数的汇编不执行。
    • 若需要调用原函数,由于原函数的汇编前三行(即拉伸栈空间)被替换了,此时需要重新拉伸栈空间,并通过偏移跳转至原函数汇编的第四行开始执行。

扩展

在逆向开发中,应用会剥离符号表,我们无法获得符号名称,在调用DobbyHook函数时,其入参就需要从地址着手进行HOOK。

由于应用每次启动时,ASLR偏移地址都不一样,所以不能直接HOOK Mach-O文件中的地址,而是先找到函数在Mach-O中的偏移地址,加上PAGEZERO(0x100000000),再加上本次启动的ASLR偏移地址

接下来以Dobby示例中sum函数为例,尝试从地址进行HOOK。
1、从Mach-O中的__TEXT段,找到sum函数的汇编代码,取出其偏移值(0x5d0c)。偏移值 = 实际运行的内存地址 - ASLR

image.png

2、修改ViewController.m的代码如下:

static uintptr_t sumP = 0x5d0c + 0x100000000;
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    sumP += _dyld_get_image_vmaddr_slide(0);
    DobbyHook((void *)sumP, mySum, (void *)&orgin_sum);
}

3、运行程序,可以看到成功HOOK了sum函数

image.png

注意:
在以上HOOK过程中,需要注意,当代码被修改时,__TEXT段的偏移地址也会发生变化,因此,在获取sum函数汇编代码在Mach-O中的偏移后,代码不能再发生变化,若代码发生了变化,则sum函数汇编代码的偏移值将是一个不正确的值。

以上是在自己的测试案例中进行函数HOOK,因此会涉及代码修改,从而导致地址被修改,但在逆向开发中,分析第三方应用,不会出现这种问题。

你可能感兴趣的:(16 - Dobby框架)