iOS匯編
iOS匯編語音有很多鐘霎肯。常見的有8086匯編柑土、arm匯編、x86匯編等等酌予。
arm匯編
iOS的架構(gòu)從最初的armv6發(fā)展到后來的armv7和armv7s磺箕,最后發(fā)展到現(xiàn)在的arm64,不管是armv6還是后來的armv7,以及arm64都是arm處理器的指令集霎终。armv7和armv7s是真機32位處理器使用的架構(gòu)滞磺,而arm64是真機64位處理器使用的架構(gòu)。 iPhone 5C是最后一款arm32位版本的iPhone莱褒,在iPhone5s之后击困,所有的iPhone設(shè)備都采用arm64架構(gòu)。arm64匯編在真機上使用广凸,如下:
TestFont`-[ViewController test]:
0x10286e574 <+0>: sub sp, sp, #0x20 ; =0x20
0x10286e578 <+4>: mov w8, #0x14
0x10286e57c <+8>: mov w9, #0xa
0x10286e580 <+12>: str x0, [sp, #0x18]
0x10286e584 <+16>: str x1, [sp, #0x10]
-> 0x10286e588 <+20>: str w9, [sp, #0xc]
0x10286e58c <+24>: str w8, [sp, #0x8]
0x10286e590 <+28>: add sp, sp, #0x20 ; =0x20
0x10286e594 <+32>: ret
x86匯編
x86匯編是模擬器使用的匯編語言阅茶,它的指令和arm64匯編的語法不同,如下
TestFont`-[ViewController test]:
0x10b089520 <+0>: pushq %rbp
0x10b089521 <+1>: movq %rsp, %rbp
0x10b089524 <+4>: movq %rdi, -0x8(%rbp)
0x10b089528 <+8>: movq %rsi, -0x10(%rbp)
-> 0x10b08952c <+12>: movl $0xa, -0x14(%rbp)
0x10b089533 <+19>: movl $0x14, -0x18(%rbp)
0x10b08953a <+26>: popq %rbp
0x10b08953b <+27>: retq
為什么要學習arm64匯編?
代碼調(diào)試
在平常開發(fā)中谅海,在調(diào)試程序的時候脸哀,如果程序crash,通常會定位到具體的崩潰代碼扭吁。但是有時候也會遇到一些比較詭異的crash撞蜂,比如說崩潰在了系統(tǒng)庫中,這個時候定位到具體的crash原因會非常困難侥袜。如果利用匯編調(diào)試技巧來進行調(diào)試蝌诡,可能會讓我們事半功倍。
逆向調(diào)試
在逆向別人App過程中枫吧,我們可以通過LLDB對內(nèi)存地址進行斷點操作浦旱,但是當執(zhí)行到斷點時,LLDB展現(xiàn)給我們的是匯編代碼九杂,而不是OC代碼颁湖,所以想要逆向并且動態(tài)調(diào)試別人的App宣蠕,就需要學習匯編的知識。
作為一個開發(fā)者甥捺,有一個學習的氛圍跟一個交流圈子特別重要抢蚀,這有個iOS交流群:642363427,不管你是小白還是大牛歡迎入駐 涎永,分享BAT,阿里面試題思币、面試經(jīng)驗,討論技術(shù)羡微,iOS開發(fā)者一起交流學習成長!
arm64匯編入門
想要學習arm64匯編惶我,需要從以下三個方面入手妈倔,寄存器、指令和堆棧绸贡。
寄存器
arm64中有34個寄存器盯蝴,如下
通用寄存器
- 64 bit的通用寄存器有29個,分別是x0 ~ x28
- 32 bit的也有29個听怕,分別是w0 ~ w28(屬于x0 ~ x28的低32位)
- 其中x0 ~ x7通常拿來存放函數(shù)的參數(shù)捧挺,如果參數(shù)更多,則采用堆棧來進行傳遞
- x0中通常存放函數(shù)的返回值
也會有人將x0 ~ x30叫做通用寄存器尿瞭,但是在實際使用中x29和x30并沒有對應的低32位的寄存器w29闽烙、w30,而且x29和x30寄存器有著特殊的用途声搁,所以在此我只講x0 ~ x28記為通用寄存器
程序計數(shù)器
pc (Program Counter)寄存器黑竞,它記錄著當前CPU正在執(zhí)行的指令的地址,通過register read pc查看寄存器中存儲的值
(lldb) register read pc
pc = 0x000000010286e588 TestFont`-[ViewController test] + 20 at ViewController.m:28
(lldb)
堆棧指針
- sp (Stack Pointer)
- fp (Frame Pointer)疏旨,也就是之前所說的x29
鏈接寄存器
lr (Link Register)寄存器很魂,也就是之前所說的x30寄存器,它存儲著函數(shù)的返回地址
程序狀態(tài)寄存器
arm體系中包含一個當前程序狀態(tài)寄存器cpsr (Current Program Status Register)和五個備份的程序狀態(tài)寄存器spsr (Saved Program Status Registe)檐涝,備份的程序狀態(tài)寄存器用來進行異常處理遏匆。
- 程序狀態(tài)寄存器的每一位都有特定的用途,此處只介紹幾種常用的標志位
- 其中N谁榜、Z幅聘、C、V均為條件碼標志位惰爬,他們的內(nèi)容可被算數(shù)或者邏輯運算的結(jié)果所改變喊暖,并且可以決定某條指令是否被執(zhí)行。條件碼標志各位的具體含義如下
指令
mov指令
mov指令可以將另一個寄存器撕瞧、被移位的寄存器或者將一個立即數(shù)加載到目的寄存器
mov指令在arm64匯編中的實際使用
- 在xcode中新建test.s文件陵叽,在test.s文件中添加以下代碼
; 此處.text表示此代碼放在text段中
.text
; .global表示將后面跟隨的方法給暴露出去狞尔,不然外部無法調(diào)用,方法名以_開頭
.global _test
; 此處為_test方法
_test:
; mov指令巩掺,將立即數(shù)4加載到x0寄存器中
mov x0, #0x4
mov x1, x0
; 匯編指令中偏序,ret表示函數(shù)的終止
ret
- 在xcode中新建test.h頭文件,將test.s中的_test方法暴露出來
#ifndef test_h
#define test_h
void test(void);
#endif /* test_h */
- 在viewDidLoad中調(diào)用test()函數(shù)胖替,然后在LLDB中使用register read x0 讀取寄存器中存放的值
(lldb) register read x0
x0 = 0x000000010320c980
(lldb) si
(lldb) register read x0
x0 = 0x0000000000000004
(lldb) register read x1
x1 = 0x00000001e60f3bc7 "viewDidLoad"
(lldb) si
(lldb) register read x1
x1 = 0x0000000000000004
通過對匯編指令增加斷點研儒,一步一步調(diào)試可以看出,在執(zhí)行完mov指令后独令,x0和x1寄存器的值都被修改了
ret
ret指令表示函數(shù)的返回端朵,而且它還有一個非常重要的作用,就是將lr(x30)寄存器的值賦值給pc寄存器
- 在viewDidLoad中調(diào)用test()函數(shù),在test()函數(shù)上打上斷點燃箭,執(zhí)行程序如下
- 使用register read 查看lr和pc寄存器的值
(lldb) register read lr
lr = 0x00000001021965a4 TestFont`-[ViewController viewDidLoad] + 68 at ViewController.m:23
(lldb) register read pc
pc = 0x00000001021965a4 TestFont`-[ViewController viewDidLoad] + 68 at ViewController.m:23
(lldb)
此時冲呢,lr寄存器和pc寄存器的值都是test()函數(shù)起始地址
- 使用si指令跳轉(zhuǎn)到test()函數(shù)中
- 再次查看lr和pc寄存器的值,發(fā)現(xiàn)lr的值變成了test()函數(shù)的下一條指令的地址招狸,也就是test()函數(shù)執(zhí)行完成之后敬拓,主程序需要執(zhí)行的下一條指令。pc寄存器保存了當前即將執(zhí)行的指令的地址裙戏,如下
(lldb) register read lr
lr = 0x00000001021965a8 TestFont`-[ViewController viewDidLoad] + 72 at ViewController.m:24
(lldb) register read pc
pc = 0x0000000102196abc TestFont`test
- 執(zhí)行完test()函數(shù)乘凸,發(fā)現(xiàn)程序跳轉(zhuǎn)到了lr寄存器所保存的指令地址,也就是0x00000001021965a8累榜,此時再次查看lr和pc寄存器的值营勤,發(fā)現(xiàn)pc寄存器存放的地址已經(jīng)變成了lr寄存器存放的地址
(lldb) register read lr
lr = 0x00000001021965a8 TestFont`-[ViewController viewDidLoad] + 72 at ViewController.m:24
(lldb) register read pc
pc = 0x00000001021965a8 TestFont`-[ViewController viewDidLoad] + 72 at ViewController.m:24
(lldb)
add指令
add指令是將兩個操作數(shù)相加,并將結(jié)果存放到目標寄存器中信柿。具體說明如下
在arm64匯編中冀偶,相應的就是操作x0~x28,執(zhí)行如下匯編代碼
.text
.global _test
_test:
mov x0, #0x4
mov x1, #0x3
add x0, x1, x0
ret
執(zhí)行完test()函數(shù)渔嚷,通過register read查詢x0的值进鸠,最后可以看到x0存放的值為7,如下
(lldb) register read x0
x0 = 0x0000000000000004
(lldb) si
(lldb) register read x1
x1 = 0x0000000000000003
(lldb) si
(lldb) register read x0
x0 = 0x0000000000000007
sub指令
sub指令是將操作數(shù)1減去操作數(shù)2,再減去cpsr中的C條件標志位的反碼形病,并將結(jié)果存放到目標寄存器中
cmp指令
cmp指令是把一個寄存器的內(nèi)容和另一個寄存器的內(nèi)容或者立即數(shù)做比較客年,同時會更新CPSR寄存器中條件標志位的值
- 執(zhí)行如下匯編代碼
.text
.global _test
_test:
mov x0, #0x4
mov x1, #0x3
cmp x0, x1
ret
- 在執(zhí)行cmp代碼之前和之后各打印一次CPSR寄存器的值如下
(lldb) register read cpsr
cpsr = 0x60000000
(lldb) si
(lldb) si
(lldb) si
(lldb) register read cpsr
cpsr = 0x20000000
(lldb)
可以發(fā)現(xiàn),在執(zhí)行cmp操作之后漠吻,cpsr寄存器的值變成了0x20000000,轉(zhuǎn)換成16進制后量瓜,得到32位標志位如下
可以發(fā)現(xiàn)第31位,也就是N位的值為0途乃,同時第30位绍傲,也就是Z位的值也為0,這就表示,x0和x1寄存器相比較之后的值為非零非負,而使用x0 - x1得到的結(jié)果是1烫饼,符合非零非負的條件猎塞。
- 修改匯編代碼,調(diào)換x0和x1寄存器的位置杠纵,如下
_test:
mov x0, #0x4
mov x1, #0x3
cmp x1, x0
ret
- 再次在cmp代碼執(zhí)行前后讀取CPSR寄存器的值
(lldb) register read cpsr
cpsr = 0x60000000
(lldb) s
(lldb) register read cpsr
cpsr = 0x80000000
(lldb)
這個時候荠耽,cpsr寄存器的值變成了0x80000000,轉(zhuǎn)換成16進制后,如下
可以看出比藻,第31位N位的值變成了1铝量,第30位Z位的值為0,這表示银亲,x0和x1寄存器相比較之后的值為非零負數(shù)慢叨,使用x1-x0得到的結(jié)果是-1,符合非零負數(shù)的條件
B指令
B指令是最簡單的跳轉(zhuǎn)指令群凶,一旦遇到B指令插爹,程序會無條件跳轉(zhuǎn)到B之后所指定的目標地址處執(zhí)行。
BL指令
BL指令是另一個跳轉(zhuǎn)指令请梢,但是在跳轉(zhuǎn)之前,它會先將當前標記位的下一條指令存儲在寄存器lr(x30)中力穗,然后跳轉(zhuǎn)到標記處開始執(zhí)行代碼毅弧,當遇到ret時,會將lr(x30)中存儲的地址重新加載到PC寄存器中当窗,使得程序能返回標記位的下一條指令繼續(xù)執(zhí)行够坐。
- 首先執(zhí)行以下匯編代碼
.text
.global _test
label:
mov x0, #0x1
mov x1, #0x8
ret
_test:
mov x0, #0x4
bl label
mov x1, #0x3
cmp x1, x0
ret
- 斷點到bl label指令時,讀取lr寄存器和PC寄存器的值
- 執(zhí)行bl label指令崖面,跳轉(zhuǎn)到label標記處元咙,再次讀取lr(x30)寄存器和PC寄存器的值,這個時候會發(fā)現(xiàn)lr(x30)寄存器存放的地址已經(jīng)變成mov x1, #0x3這條指令的內(nèi)存地址
- 執(zhí)行完label標記中的所有代碼,發(fā)現(xiàn)程序再次回到lr寄存器所存儲的地址巫员,也就是mov x1, #0x3這句指令繼續(xù)執(zhí)行,并且此時pc寄存器所存儲的地址也變成了mov x1, #0x3這句指令的地址庶香。
想了解更多逆向相關(guān)知識不妨動動小手,添加一下咱們的交流群642363427來為你的技術(shù)多添一份光彩简识。