iOS开发逆向之循环选择指针(下)

作者: 51CTO博文  更新时间:2021-05-07 19:11:46  原文链接


本文主要讲解 Switch 的汇编代码

Switch

  • 1、假设 switch 语句的分支比较少时(例如3,少于4的时候没有意义),没有必要使用次结构,相当于 if-else

  • 2、各个 分支常量的差值较大 时,编译器会在效率还是内存进行取舍,这时编译器还是会编译成类似于if-else的结构

  • 3、在 分支比较多 的时候,在编译的时候会 生成一个表 ,不同的case通过跳转表的不同地址,每个地址占四个字节。

案例分析

首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群: 130595548 ,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

1、当case有3个时

void func(int a){    switch (a) {        case 1:            printf("打坐");            break;        case 2:            printf("加红");            break;        case 3:            printf("加蓝");            break;        default:            printf("啥也不干");            break;
    }
}int main(int argc, char * argv[]) {

    func(1);
  • 断点调试func

    其中3个case的汇编是以 if-else 的形式

2、如果是4个case呢?

void func(int a){    switch (a) {        case 1:            printf("打坐");            break;        case 2:            printf("加红");            break;        case 3:            printf("加蓝");            break;        case 4:            printf("打怪");            break;        default:            printf("啥也不干");            break;
    }
}int main(int argc, char * argv[]) {

    func(1);
}
  • 此时 w8 就是传入的参数,即 w8=4

  • subs w8, w8, #0x1 :subs会影响目标寄存器,即影响w8(注:但不执行汇编命令: ni

  • ubfx x9, x9, #0, #32 :将x9 高32位清零 ,下面通过修改x9寄存器的值来验证:确实是将高32位清零(从x9第0位开始,保留低32位,高位用0补齐)

    下面是实际操作后的结果

  • 执行 cmp x9 #0x4ldr x11,[sp] ,在这里是x9和4比较,判断是否等于。此时的结果是不等于则继续往下执行

  • ldrsw x10, [x8, x11, lsl #2] (先运算后面,再运算前面):将x8的值加上x11(需要先将x11左移2位,即0x3左移2位,为1100,为12)作为地址,然后将地址的值给x10,此时x10的值就是-24 ????(即0xffffffffffffffe8)

    执行 ldrsw x10, [x8, x11, lsl #2] 后的x10

    x8 = 0x0000000102e22828 与最后 0x102e22824 对比,可以看出,x8的地址是func执行完后下一句代码的地址

  • add x9, x8, x10 :x8的地址加上x10偏移(由于x10为负数,所以是 x8-x10 ),给到x9.即x9的值为  0x102e22828-0x18(24的十六进制)=0x102e22810 ,即到下面这句,这里就是走到 default

  • 执行到 ldp x29, x30, [sp, #0x10] ,输出 啥也不干(lldb)

3、修改案例

void func(int a){    switch (a) {        case 5:            printf("打坐");            break;        case 6:            printf("加红");            break;        case 7:            printf("加蓝");            break;        case 8:            printf("打怪");            break;        default:            printf("啥也不干");            break;
    }
}int main(int argc, char * argv[]) {
    func(4);
}

其汇编如下,发现 subs 减的是5

  • 汇编代码分析如下

4、修改2:将第二个case修改为2

void func(int a){    switch (a) {        case 5:            printf("打坐");            break;        case 2:            printf("加红");            break;        case 7:            printf("加蓝");            break;        case 8:            printf("打怪");            break;        default:            printf("啥也不干");            break;
    }
}

汇编如下,从这里可以发现,subs后 减的是最小的case

  • 疑问1 :其中的 cmp x9,#0x6 ,这里的0x6又是怎么来的呢?其实是 最小case与最大case(即8-2)的差值

  • 疑问2 :为什么要这么比较呢?主要是为了确认传入的参数 是否在[最小case,最大case]这个区间内 ,如果 不在,则走default

  • 疑问3 :为什么是无符号?因为如果传入比最小case小的值,例如传入的是1,得到的是一个负数,

    • 如果是 有符号数 运算,一定比区间值小

    • 如果是 无符号数 运算,是一个非常大的数,一定超过了范围,直接走到default

分析

  • 代码中有一张表,表中有7个字节,都是负数,为什么是7个?因为8-2=6,加上default,所以是7个。

    以下是switch表中的7个字节

    • switch分支的代码是连续的

    • 结论 :表中的字节数,取决于  最大case-最小case+1(1表示default)

    • 目的 :为什么这么创建? 用空间换时间 ,为了让代码效率更高

    • ldrsw 为什么是左移2位?因为一个字节是4位,便于查找下一个case的值(x11 是 参数-最小case的值)

switch总结

  • 1、先将参数减去最小case

  • 2、先判断是否是default,如果不是,则在范围之内,可以通过表直接拿到地址,反之则default

  • 疑问 :表中为什么不直接存地址,而是存差值?

    • 1)地址太长了
    • 2)!!地址在运行时期才会知道虚拟地址(即ASLR),如果存的是偏移地址,使用时需要加上ASLR

如果case很大呢?

  • 只要是连续的就行,因为汇编会减去最小case
  • 如果case之间相差太大了,编译器会进行优化,变成if-else
void func(int a){    switch (a) {        case 1:            printf("打坐");            break;        case 400:            printf("加红");            break;        case 800:            printf("加蓝");            break;        case 8:            printf("打怪");            break;        default:            printf("啥也不干");            break;
    }
}int main(int argc, char * argv[]) {
    func(4);
}

首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群: 130595548 ,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

总结

  • 1、假设 switch 语句的分支比较少时(例如3,少于4的时候没有意义),没有必要使用次结构,相当于 if-else

  • 2、各个分支常量的 差值较大 时,编译器会在效率还是内存进行取舍,这时编译器还是会编译成 类似于if-else 的结构

  • 3、在 分支比较多 的时候,在编译的时候会 生成一个表 (跳转表每个地址四个字节)。