iOS逆向 03:循环选择指针(下)

简介: iOS逆向 03:循环选择指针(下)

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


Switch


  • 1、假设switch语句的分支比较少时(例如3,少于4的时候没有意义),没有必要使用次结构,相当于if-else
  • 2、各个分支常量的差值较大时,编译器会在效率还是内存进行取舍,这时编译器还是会编译成类似于if-else的结构
  • 3、在分支比较多的时候,在编译的时候会生成一个表,不同的case通过跳转表的不同地址,每个地址占四个字节。


案例分析


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

image.png

  • 其中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

image.png

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

image.png

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

image.png下面是实际操作后的结果

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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


image.png

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

image.png


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

image.png

汇编代码分析如下

image.png


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

image.png

  • 疑问1:其中的cmp x9,#0x6,这里的0x6又是怎么来的呢?其实是最小case与最大case(即8-2)的差值
  • 疑问2:为什么要这么比较呢?主要是为了确认传入的参数是否在[最小case,最大case]这个区间内,如果不在,则走default
  • 疑问3:为什么是无符号?因为如果传入比最小case小的值,例如传入的是1,得到的是一个负数,
  • 如果是有符号数运算,一定比区间值小
  • 如果是无符号数运算,是一个非常大的数,一定超过了范围,直接走到default


分析


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

image.png

以下是switch表中的7个字节

image.png

  • 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);
}

image.png


总结


  • 1、假设switch语句的分支比较少时(例如3,少于4的时候没有意义),没有必要使用次结构,相当于if-else
  • 2、各个分支常量的差值较大时,编译器会在效率还是内存进行取舍,这时编译器还是会编译成类似于if-else的结构
  • 3、在分支比较多的时候,在编译的时候会生成一个表(跳转表每个地址四个字节)。


相关文章
|
6月前
|
存储 算法
LeetCode刷题---75. 颜色分类(双指针,循环不变量)
LeetCode刷题---75. 颜色分类(双指针,循环不变量)
|
4月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
5月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
49 0
|
11月前
|
存储 安全 编译器
【C++】内联函数、auto关键字、基于范围的for循环、指针空值nullptr
【C++】内联函数、auto关键字、基于范围的for循环、指针空值nullptr
61 0
指针-实现数组循环移动
指针-实现数组循环移动
106 0
|
存储 安全 编译器
【C++初阶】C++入门(二):引用&&内联函数&&auto关键字&&范围for循环(C++11)&&指针空值nullptr
【C++初阶】C++入门(二):引用&&内联函数&&auto关键字&&范围for循环(C++11)&&指针空值nullptr
|
存储 安全 编译器
引用、 内联函数 、auto关键字(C++11)、基于范围的for循环(C++11)、指针空值---nullptr(C++12)(下)
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在: 1. 类型难于拼写 2. 含义不明确导致容易出错
90 0
|
存储 安全 编译器
引用、 内联函数 、auto关键字(C++11)、基于范围的for循环(C++11)、指针空值---nullptr(C++11)(上)
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
94 0
|
编译器 程序员 C++
【C++】内联函数&&auto关键字&&基于范围的for循环&&指针空值nullptr(下)
【C++】内联函数&&auto关键字&&基于范围的for循环&&指针空值nullptr(下)
【C++】内联函数&&auto关键字&&基于范围的for循环&&指针空值nullptr(下)
|
存储 安全 编译器
【C++】内联函数&&auto关键字&&基于范围的for循环&&指针空值nullptr(上)
【C++】内联函数&&auto关键字&&基于范围的for循环&&指针空值nullptr(上)
【C++】内联函数&&auto关键字&&基于范围的for循环&&指针空值nullptr(上)