iOS逆向?qū)崙?zhàn)--004:常量 & 全局變量

內(nèi)存五大區(qū),實(shí)際是指虛擬內(nèi)存,而不是真實(shí)物理內(nèi)存,它們是在邏輯上劃分的

  • 棧區(qū):存放參數(shù)、局部變量、臨時(shí)數(shù)據(jù)??勺x,可寫
  • 堆區(qū):向系統(tǒng)申請(qǐng)區(qū)域,并指明大小??勺x,可寫
  • 全局區(qū):存放全局變量和靜態(tài)變量??勺x,可寫
  • 常量區(qū):存放常量,整個(gè)程序運(yùn)行期不能被改變。只讀
  • 代碼區(qū):存放代碼??勺x,可執(zhí)行
常量

案例:

編譯器如何存儲(chǔ)常量?

打開ViewController.m文件,寫入以下代碼:

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   printf("haha");
}

@end

真機(jī)運(yùn)行項(xiàng)目,來到viewDidLoad方法

  • adrp x0, 1adrp指令包含三個(gè)操作
    將右側(cè)的常數(shù)左移12位,得到偏移后的地址
    pc寄存器的低12位清零
    將清零后的地址和偏移后的地址相加,寫入x0寄存器
  • add x0, x0, #0x654:將x0寄存器的值和偏移地址,寫入x0寄存器
  • bl 0x100af65b8:調(diào)用printf函數(shù)

按照函數(shù)調(diào)用的原則,printf函數(shù)的參數(shù)應(yīng)該使用x0傳遞。打印x0寄存器:

  • 由此可見,上面的一系列運(yùn)算,最終目的還是獲取常量的地址,寫入到x0寄存器

拆解指令

adrp x0, 1指令:

  • 右側(cè)的1為常數(shù),將1左移12位,即:1 << 12 = 409616進(jìn)制0x1000
  • 執(zhí)行到adrp指令時(shí)的pc寄存器為0x100af5fa0,將其低12位清零,即:0x100af5000
  • 將清零后的地址和偏移后的地址相加,即:0x100af5000 + 0x1000 = 0x100af6000

add x0, x0, #0x654指令:

  • x0寄存器的值和偏移地址相加,即:0x100af6000 + 0x654 = 0x100af6654

lldb中,通過x 0x100af6654命令,按16進(jìn)制格式輸出

  • 存儲(chǔ)的常量:haha
  • 16進(jìn)制68,10進(jìn)制104,即:hASCII
  • 16進(jìn)制61,10進(jìn)制91,即:aASCII

原理解析

adrp指令計(jì)算后,得到一個(gè)內(nèi)存結(jié)果,尾數(shù)為000。從0x000 ~ 0xfff剛好是4096,而macOS系統(tǒng)中,內(nèi)存分頁大小剛好是4KB,所以這里得到一個(gè)大小為4KB的頁的基址

  • iOS系統(tǒng)中,內(nèi)存分頁大小為16KB,但也是4的倍數(shù),所以沒有任何影響
  • adrp指令中的常數(shù)(頁碼),由當(dāng)前pc寄存器地址作為參照,和常量所在地址進(jìn)行計(jì)算,所得到差值

通過add指令,在此頁的基址上加上偏移地址,從而拿到具體數(shù)據(jù)

全局變量

和常量一樣,先計(jì)算數(shù)據(jù)在全局區(qū)所在頁的基址,再加上偏移地址,從而拿到具體數(shù)據(jù)

靜態(tài)分析時(shí),通過匯編代碼和指令,無法區(qū)分當(dāng)前數(shù)據(jù)是全局變量還是常量,只能通過內(nèi)存的情況做理性判斷,也可以通過數(shù)據(jù)所在地址減去ASLR偏移地址,然后在Moch-O中進(jìn)行定位

案例:

編譯器如何存儲(chǔ)全局變量?

打開ViewController.m文件,寫入以下代碼:

#import "ViewController.h"

int g = 12;

int func(int a,int b){
   int c = a + g + b;
   return c;
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   func(10, 20);
}

@end

真機(jī)運(yùn)行項(xiàng)目,來到func方法

  • adrp x9, 8x9 = 0x104b91000
  • add x9, x9, #0x490x9 = 0x104b91490

使用View Memory查看內(nèi)存數(shù)據(jù)

  • 地址0x104b91490存儲(chǔ)的0xc,也就是全局變量g的值:12

繼續(xù)執(zhí)行代碼,x9是偏移后的地址,讀取x9地址的值,寫入w10

還原高級(jí)代碼

Hopper Disassembler是一款二進(jìn)制反編譯軟件,它不僅擁有拆開任何二進(jìn)制軟件的強(qiáng)大功能,還可以提供所有的軟件編碼內(nèi)容。如導(dǎo)入符號(hào)或控制流程的實(shí)用化信息,在允許您命名所有需要對(duì)象的基礎(chǔ)上,能夠輕松的將匯編語言轉(zhuǎn)換為更容易理解的偽代碼,甚至可以使用GDB來調(diào)試程序從而有效的達(dá)到反匯編的功能操作

案例:

借助Hopper進(jìn)行高級(jí)代碼的還原

使用真機(jī)編譯Demo項(xiàng)目,找到編譯后的Demo.app文件

右鍵顯示包內(nèi)容,找到里面的Mach-O文件(可執(zhí)行文件)

Mach-O文件拖到Hopper中,點(diǎn)擊OK

找到viewDidLoad方法

                    -[ViewController viewDidLoad]:
0000000100005f8c         sub        sp, sp, #0x20            ; Objective C Implementation defined at 0x10000c0a0 (instance method), DATA XREF=0x10000c0a0
0000000100005f90         stp        x29, x30, [sp, #0x10]
0000000100005f94         add        x29, sp, #0x10
0000000100005f98         str        x0, [sp, #0x8]
0000000100005f9c         str        x1, sp
0000000100005fa0         movz       w0, #0xa
0000000100005fa4         movz       w1, #0x14
0000000100005fa8         bl         _func
0000000100005fac         ldp        x29, x30, [sp, #0x10]
0000000100005fb0         add        sp, sp, #0x20
0000000100005fb4         ret
                       ; endp
  • 前五句代碼,開辟??臻g,現(xiàn)場(chǎng)保護(hù),無需代碼還原
  • movz w0, #0xa ~ movz w1, #0x14w0、w1用于給函數(shù)傳遞參數(shù),推斷這里應(yīng)該有兩個(gè)參數(shù)需要傳遞給func函數(shù)
  • bl _func:調(diào)用func函數(shù)
  • 最后三句代碼,還原x29、x30的值,恢復(fù)棧平衡并返回,無需代碼還原

根據(jù)上述匯編代碼的分析,還原viewDidLoad方法

- (void)viewDidLoad {
   int w0 = 10;
   int w1 = 20;
   func(w0, w1);
}

找到func函數(shù)

                    _func:
0000000100005f38         sub        sp, sp, #0x20             ; CODE XREF=-[ViewController viewDidLoad]+28
0000000100005f3c         stp        x29, x30, [sp, #0x10]
0000000100005f40         add        x29, sp, #0x10
0000000100005f44         stur       w0, [x29, #-0x4]
0000000100005f48         str        w1, [sp, #0x8]
0000000100005f4c         adrp       x0, #0x100006000          ; argument #1 for method imp___stubs__printf
0000000100005f50         add        x0, x0, #0x654            ; "haha"
0000000100005f54         bl         imp___stubs__printf
0000000100005f58         ldur       w8, [x29, #-0x4]
0000000100005f5c         adrp       x9, #0x10000d000
0000000100005f60         add        x9, x9, #0x498            ; _g
0000000100005f64         ldr        w10, x9
0000000100005f68         add        w8, w8, w10
0000000100005f6c         ldr        w10, [sp, #0x8]
0000000100005f70         add        w8, w8, w10
0000000100005f74         str        w8, [sp, #0x4]
0000000100005f78         ldr        w8, [sp, #0x4]
0000000100005f7c         mov        x0, x8
0000000100005f80         ldp        x29, x30, [sp, #0x10]
0000000100005f84         add        sp, sp, #0x20
0000000100005f88         ret
                       ; endp
  • 前五句代碼,開辟??臻g,現(xiàn)場(chǎng)保護(hù),參數(shù)入棧,無需代碼還原
  • adrp x0, #0x100006000:這里體現(xiàn)Hopper的強(qiáng)大之處,已經(jīng)將運(yùn)算后的地址準(zhǔn)備好了
  • add x0, x0, #0x6540x100006000 + 0x654 = 0x100006654
  • Hopper給出的地址未經(jīng)過ASLR偏移,可在Moch-O中直接查找。將Mach-O文件拖到MachOView中,0x100006654地址對(duì)應(yīng)常量區(qū)字符串:haha
  • bl imp___stubs__printf:調(diào)用printf函數(shù),x0為參數(shù)
  • ldur w8, [x29, #-0x4]:將x29 - 0x4地址的值,寫入w8,也就是參數(shù)1的值
  • adrp x9, #0x10000d000 ~ add x9, x9, #0x4980x10000d000 + 0x498 = 0x10000d498
  • MachOView中,0x10000d498地址對(duì)應(yīng)全局?jǐn)?shù)據(jù)0xC10進(jìn)制12
  • ldr w10, x9:將x9地址的值,寫入w10。此時(shí)w10相當(dāng)于全局變量
  • add w8, w8, w10w8 += w10,參數(shù)1 += 全局變量
  • ldr w10, [sp, #0x8]:將sp + 0x8地址的值,寫入w10,也就是參數(shù)2的值
  • add w8, w8, w10w8 += w10,參數(shù)1 + 全局變量 + 參數(shù)2
  • str w8, [sp, #0x4] ~ ldr w8, [sp, #0x4]:將w8入棧,然后讀取,又寫入到w8,兩句廢話,無需代碼還原
  • mov x0, x8:將x8寫入x0x0寄存器用于函數(shù)的返回值
  • 最后三句代碼,還原x29、x30的值,恢復(fù)棧平衡并返回,無需代碼還原

根據(jù)上述匯編代碼的分析,還原func函數(shù)

int x9 = 12;

int func(int p1, int p2){
   
   const char *c_x0 = "haha";
   printf("%s", c_x0);
   
   int w8 = p1;
   
   int w10 = x9;
   w8 += w10;
   
   w10 = p2;
   w8 += w10;
   
   int x0 = w8;
   return x0;
}

將還原后的高級(jí)代碼進(jìn)行優(yōu)化,去掉繁瑣的中間步驟

int x9 = 12;

int func(int p1, int p2){
   const char *c = "haha";
   printf("%s", c);
   
   int w8 = p1 + x9 + p2;
   return w8;
}

- (void)viewDidLoad {
   func(10, 20);
}

打開ViewController.m文件,找到源碼進(jìn)行對(duì)比:

  • 源碼和還原后的代碼,語法上并不完全一樣,但邏輯上沒有區(qū)別,執(zhí)行結(jié)果更是毫無二致

還原高級(jí)代碼,并不關(guān)心代碼語法和執(zhí)行流程,只關(guān)心執(zhí)行后的結(jié)果,和預(yù)期結(jié)果一致即可

總結(jié)

常量 & 全局變量

  • 獲取常量和全局變量時(shí),會(huì)出現(xiàn)adrpadd兩條指令獲得一個(gè)地址的情況

adrp x0,1

  • adrp:(Address Page)內(nèi)存分頁尋址
  • pc寄存器的低12位清零
  • 1的值,左移12位。16進(jìn)制就是0x1000
  • 以上兩個(gè)結(jié)果相加放入x0寄存器

add x0, x0, #0x654

  • 通過add指令,在此頁的基址上加上偏移地址,從而拿到具體數(shù)據(jù)

還原高級(jí)代碼

  • 不關(guān)心代碼語法和執(zhí)行流程,只關(guān)心執(zhí)行后的結(jié)果,和預(yù)期結(jié)果一致即可
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容