iOS逆向 03:循環(huán)選擇指針(下)

iOS 底層原理 + 逆向 文章匯總

本文主要講解Switch的匯編代碼

Switch

  • 1、假設(shè)switch語句的分支比較少時(shí)(例如3,少于4的時(shí)候沒有意義),沒有必要使用次結(jié)構(gòu),相當(dāng)于if-else

  • 2、各個(gè)分支常量的差值較大時(shí),編譯器會(huì)在效率還是內(nèi)存進(jìn)行取舍,這時(shí)編譯器還是會(huì)編譯成類似于if-else的結(jié)構(gòu)

  • 3、在分支比較多的時(shí)候,在編譯的時(shí)候會(huì)生成一個(gè)表,不同的case通過跳轉(zhuǎn)表的不同地址,每個(gè)地址占四個(gè)字節(jié)。

案例分析

1、當(dāng)case有3個(gè)時(shí)

void func(int a){
    switch (a) {
        case 1:
            printf("打坐");
            break;
        case 2:
            printf("加紅");
            break;
        case 3:
            printf("加藍(lán)");
            break;
        default:
            printf("啥也不干");
            break;
    }
}

int main(int argc, char * argv[]) {
    
    func(1);
  • 斷點(diǎn)調(diào)試func
    image

    其中3個(gè)case的匯編是以if-else的形式

2、如果是4個(gè)case呢?

void func(int a){
    switch (a) {
        case 1:
            printf("打坐");
            break;
        case 2:
            printf("加紅");
            break;
        case 3:
            printf("加藍(lán)");
            break;
        case 4:
            printf("打怪");
            break;
        default:
            printf("啥也不干");
            break;
    }
}

int main(int argc, char * argv[]) {
    
    func(1);
}
  • 此時(shí)w8就是傳入的參數(shù),即w8=4

    image

  • subs w8, w8, #0x1 :subs會(huì)影響目標(biāo)寄存器,即影響w8(注:但不執(zhí)行匯編命令:ni

    image

  • ubfx x9, x9, #0, #32:將x9高32位清零,下面通過修改x9寄存器的值來驗(yàn)證:確實(shí)是將高32位清零(從x9第0位開始,保留低32位,高位用0補(bǔ)齊)

    image

    下面是實(shí)際操作后的結(jié)果
    image

  • 執(zhí)行cmp x9 #0x4 ~ ldr x11,[sp],在這里是x9和4比較,判斷是否等于。此時(shí)的結(jié)果是不等于則繼續(xù)往下執(zhí)行

    image

  • ldrsw x10, [x8, x11, lsl #2](先運(yùn)算后面,再運(yùn)算前面):將x8的值加上x11(需要先將x11左移2位,即0x3左移2位,為1100,為12)作為地址,然后將地址的值給x10,此時(shí)x10的值就是-24 ????(即0xffffffffffffffe8)

    image

    執(zhí)行ldrsw x10, [x8, x11, lsl #2]后的x10
    image

    x8 = 0x0000000102e22828 與最后0x102e22824對(duì)比,可以看出,x8的地址是func執(zhí)行完后下一句代碼的地址
    image

  • add x9, x8, x10:x8的地址加上x10偏移(由于x10為負(fù)數(shù),所以是x8-x10),給到x9.即x9的值為 0x102e22828-0x18(24的十六進(jìn)制)=0x102e22810,即到下面這句,這里就是走到default

    image

  • 執(zhí)行到ldp x29, x30, [sp, #0x10],輸出啥也不干(lldb)

    image

3、修改案例

void func(int a){
    switch (a) {
        case 5:
            printf("打坐");
            break;
        case 6:
            printf("加紅");
            break;
        case 7:
            printf("加藍(lán)");
            break;
        case 8:
            printf("打怪");
            break;
        default:
            printf("啥也不干");
            break;
    }
}



int main(int argc, char * argv[]) {
    func(4);
}

其匯編如下,發(fā)現(xiàn)subs減的是5

image

  • 匯編代碼分析如下


    image

4、修改2:將第二個(gè)case修改為2

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

匯編如下,從這里可以發(fā)現(xiàn),subs后減的是最小的case

image

  • 疑問1:其中的cmp x9,#0x6,這里的0x6又是怎么來的呢?其實(shí)是最小case與最大case(即8-2)的差值,

  • 疑問2:為什么要這么比較呢?主要是為了確認(rèn)傳入的參數(shù)是否在[最小case,最大case]這個(gè)區(qū)間內(nèi),如果不在,則走default

  • 疑問3:為什么是無符號(hào)?因?yàn)槿绻麄魅氡茸钚ase小的值,例如傳入的是1,得到的是一個(gè)負(fù)數(shù),

    • 如果是有符號(hào)數(shù)運(yùn)算,一定比區(qū)間值小

    • 如果是無符號(hào)數(shù)運(yùn)算,是一個(gè)非常大的數(shù),一定超過了范圍,直接走到default

分析

  • 代碼中有一張表,表中有7個(gè)字節(jié),都是負(fù)數(shù),為什么是7個(gè)?因?yàn)?-2=6,加上default,所以是7個(gè)。


    image

    以下是switch表中的7個(gè)字節(jié)


    image
    • switch分支的代碼是連續(xù)的

    • 結(jié)論:表中的字節(jié)數(shù),取決于 最大case-最小case+1(1表示default)

    • 目的:為什么這么創(chuàng)建?用空間換時(shí)間,為了讓代碼效率更高

    • ldrsw 為什么是左移2位?因?yàn)橐粋€(gè)字節(jié)是4位,便于查找下一個(gè)case的值(x11 是 參數(shù)-最小case的值)

switch總結(jié)

  • 1、先將參數(shù)減去最小case

  • 2、先判斷是否是default,如果不是,則在范圍之內(nèi),可以通過表直接拿到地址,反之則default

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

    • 1)地址太長了
    • 2)!!地址在運(yùn)行時(shí)期才會(huì)知道虛擬地址(即ASLR),如果存的是偏移地址,使用時(shí)需要加上ASLR

如果case很大呢?

  • 只要是連續(xù)的就行,因?yàn)閰R編會(huì)減去最小case
  • 如果case之間相差太大了,編譯器會(huì)進(jìn)行優(yōu)化,變成if-else
void func(int a){
    switch (a) {
        case 1:
            printf("打坐");
            break;
        case 400:
            printf("加紅");
            break;
        case 800:
            printf("加藍(lán)");
            break;
        case 8:
            printf("打怪");
            break;
        default:
            printf("啥也不干");
            break;
    }
}

int main(int argc, char * argv[]) {
    func(4);
}
image

總結(jié)

  • 1、假設(shè)switch語句的分支比較少時(shí)(例如3,少于4的時(shí)候沒有意義),沒有必要使用次結(jié)構(gòu),相當(dāng)于if-else

  • 2、各個(gè)分支常量的差值較大時(shí),編譯器會(huì)在效率還是內(nèi)存進(jìn)行取舍,這時(shí)編譯器還是會(huì)編譯成類似于if-else的結(jié)構(gòu)

  • 3、在分支比較多的時(shí)候,在編譯的時(shí)候會(huì)生成一個(gè)表(跳轉(zhuǎn)表每個(gè)地址四個(gè)字節(jié))。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容