ARM64汇编09 - 分支指令与模式切换

本文主要讨论两部分内容:

  • 分支指令,B、BL 等

  • v7中的模式切换,arm切thumb,thumb切arm。理解了模式切换就会明白为什么在做 inline hook 时,有些地址需要加上1,加上 1 的作用是什么。

B

B指令是无条件跳转指令。看描述是一个相对于PC地址的跳转,范围是使用 imm26 来描述。26 位表示的范围是 +/-128M。为啥会是 128M 呢?是因为最后的值还需要乘以4。

举个例子:

在 000000000001EDA4 处有一条 B 指令,执行完该指令之后就会直接跳转到 000000000001EDAC 处,IDA使用一条带箭头的线给出了提示。

之前在学习 so 的时候,写过一个死循环指令:

00 00 00 14

我们可以分析一下其具体的每一位:

00010100 00000000 00000000 00000000 ->
0 00101 00000000000000000000000000

因为是跳转到自己位置,所以 imm26 是 0,很好理解。

对比了一下 v7 与 v8 中的死循环指令,发现一个未曾注意到的区别:

v8: 00 00 00 14
v7: fe ff ff ea

在 v7 中,feffff 表示的是 -2,由于 pc = pc + 8,所以算下来,offset = 8 + (-2)* 4 = 0。

但是在 v8 中,offset 就直接是 imm26 的值,这是否说明在 v8 中,pc就是自身的值?暂时未找到具体文档,先留个疑问。

在 V7 中,存在 arm 与 thumb 指令,那么 B 指令跳转会发生模式切换吗?是不会的,编译器可以保证,除非你修改了指令代码,如果在 B 指令中发生了模式切换,肯定是会出问题的,因为 T 位没有发生变化,执行 B 指令的跳转后,CPU 仍会将跳转后的模式当成跳转前的模式来处理。所以,如果你修改了指令代码,切记保证跳转前后的模式一致。

BL

BL指令与B指令几乎一样,唯一的区别就是它跳转的时候会将下一条指令(PC+4,这里也说明了PC的值就是所见即所得)的地址写入到LR寄存器。这条指令表示调用了一个函数。

看V8中的一个例子:

按下 F7:

可以看到发生了两件事:

  • 指令跳转到了目标地址去执行

  • X30 寄存器的值被修改成了BL指令的下一条指令地址,但是这个说法并不准确,因为在 v7 中表现稍微有点不一样。在 arm 模式中,储存的是BL指令的下一条指令地址,在 thumb 模式中,储存的的是下一条指令的地址+1,这个1 是 t 标志位的值。

按下 F7:

看到,LR 寄存器的值变成了 0xB708160F ,这是一个奇数,指令的地址不可能是一个奇数。它的值是 B708160E 的值加上1,这个1表示当从函数跳转回来继续执行的时候,需要以 thumb 模式来执行。

BX

在 v8  中,没有BX指令了,猜测因为BX指令相比B和BL指令的不同之处在于它可以描述模式切换,但是v8中没有模式切换了,所以BX指令就没必要了。

BX后面只能跟寄存器。

.text:000005C8 10 FF 2F E1                   BX              R0

这里有一个BX指令,它跳转到 R0 指向的地址,一般情况下,R0是直接储存的目标地址,这个时候,表示模式不会切换。当它储存的地址是目标地址+1的时候,就会发生模式切换。

BLX

这个也是带模式切换的,v8中也没了。

BLX后可以跟寄存器或者立即数。当 BLX 后面跟的是立即数是,一定会发生模式切换。

比如:

BLX sub_B^EBA934

如果当前是arm模式,执行后变成 thumb 模式,如果当前是 thumb 模式,执行后变成 arm 模式。

BLX 与 BL 一样,也会写 LR 寄存器的值,写入的值规则也是一样,下一条指令地址 + t 标志位的值。

BLX 后面跟寄存器的时候,不一定会发生模式切换。如果想带模式切换,需要将寄存器的值+1,这个时候就会进行从 arm 到 thumb 模式的切换。如果本身就处于 thumb 模式,那么寄存器的值就不需要+1,因为寄存器最后一位的值会直接写入到t标志位。当处于 thumb 模式时,寄存器最后一位是0,就会进行从 thumb 到 arm 模式的切换。

总结一下:BX 与 BLX 指令会有模式切换,当指令跟着寄存器的时候,寄存器的最后一位的值会影响模式的切换,最后一位是 0 ,表示需要切换到 arm 模式,最后一位是 1,表示需要切换到 thumb 模式。当前模式与需要切换的模式不一致时,就会发生模式切换。

V7导出函数的arm与thumb模式

有一个问题:就是libc.so中有许多的导出函数,有些函数可能是以arm模式运行,有些函数可能是要以thumb模式运行,那么当我们使用这些函数的时候,程序是如何知道该函数要以何种模式运行的呢?

我们先看看 IDA 中的导出表的 printf 这个函数:

printf	000528A4

可以看到它的地址是一个偶数。

我们再使用 readelf 程序看看这个符号的信息:

6115: 000528a5    84 FUNC    GLOBAL DEFAULT   13 printf

发现,这个地址是一个奇数,这说明了,跳转到 printf 函数需要切换成 thumb 模式来执行。

IDA中展示的是函数的真实地址,但是没有带模式,readelf 显示的信息中带上了模式信息。

MOV PC, reg

我们上面讨论 BX 指令的时候,BX会直接跳转到目标地址,相当于是改写了 pc 寄存器的值,所以有些人就觉得mov pc, reg 与 BX 指令就是相等的。但是显然不是,因为 mov 不具有切换模式功能。就算想使用 mov 模拟 BX 来进行模式切换,比如 mov pc, target_addr + 1,这个指令只会出异常,而不会切换模式。因为奇数地址是非法的。BX指令实际上是将最后一位给了 t 标志位,而不是真的跳转到一个奇数地址。总之,不要使用 mov 指令来修改 PC 寄存器。

LDR PC, [reg]

这个指令就不同与MOV指令了,它是支持模式切换的,与 BX 指令一样,实际上我们看一下IDA的反汇编代码,会发现plt的调用就是使用的 LDR 指令:

.plt:00000540                               ; int getchar(void)
.plt:00000540                               getchar                                 ; CODE XREF: main:loc_604↓p
.plt:00000540 00 C6 8F E2                   ADR             R12, 0x548
.plt:00000544 01 CA 8C E2                   ADD             R12, R12, #0x1000
.plt:00000548 AC FA BC E5                   LDR             PC, [R12,#(getchar_ptr - 0x1548)]! ; __imp_getchar

这里调用 getchar 函数的时候,就是使用了 LDR 指令,赋值个 PC 的地址有可能是奇数,也有可能是偶数,为奇数表示需要切成 thumb 模式来执行。

再看一个例子:

.text:0000068E D0 B5                         PUSH            {R4,R6,R7,LR}
.text:00000690 02 AF                         ADD             R7, SP, #8
.text:00000692 0C 46                         MOV             R4, info
.text:00000694                               info = R4                               ; unw_proc_info_t_0 *
.text:00000694                               co = R0                                 ; libunwind::AbstractUnwindCursor *
.text:00000694 01 68                         LDR             R1, [co]
.text:00000696 4A 6A                         LDR             R2, [R1,#0x24]
.text:00000698 21 46                         MOV             R1, info
.text:0000069A 90 47                         BLX             R2
.text:0000069A
.text:0000069C 61 68                         LDR             R1, [info,#4]
.text:0000069E 00 20                         MOVS            R0, #0
.text:000006A0 00 29                         CMP             R1, #0
.text:000006A2 04 BF                         ITT EQ
.text:000006A4 4E F2 6B 60                   MOVWEQ          R0, #0xE66B
.text:000006A8 CF F6 FF 70                   MOVTEQ          R0, #0xFFFF
.text:000006AC D0 BD                         POP             {info,R6,R7,PC}

这是一段程序,可以看到,首先将 LR 等寄存器储存起来,我们知道,LR 会由 BX 修改,它的值可能是奇数也可能是偶数。再看最后一条指令,将储存起来的 LR 的值,赋值给了 PC,这就相当于是使用了 LDR 指令了。这个时候,PC 的值就可能是奇数,会发生模式切换。

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值