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

本文的主要目的是理解函數(shù)棧以及涉及的相關(guān)指令

在講函數(shù)的本質(zhì)之前曹动,首先需要講下以下幾個(gè)概念棧、SP牛曹、FP

常識(shí)

  • 棧:是一種具有特殊的訪問方式的存儲(chǔ)空間(即先進(jìn)后出 Last In Out First善已, LIFO
  • 高地址往低地址存數(shù)據(jù)(存:高-->低
- 棧空間開辟:往低地址開辟(`開辟:高-->低`)
復(fù)制代碼

SP和FP寄存器

  • SP寄存器:在任意時(shí)刻會(huì)保存棧頂?shù)牡刂?/code>

  • FP寄存器(也稱為x29寄存器):屬于通用寄存器渤涌,但是在某些時(shí)刻(例如函數(shù)嵌套調(diào)用時(shí))可以利用它保存棧底的地址

注意:

  • arm64開始,取消了32位的LDM把还、STM实蓬、PUSH、POP指令吊履,取而代之的是 ldr/ldp安皱、str/stp(r和p的區(qū)別在于處理的寄存器個(gè)數(shù),r表示處理1個(gè)寄存器艇炎,p表示處理兩個(gè)寄存器)
  • arm64中酌伊,對(duì)棧的操作是16字節(jié)對(duì)齊的!W鹤佟居砖!

以下是arm64之前和arm64之后的一個(gè)對(duì)比 !


  • 在arm64之前,棧頂指針是壓棧時(shí)一個(gè)數(shù)據(jù)移動(dòng)一個(gè)單元

  • 在arm64開始驴娃,首先是從高地址往低地址開辟一段椬嗪颍空間(由編譯器決定),然后再放入數(shù)據(jù)唇敞,所以不存在push蔗草、pop操作。這種情況可以通過內(nèi)存讀寫指令(ldr/ldp厚棵、str/stp)對(duì)其進(jìn)行操作

函數(shù)調(diào)用棧

以下是常見的函數(shù)調(diào)用開辟 (sub)以及恢復(fù)棧空間 (add)的匯編代碼

//開辟棸簦空間
sub    sp, sp, #0x40             ; 拉伸0x40(64字節(jié))空間
stp    x29, x30, [sp, #0x30]     ;x29\x30 寄存器入棧保護(hù)
add    x29, sp, #0x30            ; x29指向棧幀的底部
... 
ldp    x29, x30, [sp, #0x30]     ;恢復(fù)x29/x30 寄存器的值
//恢復(fù)椘庞玻空間
add    sp, sp, #0x40             ; 棧平衡
ret

作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要奸例,這是一個(gè)我的iOS開發(fā)交流群:130595548彬犯,不管你是小白還是大牛都?xì)g迎入駐 ,讓我們一起進(jìn)步查吊,共同發(fā)展P城(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔!)

內(nèi)存讀寫指令

  • str(store register)指令(能和內(nèi)存和寄存器交互的專門的指令):將數(shù)據(jù)從寄存器中讀出來逻卖,存到內(nèi)存中 (即一個(gè)寄存器是8字節(jié)-64位

  • ldr(load register)指令:將數(shù)據(jù)從內(nèi)存中讀出來宋列,存到寄存器中

  • 此時(shí)ldr和str的變種 ldp和stp 還可以操作2個(gè)寄存器(即128位-16字節(jié)

注意:

  • 讀/寫數(shù)據(jù)都是往高地址讀/寫
  • 寫數(shù)據(jù):先拉伸棧空間评也,再拿sp進(jìn)行寫數(shù)據(jù)炼杖,即先申請(qǐng)空間再寫數(shù)據(jù)

練習(xí)

使用32個(gè)字節(jié)空間作為這段程序的椕鸱担空間,然后利用棧將x0和x1的值進(jìn)行交換

sub sp, sp, #0x20       ;拉伸椑ば埃空間32個(gè)字節(jié)
stp x0, x1, [sp, #0x10] ;sp往上加16個(gè)字節(jié)熙含,存放x0和x1
ldp x1, x0, [sp, #0x10] ;將sp偏移16個(gè)字節(jié)的值取出來,放入x1和x0艇纺,內(nèi)存是temp(寄存器里面的值進(jìn)行交換了)
add sp, sp, #0x20       ;棧平衡
ret                     ;返回

棧的操作如下圖所示 !


調(diào)試查看棧

  • 重寫x0怎静、x1的值
調(diào)試查看棧-01
  • register read sp【查看棧的存儲(chǔ)情況:debug - debug workflow - view Memory
調(diào)試查看棧-02
  • 然后單步往下執(zhí)行,發(fā)現(xiàn)x0黔衡、x1已經(jīng)變成我們寫入的值
調(diào)試查看棧-03

) 查看內(nèi)存變化蚓聘,發(fā)現(xiàn)sp拉伸了32字節(jié)
調(diào)試查看棧-04
  • stp x0, x1, [sp, #0x10]:將x0、x1寫入fp偏移0x10的位置员帮,繼續(xù)往下執(zhí)行一步
調(diào)試查看棧-05
調(diào)試查看棧-06

此時(shí)sp的值并沒有變化或粮,還是指向40
調(diào)試查看棧-07
  • ldp x1, x0, [sp, #0x10]:讀取x0,x1的數(shù)據(jù)并交換捞高,繼續(xù)往下執(zhí)行一步氯材,此時(shí)內(nèi)存并沒有變化
調(diào)試查看棧-08

疑問:再來看sp是否有變化? 從結(jié)果來看硝岗,也沒有變化氢哮。所以這里只是讀出來進(jìn)行的交換,并不會(huì)導(dǎo)致內(nèi)存變化

調(diào)試查看棧-09

  • add sp, sp, #0x20:繼續(xù)執(zhí)行一步型檀,走到棧平衡冗尤,即sp恢復(fù)了,此時(shí)的a和b仍然在內(nèi)存中胀溺,等待著下一輪棧拉伸后數(shù)據(jù)的寫入覆蓋裂七。如果此時(shí)讀取,讀取到的是垃圾數(shù)據(jù)
調(diào)試查看棧-10

疑問:棽治耄空間不斷開辟背零,死循環(huán),會(huì)不會(huì)崩潰无埃?

在這里我們將會(huì)處理上篇(逆向初識(shí)匯編)文章中文末遺留的問題

下面我們通過一個(gè)匯編代碼來演示

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

_B:
    sub sp,sp,#0x20
    stp x0,x1,[sp,#0x10]
    ldp x1,x0,[sp,#0x10];寄存器里面的值進(jìn)行交換
    bl _B
    add sp,sp,#0x20
    ret

<!--調(diào)用-->
int B();

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

運(yùn)行結(jié)果發(fā)現(xiàn):死循環(huán)會(huì)崩潰徙瓶,會(huì)導(dǎo)致堆棧溢出

死循環(huán)崩潰圖示

bl 、ret指令

  • b 標(biāo)號(hào) :跳轉(zhuǎn)

  • bl標(biāo)號(hào)

    • 將下一條指令的地址放入lr(x30)寄存器(lr保存的是回家的路)(即l)
    • 轉(zhuǎn)到標(biāo)號(hào)處執(zhí)行指令(即b)
bl圖示

等到B函數(shù)ret時(shí)嫉称,通過lr獲取回家的路(注:lr就是保存回家的路)

  • ret
    • 默認(rèn)使用lr(x30)寄存器的值侦镇,通過底層指令提示CPU此處作為下條指令地址

    • arm64平臺(tái)的特色指令,它面向硬件做了優(yōu)化處理的

練習(xí)

下面通過匯編代碼來演示bl织阅、ret指令

.text
.global _A, _B

_A:
    mov x0\. #0xaaaa
    bl _B
    mov x0, #0xaaaa
    ret

_B:
    mov x0, #0xbbbb
    ret
  • 斷點(diǎn)運(yùn)行
演示bl壳繁、ret指令-01

疑問:發(fā)現(xiàn)A和print之間你還有幾個(gè)匯編操作,這個(gè)是什么意思呢?

演示bl氮趋、ret指令-02

  • 執(zhí)行mov x0\. #0xaaaa:x0變成aaaa伍派,此時(shí)此刻lr寄存器保存的是5f34
演示bl、ret指令-03
  • 驗(yàn)證lr是否保存的是5f34剩胁,通過查看寄存器發(fā)現(xiàn)結(jié)果與預(yù)期是一致的
演示bl诉植、ret指令-04
  • 繼續(xù)執(zhí)行bl _B,跳轉(zhuǎn)到B昵观,此時(shí)的lr會(huì)變成A中bl的下一條指令的地址5eb8
演示bl晾腔、ret指令-05
  • 執(zhí)行完B中的mov x0, #0xbbbb,x0變成bbbb
演示bl啊犬、ret指令-06
  • 執(zhí)行B中的ret灼擂,會(huì)回到A中5eb8
演示bl、ret指令-07
  • 繼續(xù)執(zhí)行A中的ret觉至,會(huì)再次回到5eb8
演示bl剔应、ret指令-08

走到這里,發(fā)現(xiàn)死循環(huán)了语御,主要是因?yàn)?code>lr一直是5eb8峻贮,ret只會(huì)看lr 。其中pc是指接下來要執(zhí)行的內(nèi)存地址应闯,ret是指讓CPU將lr作為接下來執(zhí)行的地址(相當(dāng)于將lr賦值給pc)

演示bl纤控、ret指令-09

疑問1:此時(shí)B回到A沒問題,那么A回到viewDidload怎么回呢碉纺?

  • 需要在A的bl之前保護(hù)lr寄存器
    • 疑問2:是否可以保存到其他寄存器上船万?答案是不可以,原因是不安全骨田,因?yàn)槟悴淮_定這個(gè)寄存器會(huì)在什么時(shí)候被別人使用
    • 正確做法:保存到棧區(qū)域

系統(tǒng)中函數(shù)嵌套是如何返回耿导? 下面我們來看下系統(tǒng)是如何操作的,例如:d -> c -> viewDidLoad

void d(){
}
void c(){
    d();
    return;
}
- (void)viewDidLoad{
    [super viewDidLoad];
    printf("A");
    c();
    printf("B");
}
  • 查看匯編态贤,斷點(diǎn)斷在c函數(shù)
函數(shù)嵌套調(diào)試-01
  • 進(jìn)入c函數(shù)的匯編
函數(shù)嵌套調(diào)試-02
  • stp x29,x30,[sp,#-0x10]!:邊開辟棧舱呻,邊寫入,其中 x29就是fp,x30是lr抵卫。!表示將這里算出來的結(jié)果狮荔,賦值給sp
- `lsp x29,x30,[sp],#0x10`:讀取sp指向地址的數(shù)據(jù)胎撇,放入x29介粘、x30,然后`,,#0x10`表示將sp+0x10,賦值給sp
復(fù)制代碼
  • 結(jié)論:當(dāng)有函數(shù)嵌套調(diào)用時(shí)晚树,將上一個(gè)函數(shù)的地址通過x30(即lr)放在棧中保存姻采,保證可以找到回家的路,如下圖所示
函數(shù)嵌套調(diào)試-03

自定義匯編代碼完善:_A中保存回家的路 所以根據(jù)系統(tǒng)的函數(shù)嵌套操作爵憎,最終在_A中增加了如下匯編代碼慨亲,用于保存回家的路

<!--導(dǎo)致死循環(huán)的匯編代碼-->
_A:
    mov x0\. #0xaaaa
    bl _B
    mov x0, #0xaaaa
    ret

<!--增加lr保存:可以找到回家的路-->
_A:
    sub sp, sp, #0x10  //拉伸
    str x30, [sp]     //存
    mov x0, #0xaaaa
    //保護(hù)lr寄存器婚瓜,存儲(chǔ)到棧區(qū)域
    bl _B
    mov x0, #0xaaa
    ldr x30, [sp]      //修改lr,用于A找到回家的路
    add sp, sp, #0x10 //棧平衡
    ret
復(fù)制代碼

修改_A刑棵、_B:改成簡寫形式

  • 其中lrx30的一個(gè)別名
_A:
    sub sp, sp, #0x10  //拉伸
    str x30, [sp]     //存
    mov x0, #0xaaaa
    //保護(hù)lr寄存器巴刻,存儲(chǔ)到棧區(qū)域
    bl _B
    mov x0, #0xaaa
    ldr x30, [sp]      //修改lr,用于A找到回家的路
    add sp, sp, #0x10 //棧平衡
    ret

_B:
    mov x0, #0xbbbb
    ret

<!--改成簡寫形式-->
_A:
    //sub sp, sp, #0x10  //拉伸
    //str x30, [sp]     //存
    str x30, [sp, #-0x10]
    mov x0, #0xaaaa
    //保護(hù)lr寄存器蛉签,存儲(chǔ)到棧區(qū)域
    bl _B
    mov x0, #0xaaa
    //ldr x30, [sp]      //修改lr胡陪,用于A找到回家的路
    //add sp, sp, #0x10 //棧平衡
    ldr x30, [sp], #0x10 //將sp的值讀取出來,給到x30碍舍,然后sp += 0x10
    ret

_B:
    mov x0, #0xbbbb
    ret
復(fù)制代碼

斷點(diǎn)調(diào)試

  • 查看此時(shí)sp寄存器的地址
函數(shù)嵌套調(diào)試-04
  • 執(zhí)行str x30, [sp, #-0x10]柠座,繼續(xù)查看sp,發(fā)現(xiàn)sp變化了片橡,但是此時(shí)lr沒變
函數(shù)嵌套調(diào)試-05

查看0x16f5a1c50的memory妈经,此時(shí)放入的是lr的值 861f2c,即ViewDidLoad中的bl下一條指令的地址捧书,目前只放了8個(gè)字節(jié)(1個(gè)寄存器)

函數(shù)嵌套調(diào)試-06

  • 執(zhí)行A中的mov x0, #0xaaaa:x0變成aaaa
函數(shù)嵌套調(diào)試-07
  • 執(zhí)行A中的bl _B吹泡,跳轉(zhuǎn)到B,此時(shí)lr變成 1e94鳄厌,x0變成bbbb
函數(shù)嵌套調(diào)試-08
  • 執(zhí)行B的ret:從B回到A荞胡,此時(shí)lr還是 1e94
函數(shù)嵌套調(diào)試-09
  • 執(zhí)行A中的ldr x30, [sp], #0x10
函數(shù)嵌套調(diào)試-10

發(fā)現(xiàn)此時(shí)sp也變了,從0x16f5a1c50->0x16f5a1c60了嚎。從這里可以看出泪漂,A找到了回家的路

函數(shù)嵌套調(diào)試-11

疑問:為什么是拉伸16字節(jié),而不是8字節(jié)呢歪泳? 通過手動(dòng)嘗試萝勤,有以下說明:

  • 寫入沒問題

  • 讀取時(shí)會(huì)崩潰:因?yàn)閟p中,對(duì)棧的操作必須是16字節(jié)對(duì)齊的呐伞,所以會(huì)在做棧的操作時(shí)就會(huì)崩潰

函數(shù)嵌套調(diào)試-12

x30寄存器

  • x30寄存器存放的是函數(shù)的返回地址敌卓,當(dāng)ret指令執(zhí)行時(shí)刻,會(huì)尋找x30寄存器保存的地址值

  • 注意:在函數(shù)嵌套調(diào)用時(shí)伶氢,需要將x30入棧

  • lr是x30的別名

  • sp棧里面的操作必須是16字節(jié)對(duì)齊趟径,崩潰是在棧的操作時(shí)掛的

總結(jié)

作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要癣防,這是一個(gè)我的iOS開發(fā)交流群:130595548蜗巧,不管你是小白還是大牛都?xì)g迎入駐 ,讓我們一起進(jìn)步蕾盯,共同發(fā)展D灰佟(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔!)

  • 棧:是一種具有特殊的訪問方式的存儲(chǔ)空間(后進(jìn)先出,Last in First out望拖, LIFO

    • ARM64里面對(duì)棧的操作16字節(jié)對(duì)齊
  • SPFP寄存器

    • SP寄存器在任意時(shí)刻會(huì)保存棧頂?shù)牡刂?/code>
    • FP寄存器也稱為x29寄存器渺尘,屬于通用寄存器,但是在某些時(shí)刻利用它保存棧底的地址
  • 棧的讀寫指令

    • 讀:ldr(load register)指令 LDR说敏、LDP

    • 寫:str(store register)指令 STR鸥跟、STP

  • 匯編練習(xí)

    • 指令

      • sub sp,sp,$0x10 ;拉伸棧空間18字節(jié)

      • stp x0,x1,[sp] ;sp所在位置存放x0盔沫、x1

    • 簡寫

      • str x0,x1,[sp,$-0x10]!(锌雀!就是將[]里面的結(jié)果賦值給sp)
  • bl指令

    • 跳轉(zhuǎn)指令:bl 標(biāo)號(hào),表示程序執(zhí)行到標(biāo)號(hào)處迅诬,將下一條指令的地址保存到lr寄存器

    • B代表著跳轉(zhuǎn)

    • L表示lr(x30)寄存ios_reverse_02器

  • ret指令

    • 類似函數(shù)的return
    • 讓CPU執(zhí)行l(wèi)r寄存器所指向的指令
  • 避免嵌套函數(shù)無法回去:需要保護(hù)bl(即lr寄存器腋逆,存放回家的路),保存在當(dāng)前函數(shù)自己的棾薮空間

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惩歉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俏蛮,更是在濱河造成了極大的恐慌撑蚌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搏屑,死亡現(xiàn)場離奇詭異争涌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辣恋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門亮垫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伟骨,你說我怎么就攤上這事饮潦。” “怎么了携狭?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵继蜡,是天一觀的道長。 經(jīng)常有香客問我逛腿,道長稀并,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任单默,我火速辦了婚禮碘举,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雕凹。我一直安慰自己殴俱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布枚抵。 她就那樣靜靜地躺著线欲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汽摹。 梳的紋絲不亂的頭發(fā)上李丰,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音逼泣,去河邊找鬼趴泌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拉庶,可吹牛的內(nèi)容都是我干的嗜憔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼氏仗,長吁一口氣:“原來是場噩夢啊……” “哼吉捶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起皆尔,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤呐舔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后慷蠕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珊拼,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年流炕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澎现。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡每辟,死狀恐怖昔头,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情影兽,我是刑警寧澤揭斧,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站峻堰,受9級(jí)特大地震影響讹开,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捐名,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一旦万、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镶蹋,春花似錦成艘、人聲如沸赏半。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽断箫。三九已至,卻和暖如春秋冰,著一層夾襖步出監(jiān)牢的瞬間仲义,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工剑勾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留埃撵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓虽另,卻偏偏與公主長得像暂刘,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捂刺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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