iOS逆向 02:函數(shù)本質(zhì)(下)

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

本文主要是講解函數(shù)的參數(shù)、返回值、局部變量在匯編中是如何存儲,以及CPSR標(biāo)志寄存器

函數(shù)的參數(shù)和返回值

  • arm64下,函數(shù)的參數(shù)是存放在x0-x7(w0-w7)這8個寄存器里面的,如果超過8個參數(shù),就會入棧

    • 如果自定義函數(shù)時,參數(shù)最好不要超過6個(因為有兩個隱藏參數(shù) self,_cmd

    • 如果函數(shù)需要多個參數(shù),可以傳入數(shù)組、結(jié)構(gòu)體、指針等類型

  • 函數(shù)的返回值放在x0寄存器

    • 如果返回值大于8個字節(jié),就會利用內(nèi)存?zhèn)鬟f

查看系統(tǒng)的參數(shù)匯編

下面通過系統(tǒng)中對函數(shù)的匯編來查看系統(tǒng)對參數(shù)、返回值是如何操作的

int sum(int a, int b){
    return a + b;
}
- (void)viewDidLoad{
    [super viewDidLoad];
    sum(10, 20);
}
  • 查看匯編,在跳轉(zhuǎn)到sum函數(shù)之前,已經(jīng)將參數(shù)存入了w0、w1


    系統(tǒng)的參數(shù)匯編-01
  • 在sum函數(shù)中,讀取w0、w1,放入w8、w9。然后將相加后的結(jié)果放入w0(即返回值在w0寄存器)


    系統(tǒng)的參數(shù)匯編-02

自己優(yōu)化實現(xiàn)suma

運行發(fā)現(xiàn),其結(jié)果與sum函數(shù)是一致的,結(jié)果都是30

<!--asm.s-->
.text
.global _suma

_suma:
    add x0, x0, x1
    ret 
    
<!--調(diào)用-->
int suma;
- (void)viewDidLoad{
    [super viewDidLoad];
    suma(10, 20);
}

編譯器優(yōu)化

來看以下代碼的匯編

int test(int a, int b, int c, int d, int e, int f, int g, int h, int i){
    return a + b + c + d + e + f + g + h + i;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    test(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
  • 在test函數(shù)斷住,查看匯編

    未優(yōu)化分析-01

    以下是viewDidLoad棧空間的存入分析過程
    未優(yōu)化分析-02

    下圖是對匯編代碼中入棧過程的一個圖示
    未優(yōu)化分析-03

  • 以下是test函數(shù)的匯編分析

    未優(yōu)化分析-04

編譯器優(yōu)化

  • debug模式改成release模式,此時再來查看匯編代碼,發(fā)現(xiàn)沒有test函數(shù),被優(yōu)化掉了


    優(yōu)化分析-01
  • 如果非要執(zhí)行test,可以這樣寫

- (void)viewDidLoad {
    [super viewDidLoad];
    printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9));
}

匯編代碼如下,發(fā)現(xiàn)優(yōu)化后的test函數(shù)在匯編中,其本質(zhì)是一個數(shù),也就是test函數(shù)的返回值。(相當(dāng)于將printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9));直接優(yōu)化成了printf("%d", 45);

優(yōu)化分析-02

通過匯編實現(xiàn)函數(shù)

  • 定義函數(shù)聲明及調(diào)用
int funcA(int a, int b);

- (void)viewDidLoad {
    [super viewDidLoad];
    int a = funcA(10, 20);
    printf("%d", a);
}
  • 匯編實現(xiàn)funcA
.text
.global _funcA, _sum

_funcA:
    sub sp, sp, #0x10
    stp x29, x30, [sp] //保護(hù)lr
    bl _sum
    ldp x29, x30, [sp]
    add sp, sp, #0x10
    ret

_sum:
    add x0, x0, x1
    ret
    
<!--簡寫-->

_funcA:
    stp x29, x30, [sp, #-0x10]!
    bl _sum
    ldp x29, x30, [sp], #0x10
    ret

_sum:
    add x0, x0, x1
    ret
    
<!--巧合:還可以將bl替換成b-->
//b就是簡單跳轉(zhuǎn),在逆向中用于繞過某些代碼(例如安全監(jiān)測)

_funcA:
    b _sum

_sum:
    add x0, x0, x1
    ret

運行結(jié)果如下所示


匯編實現(xiàn)函數(shù)結(jié)果

說明:

  • 關(guān)于b指令:只是跳轉(zhuǎn),不改變lr寄存器
  • 拉伸??臻g和參數(shù)個數(shù)有沒有關(guān)系?:有關(guān)系,當(dāng)參數(shù)越多時,如果寄存器放不下,就需要用到內(nèi)存。就會將棧空間放大,影響??臻g的不僅僅是參數(shù)個數(shù),還有局部變量

函數(shù)的返回值

如果返回值是一個結(jié)構(gòu)體類型,一個寄存器放不下,這時是什么情況?

有以下代碼,運行查看其匯編

struct str {
    int a;
    int b;
    int c;
    int d;
    int f;
    int g;
};
struct Str getStr(int a, int b, int c, int d, int f, int g){
    struct Str str1;
    str1.a = a;
    str1.b = b;
    str1.c = c;
    str1.d = d;
    str1.f = f;
    str1.g = g;
    return str1;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    struct Str str2 = getStr(1, 2, 3, 4, 5, 6);
}
  • 斷點運行,以下是viewDidLoad函數(shù)的匯編
    返回值調(diào)試-01
  • 以下是getStr函數(shù)的匯編代碼


    返回值調(diào)試-02

結(jié)論:如果返回值大于x0的8個字節(jié),也會使用??臻g來存儲

練習(xí)

1、如果函數(shù)的參數(shù)/返回是9個呢?

struct Str{
    int a;
    int b;
    int c;
    int d;
    int e;
    int f;
    int g;
    int h;
    int i;
};

struct Str getStr(int a, int b, int c, int d, int e, int f, int g, int h, int i){
    struct Str str1;
    str1.a = a;
    str1.b = b;
    str1.c = c;
    str1.d = d;
    str1.e = e;
    str1.f = f;
    str1.g = g;
    str1.h = h;
    str1.i = i;
    return str1;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    struct Str str2 = getStr(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
  • 查看viewDidLoad函數(shù)匯編

    返回值練習(xí)-01

  • 查看getStr函數(shù)匯編

    返回值練習(xí)-02

    結(jié)論:發(fā)現(xiàn)前8個參數(shù)是存儲到寄存器,第9個參數(shù)是存儲到棧空間

2、如果結(jié)構(gòu)體參數(shù)是10個呢?

struct Str{
    int a;
    int b;
    int c;
    int d;
    int e;
    int f;
    int g;
    int h;
    int i;
    int j;
};

struct Str getStr(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){
    struct Str str1;
    str1.a = a;
    str1.b = b;
    str1.c = c;
    str1.d = d;
    str1.e = e;
    str1.f = f;
    str1.g = g;
    str1.h = h;
    str1.i = i;
    str1.j = j;
    return str1;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    struct Str str2 = getStr(1, 2, 3, 4, 5, 6, 7, 8, 9);
  • 查看viewDidLoad函數(shù)匯編

    返回值練習(xí)-03

  • 查看getStr函數(shù)匯編

    返回值練習(xí)-04

    • 其中w0-w7都是參數(shù)
    • x8用于返回值參照
    • w9用作臨時變量

結(jié)論:前8個參數(shù)存儲到寄存器,后兩個參數(shù)存儲到??臻g

函數(shù)的局部變量

  • 函數(shù)的局部變量放在里面

分析下面代碼的匯編

int funcB(int a, int b){
    int c = 6;
    return a + b + c;
}

- (void)viewDidLoad {
    [super viewDidLoad]; 
    funcB(10, 20);
}
  • 查看viewDidLoad的匯編

    局部變量-01

  • 查看funcB的匯編

    局部變量-02

總結(jié)

  • 局部變量存儲在空間
  • 參數(shù)的傳遞是用的寄存器,然后將寄存器的值寫入棧

如果函數(shù)有嵌套調(diào)用的情況呢?

int funcB(int a, int b){
    int c = 6;
    int d = funcSum(a, b, c);
    return d;
}

int funcSum(int a, int b, int c){
    int d = a + b + c;
    printf("%d", d);
    return d;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    funcB(10, 20);
}
  • 從viewDidLoad到funcB沒有任何變化


    函數(shù)嵌套-01
  • 來看funcB的匯編


    函數(shù)嵌套-02
    • 參數(shù)入棧,其實本質(zhì)也是對參數(shù)的一個保護(hù)
    • 所以使用的參數(shù)是從棧中獲取,而不是直接通過寄存器獲取
    • stur 操作4個字節(jié)時使用

標(biāo)記/狀態(tài)寄存器

標(biāo)記/狀態(tài)寄存器:主要用于控制程序的執(zhí)行流程

引子

分析下面函數(shù)的匯編

void func(){
    int a = 1;
    int b = 2;
    
    if (a == b){
        printf("a == b");
    }else{
        printf("error");
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    func();
}
  • 查看func的匯編
    引子-01
  • 手動更改cpsr的值(從1000 -> 0100)
    引子-02

    輸入c執(zhí)行,此時的輸出是 a == b
    引子-03

    所以,從這里可以說明,高四位(31-28)是有特殊含義的

CPSR

  • CPU內(nèi)部的寄存器中,有一種特殊的寄存器(對于不同的處理器,個數(shù)和數(shù)據(jù)結(jié)構(gòu)都可能不同)。這種寄存器在ARM中,被稱為狀態(tài)寄存器,即CPSR(current program status register)寄存器

  • CPSR和其他寄存器不一樣,其他寄存器是用來存放數(shù)據(jù)的,都是整個寄存器具有一個含義,而CPSR寄存器是按位起作用的,即它的每一位都有專門的含義,用于記錄特定的信息

  • 注意:CPSR寄存器是32位

  • CPSR的低8位(包括I、F、T和M[4:0])稱為控制位程序無法修改,除非CPU運行于特權(quán)模式下,程序才能修改控制位

  • 中間27-8是保留位,主要作用是為了升級,即更新擴(kuò)展

  • N、Z、C、V均為條件碼標(biāo)志位,它們的內(nèi)容可被算術(shù)或邏輯運算的結(jié)果所改變,并且可以決定某條指令是否被執(zhí)行,意義重大!

    CPSR圖示

N(Negative)標(biāo)志

  • CPSR的第31位是N,符號標(biāo)志位,它記錄相關(guān)指令執(zhí)行后,其結(jié)果是否為負(fù)
    • 如果為負(fù),則 N=1

    • 如果是非負(fù)數(shù),則 N=0

  • 注意:在ARM64的指令集中,有的指令的執(zhí)行時影響狀態(tài)寄存器的。例如adds/sub/or等,他們大都是運算指令(用于進(jìn)行邏輯/算數(shù)運算)
    • adds 執(zhí)行add運算,且會改變標(biāo)志位

案例調(diào)試

查看以下代碼的匯編

void func(){
     asm(
        "mov w0, #0xffffffff\n"
        "adds w0, w0, #0x0\n"
        );
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    func();
}
  • 執(zhí)行mov w0, #0xffffffff:w0賦值-1

    N調(diào)試-01

    查看此時的cpsr,高4位是6(即0110)
    N調(diào)試-02

  • 執(zhí)行adds w0, w0, #0x0:因為w0位負(fù)數(shù),加上0x0后,仍為負(fù)數(shù),所以N標(biāo)志為1

    N調(diào)試-03

    從cpsr的值中可以看出,6變成了8,即 0110 -> 1000,N從0變成了1,符合我們的預(yù)期

Z(zero)標(biāo)志

  • CPSR的第30位是Z,0標(biāo)志位,它記錄相關(guān)指令執(zhí)行后,其結(jié)果是否為0
    • 如果結(jié)果為0,則 Z=1(此時能確定前面兩位為01,即N非負(fù),為0,z為1)

    • 如果結(jié)果不為0,則 Z=0

對于Z的值,可以這樣看,Z標(biāo)記相關(guān)指令的計算結(jié)果是否為0,如果為0,則Z要記錄下”是0“這樣的肯定信息。在計算機(jī)中1表示邏輯真,表示肯定,所以當(dāng)結(jié)果為0時Z=1(表示”結(jié)果為0“)。如果結(jié)果不為0,則Z要記錄下”不是0“這樣的否定信息。在計算機(jī)中0表示邏輯假,表示否定,所以當(dāng)結(jié)果不為0時Z=0,表示”結(jié)果不為0“

案例調(diào)試

目的:驗證z為1時,N必為0

void func(){
    asm(
        "mov w0, #0x0\n"
        "adds w0, w0, #0x0\n"
    );
}
  • 查看此時的CPSR

    Z調(diào)試-01

  • 執(zhí)行mov w0, #0x0adds w0, w0, #0x0,發(fā)現(xiàn)N和Z仍然是0和1

  • 修改:將adds中的0x0更改為0x1

void func(){
    asm(
        "mov w0, #0x0\n"
        "adds w0, w0, #0x1\n"
    );
}

查看CPSR,從圖中可以看出,由于為結(jié)果為非負(fù)數(shù),所以N為0,同時也不為0,則Z為0

Z調(diào)試-02

C(Carry)標(biāo)志

  • CPSR的第29位是C進(jìn)位標(biāo)志位,一般情況下,進(jìn)行無符號數(shù)的運算

  • 加法運算:當(dāng)運算結(jié)果產(chǎn)生了進(jìn)位時(無符號溢出),則C=1,否則C=0

    • 例如 1111 1111 + 1 --> 1 0000 0000,此時的1就保存在C標(biāo)志位
  • 減法運算(包括CMP):當(dāng)運算時產(chǎn)生了借位時(無符號數(shù)溢出),C=0,否則C=1

    C圖示

    • 例如 0000 0001 - 0000 0010 --> 1111 1111,

對于位數(shù)為N的無符號數(shù)來說,其對應(yīng)的二進(jìn)制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相對于最高有效位的更高位,如下所示


無符號的C圖示
進(jìn)位

當(dāng)兩個數(shù)相加時,有可能產(chǎn)生從最高有效位向更高位的進(jìn)位,例如兩個32位數(shù)據(jù)0xaaaaaaaa + 0xaaaaaaaa,將產(chǎn)生進(jìn)位,由于這個進(jìn)位值在32位中無法保存,就說這個進(jìn)位值丟失了。其實CPU在運算時,并不丟棄這個進(jìn)位制,而是記錄在一個特殊的寄存器的某一位上,ARM下就用C位來記錄這個進(jìn)位值,例如下面的指令

mov w0,#0xaaaaaaaa;0xa 的二進(jìn)制是 1010
adds w0,w0,w0; 執(zhí)行后 相當(dāng)于 1010 << 1 進(jìn)位1(無符號溢出) 所以C標(biāo)記 為 1
adds w0,w0,w0; 執(zhí)行后 相當(dāng)于 0101 << 1 進(jìn)位0(無符號沒溢出) 所以C標(biāo)記 為 0
adds w0,w0,w0; 重復(fù)上面操作
adds w0,w0,w0
  • 首先將CPSR變成0x00000000,然后查看執(zhí)行mov w0,#0xaaaaaaaa后的CPSR

    進(jìn)位調(diào)試-01

  • 執(zhí)行第一次adds w0,w0,w0:進(jìn)位1,C為1

    進(jìn)位調(diào)試-02

  • 執(zhí)行第二次adds w0,w0,w0:無進(jìn)位,C由1變成0

    進(jìn)位調(diào)試-03

  • 執(zhí)行第三次adds w0,w0,w0:有進(jìn)位,0變成1

    進(jìn)位調(diào)試-04

  • 執(zhí)行第四次adds w0,w0,w0,無進(jìn)位,1變成0

    進(jìn)位調(diào)試-05

借位

當(dāng)兩個數(shù)據(jù)做減法時,有可能向更高位借位,例如,兩個32位數(shù)據(jù)0x00000000 - 0x000000ff,將產(chǎn)生借位,借位后,相當(dāng)于計算0x100000000 - 0x000000ff,得到0xffffff01這個值,由于借了一位,所以C位用來標(biāo)記借位。C=0,例如下面的指令

mov w0,#0x0
subs w0,w0,#0xff
subs w0,w0,#0xff
subs w0,w0,#0xff
  • 將CPSR修改為0x00000000,執(zhí)行mov w0,#0x0

    借位調(diào)試-01

  • 執(zhí)行第一次subs w0,w0,#0xff:產(chǎn)生了借位,所以C=0

    借位調(diào)試-02

  • 執(zhí)行第二次subs w0,w0,#0xff:無借位,所以C=1

    借位調(diào)試-03

  • 執(zhí)行第三次subs w0,w0,#0xff:無借位,所以C=1

    借位調(diào)試-04

總結(jié)

  • 函數(shù)參數(shù)

    • arm64中,參數(shù)是放在x0-x7的8個寄存器中

    • 如果是浮點數(shù),就會用浮點數(shù)寄存器

    • 如果超過8個參數(shù)就會用棧傳遞

  • 函數(shù)返回值

    • 一般函數(shù)的返回值使用x0寄存器保存

    • 如果返回值大于了8個字節(jié)(x0寄存器大小是8個字節(jié)),就會利用內(nèi)存?zhèn)鬟f返回值

  • 函數(shù)局部變量

    • 局部變量存儲在空間
  • 函數(shù)的嵌套調(diào)用:會將x29、x30寄存器入棧保護(hù)

  • 狀態(tài)(標(biāo)志)寄存器 - CPSR

    • arm64中cpsr寄存器(32位)為狀態(tài)寄存器

    • 最高4位(28、29、30、31)為標(biāo)志位

      • N標(biāo)志(負(fù)標(biāo)記位)
        • 執(zhí)行結(jié)果為負(fù)數(shù)N=1

        • 執(zhí)行結(jié)果非負(fù)數(shù)N=0

      • Z標(biāo)志(0標(biāo)記位)
        • 結(jié)果為0則Z=1

        • 結(jié)果非0則Z=0

      • C標(biāo)志(無符號溢出)
        • 加法:進(jìn)位 C=1,否則C=0

        • 減法:借位 C=0,否則C=1

      • V標(biāo)志(有符號溢出)
        • 正數(shù)+正數(shù)=負(fù)數(shù),則V=1

        • 正數(shù)+負(fù)數(shù)=正數(shù),則V=0

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容