本文的主要目的是了解彙編中全局變量、常量的存儲,以及如何将if、while等彙編代碼還原成進階代碼

全局變量
在這之前首先需要了解記憶體的分區,對這塊不是特别清晰的,建議看看iOS-底層原理:記憶體五大區,下面進行一個簡單的彙總說明
-
:存放代碼,可讀、可執行代碼區
-
:存放參數、局部變量、臨時資料,可讀寫棧區
-
:開發人員動态申請,大小可變,可讀寫堆區
-
:可讀可寫全局變量
-
:隻讀常量
案例分析
首先作為一個開發者,有一個學習的氛圍跟一個交流圈子特别重要,這是一個我的iOS開發交流群:130595548,不管你是小白還是大牛都歡迎入駐 ,讓我們一起進步,共同發展!(群内會免費提供一些群主收藏的免費學習書籍資料以及整理好的幾百道面試題和答案文檔!)
在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函數的彙編代碼
iOS開發逆向之循環選擇指針(上) - 檢視func的彙編代碼,分析如下
iOS開發逆向之循環選擇指針(上) - 檢視x0是否為“haha”,通過調試得以驗證,
x0存的是haha的位址
iOS開發逆向之循環選擇指針(上) - 檢視其位址:
,屬于字元串的常量區(即左邊是右邊字元串的ASCII碼)x 0x000000010098bf9f
iOS開發逆向之循環選擇指針(上)
- 檢視x0是否為“haha”,通過調試得以驗證,
其中重點分析
adrp x0,1
和
add x0,x0,#0xf9f
兩句
-
指令(address page 按頁尋址):adrp
- 将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
iOS開發逆向之循環選擇指針(上) -
:拿出棧中的資料,即1ldur w8, [x29, #-0x4]
iOS開發逆向之循環選擇指針(上) -
:拿出0x10098ce98記憶體位址的資料,将x9的資料給w10。這樣就拿到了全局變量gadrp + add + ldr
iOS開發逆向之循環選擇指針(上)
-
反彙編分析
示例代碼如下
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的包
iOS開發逆向之循環選擇指針(上) iOS開發逆向之循環選擇指針(上) iOS開發逆向之循環選擇指針(上) - 将第5步中的可執行檔案拖入hopper中進行分析
iOS開發逆向之循環選擇指針(上) - 在hopper中搜尋func
iOS開發逆向之循環選擇指針(上) - 拷貝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
iOS開發逆向之循環選擇指針(上)
同理,擷取
全局變量g
也是同樣的原理
//===>此時的擷取0x100008e98的資料000000010000682c adrp x9, #0x1000080000000000100006830 add x9, x9, #0xe98 ; _g0000000100006834 ldr w10, x9
總結
- 擷取
時,會出現全局變量和常量
adrp
兩條指令獲得一個位址的情況add
- ADRP(Address Page)
- 将
寄存器的PC
低12位清零
- 将1的值,左移12位,16進制就是0x1000
- 以上兩個結果相加放入
寄存器x0
-
adrp x0,1
- 将
-
指令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後面跟的是内容或立即數進行比較,但不存儲結果,隻是正确的更改标志
,即else的條件)B.LE
- 一般CMP做完判斷後會進行跳轉,後面通常會跟上B指令
-
:跳轉到标号處執行BL 标号
-
:比較結果是小于(less than ),執行标号,否則不跳轉B.LT 标号
-
:比較結果是小于等于(less than or equal to),執行标号,否則不跳轉B.LE 标号
-
:比較結果是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檢視其彙編
iOS開發逆向之循環選擇指針(上) - 彙編結束如下所示
iOS開發逆向之循環選擇指針(上)
結論:
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
-
-
PC
低12位清零
- 将1的值,左移12位
-
x0
-
adrp x0,1
-
-
ADD
擷取這頁記憶體中的偏移值
條件判斷
-
CMP
内容或立即數進行比較,但不存儲結果,隻是正确的更改标志
B.LE
-
-
BL 标号
-
B.LT 标号
-
B.LE 标号
-
B.GT 标号
大于(greater than),執行标号
-
B.GE 标号
大于等于
-
B.EQ 标号
,執行标号,否則不跳轉等于
-
:比較結果是不等于(not equal),執行标号,否則不跳轉B.NE 标号
-
B.HI 标号
無符号大于
-
B.HS 标号
無符号大于等于
-