本文的主要目的是理解匯編中全局變量、常量的存儲,以及如何將if、while等匯編代碼還原成高級代碼
全局變量
在這之前首先需要了解內存的分區,對這塊不是特別清晰的,建議看看這篇文章iOS-底層原理 24:內存五大區,下面進行一個簡單的匯總說明
代碼區
:存放代碼,可讀、可執行棧區
:存放參數、局部變量、臨時數據,可讀寫堆區
:開發人員動態申請,大小可變,可讀寫全局變量
:可讀可寫常量
:只讀
案例分析
在main.m中定義一個函數和一個全局變量
int g = 12;
int func(int a, int b){
printf("haha");
int c = a + g;
return c;
}
int main(int argc, char * argv[]) {
func(1, 2);
}
-
func函數斷點運行,以下是main函數的匯編代碼
image -
查看func的匯編代碼,分析如下
image- 查看x0是否為“haha”,通過調試得以驗證,
x0存的是haha的地址
image - 查看其地址:
x 0x000000010098bf9f
,屬于字符串的常量區(即左邊是右邊字符串的ASCII碼)
image
- 查看x0是否為“haha”,通過調試得以驗證,
其中重點分析adrp x0,1
和add x0,x0,#0xf9f
兩句
-
adrp
指令(address page 按頁尋址):- 將1的值左移12位,此時的1是二進制
- 加上pc寄存器的值(先需要將pc的低12位清零)
<!--(按頁尋址)-->
<!--adrp-->
0x10098a824 <+20>: adrp x0, 1
- 1)1左移12位:0x1000
- 2)pc寄存器低12位清零:0x10098a000
- 3)加上pc寄存器的值:0x10098a000 + 0x1000 = 0x10098b000
===> 得到x0地址就是某一頁數據的起始位置(即首地址)
<!--add-->
0x10098a828 <+24>: add x0, x0, #0xf9f ; =0xf9f
- adrp得到的地址加上偏移:0x10098b000 + 0xf9f = 0x10098bf9f
===> 此時的x0就是某一頁中某段代碼的地址,即當前代碼段的地址
通過這個計算結果可知與上面調試的x0地址是一致的
why?:一個頁的大小是4096
,而0xFFF
為4095
,加上1就是0x1000
(即4096
),所以是1左移12位即可得到一個頁的首地址(注:macOS的pageSize是 4k
(0x1000),而iPhone的pageSize是16k
(0x4000),但是16仍是4的倍數,adrp兼容者mac和iPhone,所以此時定位的仍然是一頁數據)
- 繼續分析
bl printf
以下的匯編代碼
image-
ldur w8, [x29, #-0x4]
:拿出棧中的數據,即1
image -
adrp + add + ldr
:拿出0x10098ce98內存地址的數據,將x9的數據給w10。這樣就拿到了全局變量g
image
-
反匯編分析
示例代碼如下
int g = 12;
int func(int a, int b){
printf("haha");
int c = a + g + b;
return c;
}
int main(int argc, char * argv[]) {
func(10, 20);
}
通過hopper
來進行反匯編分析
首先將工程編譯:
CMD+B
-
進入App的包
image
image
image -
將第5步中的可執行文件拖入hopper中進行分析
image -
在hopper中搜索func
image 拷貝func的匯編代碼,將其還原成高級語言代碼(即反匯編)
<!--1、將匯編初步還原為高級語言代碼-->
int gl = 12;
int func2(int a, int b){
/*
//一個函數的開始
0000000100006808 sub sp, sp, #0x20
000000010000680c stp x29, x30, [sp, #0x10]
0000000100006810 add x29, sp, #0x10
*/
/*
//調用bl printf
0000000100006814 stur w0, [x29, #-0x4]
0000000100006818 str w1, [sp, #0x8]
//===>此時的獲取的0x100007f9f地址的數據 是沒有ASLR的值
000000010000681c adrp x0, #0x100007000
0000000100006820 add x0, x0, #0xf9f ; "haha"
0000000100006824 bl imp___stubs__printf
*/
printf("haha");
/*
0000000100006828 ldur w8, [x29, #-0x4]
*/
int w8 = a;
/*
//===>此時的獲取0x100008e98的數據
000000010000682c adrp x9, #0x100008000
0000000100006830 add x9, x9, #0xe98 ; _g
*/
// int gl = 12;//(需要寫外面)
/*
0000000100006834 ldr w10, x9
*/
int w10 = gl;
/*
0000000100006838 add w8, w8, w10
*/
w8 += w10;
/*
000000010000683c ldr w10, [sp, #0x8]
*/
w10 = b;
/*
0000000100006840 add w8, w8, w10
*/
w8 += w10;
/*
0000000100006844 str w8, [sp, #0x4]
0000000100006848 ldr w8, [sp, #0x4]
000000010000684c mov x0, x8
*/
return w8;
/*
//一個函數的結束
0000000100006850 ldp x29, x30, [sp, #0x10]
0000000100006854 add sp, sp, #0x20
0000000100006858 ret
*/
}
<!--2、去掉匯編-->
int gl = 12;
int func2(int a, int b){
printf("haha");
int w8 = a;
int w10 = gl;
w8 += w10;
w10 = b;
w8 += w10;
return w8;
}
<!--3、簡化代碼-->
int gl = 12;
int func2(int a, int b){
printf("haha");
return a + b + gl;
}
簡化過程如下圖所示(注:是從下向上還原
,而不是從上向下(業務邏輯是從上至下執行):
其中
//===>此時的獲取的0x100007f9f地址的數據 是沒有ASLR的值
000000010000681c adrp x0, #0x100007000
0000000100006820 add x0, x0, #0xf9f
- hopper中按
G
,查找0x100007f9f
對應的數據
image
同理,獲取全局變量g
也是同樣的原理
//===>此時的獲取0x100008e98的數據
000000010000682c adrp x9, #0x100008000
0000000100006830 add x9, x9, #0xe98 ; _g
0000000100006834 ldr w10, x9
總結
獲取
全局變量和常量
時,會出現adrp
和add
兩條指令獲得一個地址的情況-
ADRP(Address Page)
-
adrp x0,1
將
PC
寄存器的低12位清零
將1的值,左移12位,16進制就是0x1000
以上兩個結果相加放入
x0
寄存器
-
通過
ADD
指令獲取這頁內存中的偏移值
條件
有如下代碼,查看其匯編
int g = 12;
void func(int a, int b){
if (a > b) {
g = a;
}else{
g = b;
}
}
int main(int argc, char * argv[]) {
func(1, 2);
}
通過hopper查看其匯編,代碼如下
_func:
==>拉伸棧空間
0000000100006828 sub sp, sp, #0x10 ; CODE XREF=_main+32
==>w0、w1數據入棧
000000010000682c str w0, [sp, #0xc]
0000000100006830 str w1, [sp, #0x8]
==>從棧中讀取數據到w8、w9
0000000100006834 ldr w8, [sp, #0xc]
0000000100006838 ldr w9, [sp, #0x8]
==>比較w8、w9,即比較w0、w1(cmp是減法,但不影響目標寄存器w8、w9,只看減法結果,修改標記寄存器)
000000010000683c cmp w8, w9
//如果是小于等于,就跳到到 loc_100006858 執行,如果是大于,則直接往下執行
0000000100006840 b.le loc_100006858
0000000100006844 ldr w8, [sp, #0xc]
0000000100006848 adrp x9, #0x100008000
000000010000684c add x9, x9, #0xe90 ; _g
0000000100006850 str w8, x9
//硬跳,規避小于等于的代碼,跳到loc_100006868
0000000100006854 b loc_100006868
loc_100006858:
0000000100006858 ldr w8, [sp, #0x8] ; CODE XREF=_func+24
000000010000685c adrp x9, #0x100008000
0000000100006860 add x9, x9, #0xe90 ; _g
0000000100006864 str w8, x9
loc_100006868:
0000000100006868 add sp, sp, #0x10 ; CODE XREF=_func+44
000000010000686c ret
這是典型的if-else
,通過hopper查看其匯編代碼如下
將上述匯編代碼進行還原
<!--1、還原-->
int cc = 12;
void func2(int a, int b){
//==>拉伸棧空間
//0000000100006828 sub sp, sp, #0x10
//==>w0、w1數據入棧
//000000010000682c str w0, [sp, #0xc]
//0000000100006830 str w1, [sp, #0x8]
//==>從棧中讀取數據到w8、w9
//0000000100006834 ldr w8, [sp, #0xc]
//0000000100006838 ldr w9, [sp, #0x8]
int w8 = a;
int w9 = b;
//==>比較w8、w9,即比較w0、w1(cmp是減法,但不影響目標寄存器w8、w9,只看減法結果,修改標記寄存器)
//000000010000683c cmp w8, w9
////如果是小于等于,就跳到到 loc_100006858 執行,如果是大于,則直接往下執行
//0000000100006840 b.le loc_100006858
if (w8 > w9 ) {//大于
//0000000100006844 ldr w8, [sp, #0xc]
//0000000100006848 adrp x9, #0x100008000
//000000010000684c add x9, x9, #0xe90 ; _g
//0000000100006850 str w8, x9
cc = w8;//此時的w8是a
////硬跳,規避小于等于的代碼,跳到loc_100006868
//0000000100006854 b loc_100006868
}else{//小于等于
// loc_100006858:
//0000000100006858 ldr w8, [sp, #0x8]
//000000010000685c adrp x9, #0x100008000
//0000000100006860 add x9, x9, #0xe90 ; _g
//0000000100006864 str w8, x9
cc = w8;//此時的w8是b
}
// loc_100006868:
//0000000100006868 add sp, sp, #0x10
//000000010000686c ret
}
<!--2、簡化-->
int cc = 12;
void func2(int a, int b){
if (a > b ) {//大于
cc = a;
}else{//小于等于
cc = b;
}
}
cmp(Compare)比較指令
-
CMP
把一個寄存器的內容和另一個寄存器的內容或立即數進行比較,但不存儲結果,只是正確的更改標志
(CMP后面跟的是B.LE
,即else的條件) - 一般CMP做完判斷后會進行跳轉,后面通常會跟上B指令
BL 標號
:跳轉到標號處執行B.LT 標號
:比較結果是小于(less than ),執行標號,否則不跳轉B.LE 標號
:比較結果是小于等于(less than or equal to),執行標號,否則不跳轉B.GT 標號
:比較結果是大于(greater than),執行標號
,否則不跳轉B.GE 標號
:比較結果是大于等于
(greater than or equal to),執行標號,否則不跳轉
- `B.EQ 標號`:比較結果是`等于`,執行標號,否則不跳轉
- `B.NE 標號`:比較結果是不等于(not equal),執行標號,否則不跳轉
- `B.HI 標號`:比較結果是`無符號大于`,執行標號,否則不跳轉
- `B.HS 標號`:比較結果是`無符號大于等于`,執行標號,否則不跳轉
循環
循環常用的主要有for
、while
、do-while
,下面來一一進行分析
do-while分析
分析以下do-while的代碼
int main(int argc, char * argv[]) {
int sum = 0;
int i = 0;
do{
sum += 1;
i++;
}while (i<100);
}
-
通過hopper查看其匯編
image -
匯編結束如下所示
image
結論:do-while
循環:判斷條件在后面,滿足條件往外跳
while循環分析
int main(int argc, char * argv[]) {
int sum = 0;
int i = 0;
while (i<100){
sum += 1;
i++;
}
}
匯編如圖所示
結論:while
循環:判斷條件在里面,不滿足就往外跳
for循環分析
int main(int argc, char * argv[]) {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += 1;
}
}
此時和while的匯編是一樣的
結論:for
循環很像:判斷條件在里面,不滿足就往外跳
總結
全局變量和常量
獲取
全局變量和常量
時,會出現adrp
和add
兩條指令獲得一個地址的情況-
ADRP(Address Page)
-
adrp x0,1
將
PC
寄存器的低12位清零
將1的值,左移12位
以上兩個結果相加放入
x0
寄存器
-
通過
ADD
指令獲取這頁內存中的偏移值
條件判斷
-
CMP
把一個寄存器的內容和另一個寄存器的內容或立即數進行比較,但不存儲結果,只是正確的更改標志
(CMP后面跟的是B.LE
,即else的條件) - 一般CMP做完判斷后會進行跳轉,后面通常會跟上B指令
BL 標號
:跳轉到標號處執行B.LT 標號
:比較結果是小于(less than ),執行標號,否則不跳轉B.LE 標號
:比較結果是小于等于(less than or equal to),執行標號,否則不跳轉B.GT 標號
:比較結果是大于(greater than),執行標號
,否則不跳轉B.GE 標號
:比較結果是大于等于
(greater than or equal to),執行標號,否則不跳轉B.EQ 標號
:比較結果是等于
,執行標號,否則不跳轉B.NE 標號
:比較結果是不等于(not equal),執行標號,否則不跳轉B.HI 標號
:比較結果是無符號大于
,執行標號,否則不跳轉B.HS 標號
:比較結果是無符號大于等于
,執行標號,否則不跳轉
循環
do-while
循環:判斷條件在后面
,滿足條件往外跳for
循環和while
循環很像:判斷條件在里面
,不滿足就往外跳