iOS逆向實戰--004:常量 & 全局變量

內存五大區,實際是指虛擬內存,而不是真實物理內存,它們是在邏輯上劃分的

  • 棧區:存放參數、局部變量、臨時數據。可讀,可寫
  • 堆區:向系統申請區域,并指明大小。可讀,可寫
  • 全局區:存放全局變量和靜態變量。可讀,可寫
  • 常量區:存放常量,整個程序運行期不能被改變。只讀
  • 代碼區:存放代碼。可讀,可執行
常量

案例:

編譯器如何存儲常量?

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

#import "ViewController.h"

@implementation ViewController

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

@end

真機運行項目,來到viewDidLoad方法

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

按照函數調用的原則,printf函數的參數應該使用x0傳遞。打印x0寄存器:

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

拆解指令

adrp x0, 1指令:

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

add x0, x0, #0x654指令:

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

lldb中,通過x 0x100af6654命令,按16進制格式輸出

  • 存儲的常量:haha
  • 16進制6810進制104,即:hASCII
  • 16進制6110進制91,即:aASCII

原理解析

adrp指令計算后,得到一個內存結果,尾數為000。從0x000 ~ 0xfff剛好是4096,而macOS系統中,內存分頁大小剛好是4KB,所以這里得到一個大小為4KB的頁的基址

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

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

全局變量

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

靜態分析時,通過匯編代碼和指令,無法區分當前數據是全局變量還是常量,只能通過內存的情況做理性判斷,也可以通過數據所在地址減去ASLR偏移地址,然后在Moch-O中進行定位

案例:

編譯器如何存儲全局變量?

打開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

真機運行項目,來到func方法

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

使用View Memory查看內存數據

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

繼續執行代碼,x9是偏移后的地址,讀取x9地址的值,寫入w10

還原高級代碼

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

案例:

借助Hopper進行高級代碼的還原

使用真機編譯Demo項目,找到編譯后的Demo.app文件

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

Mach-O文件拖到Hopper中,點擊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
  • 前五句代碼,開辟棧空間,現場保護,無需代碼還原
  • movz w0, #0xa ~ movz w1, #0x14w0w1用于給函數傳遞參數,推斷這里應該有兩個參數需要傳遞給func函數
  • bl _func:調用func函數
  • 最后三句代碼,還原x29x30的值,恢復棧平衡并返回,無需代碼還原

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

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

找到func函數

                    _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
  • 前五句代碼,開辟棧空間,現場保護,參數入棧,無需代碼還原
  • adrp x0, #0x100006000:這里體現Hopper的強大之處,已經將運算后的地址準備好了
  • add x0, x0, #0x6540x100006000 + 0x654 = 0x100006654
  • Hopper給出的地址未經過ASLR偏移,可在Moch-O中直接查找。將Mach-O文件拖到MachOView中,0x100006654地址對應常量區字符串:haha
  • bl imp___stubs__printf:調用printf函數,x0為參數
  • ldur w8, [x29, #-0x4]:將x29 - 0x4地址的值,寫入w8,也就是參數1的值
  • adrp x9, #0x10000d000 ~ add x9, x9, #0x4980x10000d000 + 0x498 = 0x10000d498
  • MachOView中,0x10000d498地址對應全局數據0xC10進制12
  • ldr w10, x9:將x9地址的值,寫入w10。此時w10相當于全局變量
  • add w8, w8, w10w8 += w10參數1 += 全局變量
  • ldr w10, [sp, #0x8]:將sp + 0x8地址的值,寫入w10,也就是參數2的值
  • add w8, w8, w10w8 += w10參數1 + 全局變量 + 參數2
  • str w8, [sp, #0x4] ~ ldr w8, [sp, #0x4]:將w8入棧,然后讀取,又寫入到w8,兩句廢話,無需代碼還原
  • mov x0, x8:將x8寫入x0x0寄存器用于函數的返回值
  • 最后三句代碼,還原x29x30的值,恢復棧平衡并返回,無需代碼還原

根據上述匯編代碼的分析,還原func函數

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;
}

將還原后的高級代碼進行優化,去掉繁瑣的中間步驟

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文件,找到源碼進行對比:

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

還原高級代碼,并不關心代碼語法和執行流程,只關心執行后的結果,和預期結果一致即可

總結

常量 & 全局變量

  • 獲取常量和全局變量時,會出現adrpadd兩條指令獲得一個地址的情況

adrp x0,1

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

add x0, x0, #0x654

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

還原高級代碼

  • 不關心代碼語法和執行流程,只關心執行后的結果,和預期結果一致即可
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容