iOS逆向實戰--008:反匯編

OC的反匯編

案例1:

OC的方法調用

打開Person.h,寫入以下代碼:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property(nonatomic,copy)NSString *name;

@property(nonatomic,assign)int age;

+(instancetype)person;

@end

打開Person.m,寫入以下代碼:

#import "Person.h"

@implementation Person

+(instancetype)person{
   return [[Person alloc] init];
}

@end

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

#import "ViewController.h"
#import "Person.h"

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   Person *p = [Person person];
}

@end

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

  • objc_msgSend函數有兩個默認參數,id類型的selfSEL類型的_cmd
  • 兩個參數分別對應x0x1

查看x0寄存器

   0x1046de46c <+20>: adrp   x8, 2
   0x1046de470 <+24>: add    x8, x8, #0xec8            ; =0xec8 
   0x1046de474 <+28>: ldr    x0, [x8]
  • 使用adrp + add指令,計算地址為0x1046e0ec8,賦值x8
  • x8進行尋址,寫入x0
  • x0對應id類型參數,id類型本質上是結構體指針,占8字節

查看內存中的數據

x 0x1046e0ec8
-------------------------
0x1046e0ec8: 38 0f 6e 04 01 00 00 00 b0 0f 6e 04 01 00 00 00  8.n.......n.....
0x1046e0ed8: 08 00 00 00 10 00 00 00 10 00 00 00 00 00 00 00  ................
  • 讀取8字節數據,小端模式,從右往左讀取0x01046e0f38

打印x0

po 0x01046e0f38
-------------------------
Person
  • 打印的Person是類對象

查看x1寄存器

   0x1046de478 <+32>: adrp   x8, 2
   0x1046de47c <+36>: add    x8, x8, #0xeb0            ; =0xeb0 
   0x1046de480 <+40>: ldr    x1, [x8]
  • 使用adrp + add指令,計算地址為0x1046e0eb0,賦值x8
  • x8進行尋址,寫入x1
  • x1對應SEL類型參數,占8字節

查看內存中的數據

x 0x1046e0eb0
-------------------------
0x1046e0eb0: 10 c8 88 e2 01 00 00 00 18 f6 15 e3 01 00 00 00  ................
0x1046e0ec0: 98 88 fd e2 01 00 00 00 38 0f 6e 04 01 00 00 00  ........8.n.....
  • 讀取8字節數據,小端模式,從右往左讀取0x01e288c810

打印x1

po (SEL)0x01e288c810
-------------------------
"person"
  • 打印的"person"是方法名稱

當匯編代碼中出現objc_msgSend,表示調用了一個OC方法,讀取x0x1便可知道調用了哪個對象的哪個方法

案例2:

allocinit函數

使用iOS12.2系統調試,來到Person對象的person方法

  • 調用objc_alloc_init函數

iOS12.2開始,系統優化allocinit函數,不再使用消息發送(objc_msgSend),直接調用優化后的objc_alloc_init函數


使用iOS12.1系統調試

  • 分別調用objc_alloc函數和objc_msgSend函數

調用objc_msgSend函數,實際上就是在調用init函數

  • 調用objc_msgSend函數,但這里沒有對x0進行賦值。這說明在上一個調用函數objc_alloc中返回的x0,正是objc_msgSend函數將要使用的參數

iOS10.0開始,系統優化alloc函數,直接調用優化后的objc_alloc函數。但對于init函數,依然使用objc_msgSend進行消息發送


使用iOS9.1系統調試

allocinit函數,調用兩次objc_msgSend進行消息發送

案例3:

objc_storeStrong函數

Person初始化完畢,x0返回一個實例對象。之后會調用objc_storeStrong函數

  • OC中,使用strong修飾的對象,都會調用此函數
  • viewDidLoad方法中,定義的局部變量p,就是一個強引用
  • 調用objc_storeStrong函數,并不一定會讓對象的引用計數+1,也可能調用后直接銷毀。例如:局部變量p,它沒有被外部代碼使用,出棧后就會銷毀

來到objc源碼

打開NSObject.mm文件,找到objc_storeStrong函數的實現

void
objc_storeStrong(id *location, id obj)
{
   id prev = *location;
   if (obj == prev) {
       return;
   }
   objc_retain(obj);
   *location = obj;
   objc_release(prev);
}
  • 參數location,二級指針,指向對象指針的指針
  • 參數obj,對象指針
  • 局部變量prev,將location指向的對象指針賦值給prev
  • 判斷對象相等,直接return
  • 如果不等,當前對象obj引用計數+1
  • location指向obj
  • location指向的老對象釋放

objc_storeStrong函數的目的,對一個strong修飾的對象retain,對老對象release,等價于以下代碼:

Person *p = p1;
p = p2;
  • p2賦值給p,p2引用計數+1p1釋放

回到匯編代碼

查看location參數

   0x10058a3ac <+56>: add    x8, sp, #0x8              ; =0x8 
   0x10058a3b0 <+60>: str    x0, [sp, #0x8]
   0x10058a3b4 <+64>: mov    x0, x8
  • sp + #0x8地址寫入x8
  • x0,即:局部變量p,入棧到sp + #0x8
  • x8寫入x0,此時x0是指向局部變量p的指針地址

查看obj參數

   0x10058a3b8 <+68>: mov    x8, #0x0
   0x10058a3bc <+72>: mov    x1, x8
  • #0x0,即:nil,寫入x8
  • x8寫入x1,此時x1nil

此刻調用objc_storeStrong函數觸發的邏輯:

  • prev被賦值為局部變量p
  • 判斷對象不相等
  • 調用objc_retain函數,傳入nil
  • location指向nil
  • 調用objc_release函數,傳入局部變量p,將其釋放
工具反匯編

真實場景下,對Mach-O進行分析,只能使用靜態分析,無法使用lldb動態調試。所以要學會運用工具進行反匯編

案例1:

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

#import "ViewController.h"
#import "Person.h"

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   Person *p = [Person person];
   p.name = @"Zang";
   p.age = 18;
}

@end

使用真機編譯項目,將Mach-O文件拖到Hopper中,找到viewDidLoad函數

  • Hopper可以解析出方法、屬性等名稱,作為注釋標記給開發者
  • 對于bl指令,也將跳轉地址解析為對應的函數名稱

案例2:

Mach-O中,找到Hopper轉義出的注釋

找到objc_cls_ref_PersonMach-O中的位置

0000000100006350         adrp       x8, #0x10000c000
0000000100006354         add        x8, x8, #0xd58            ; >objc_cls_ref_Person
  • 使用adrp + add指令,計算地址為0x10000cd58,賦值x8

打開Mach-O,找到0x10000cd58


找到personselector,在Mach-O中的位置

000000010000635c         adrp       x8, #0x10000c000
0000000100006360         add        x8, x8, #0xd40             ; @selector(person)
  • 使用adrp + add指令,計算地址為0x10000cd40,賦值x8

打開Mach-O,找到0x10000cd40


找到person等字符串,在Mach-O中的位置

打開Mach-O,找到0x100006a2c,存儲了方法名稱的字符串


找到imp___stubs__objc_msgSendMach-O中的位置

0000000100006368         bl         imp___stubs__objc_msgSend

HopperviewDidLoad方法中,雙擊imp___stubs__objc_msgSend

                    imp___stubs__objc_msgSend:
000000010000685c         nop             ; CODE XREF=-[ViewController viewDidLoad]+44, -[ViewController viewDidLoad]+92, -[ViewController viewDidLoad]+116
0000000100006860         ldr        x16, #0x10000c028
0000000100006864         br         x16
                       ; endp

打開Mach-O,找到0x10000685c,存儲的正是_objc_msgSend函數


找到Zang字符串,在Mach-O中的位置

000000010000638c         adrp       x2, #0x100008000
0000000100006390         add        x2, x2, #0x8              ; @"Zang"
  • 使用adrp + add指令,計算地址為0x100008008,賦值x8

HopperviewDidLoad方法中,雙擊Zang

       ; Section __cfstring
       ; Range: [0x100008008; 0x100008028[ (32 bytes)
       ; File offset : [32776; 32808[ (32 bytes)
       ;   S_REGULAR

                    cfstring_Zang:
0000000100008008         dq         ___CFConstantStringClassReference, 0x7c8, 0x10000755d, 0x4 ; "Zang", DATA XREF=-[ViewController viewDidLoad]+84

打開Mach-O,找到0x100008008,存儲字符串常量Zang的相關信息

案例3:

使用Hopper查看代碼的流程圖

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

#import "ViewController.h"

@implementation ViewController

void test1(bool b){
   if(b){
       test2(b);
   }
   else{
       test3();
   }
}

void test2(bool b){
   if(b){
       test3();
   }
   else{
       test4();
   }
}

void test3(){ NSLog(@"test3"); }
void test4(){ NSLog(@"test4"); }

- (void)viewDidLoad {
//    [super viewDidLoad];
   
   int a = 1;
   int b = 2;
   
   if(a == b){
       test1(YES);
   }
   else{
       test2(NO);
   }
}

@end

使用真機編譯項目,將Mach-O文件拖到Hopper中,找到viewDidLoad函數

Hopper中,點擊CFG mode按鈕

切換到viewDidLoad函數的流程圖,清晰的展示出不同的代碼分支

雙擊test1函數,切換到test1函數中的代碼分支

還原高級代碼時,遇到復雜的邏輯分支,可以使用代碼的流程圖進行分析

案例4:

使用Hopper查看偽碼模式

Hopper中,點擊Pseudo-code mode按鈕

查看Hopper幫我們還原的viewDidLoad函數的偽碼

int -[ViewController viewDidLoad]() {
   r31 = r31 - 0x30;
   *(r31 + 0x20) = r29;
   *(r31 + 0x28) = r30;
   *(r31 + 0x18) = r0;
   *(r31 + 0x10) = r1;
   *(r31 + 0xc) = zero_extend_64(0x1);
   *(r31 + 0x8) = zero_extend_64(0x2);
   if (*(r31 + 0xc) == *(r31 + 0x8)) {
           r0 = _test1(zero_extend_64(0x1) & 0x1);
   }
   else {
           r0 = _test2(zero_extend_64(0x0) & 0x1);
   }
   return r0;
}
Block的反匯編

日常開發中,Block有時作為參數傳遞,有時作為返回值,但大多數情況下,Block會作為方法的參數回調

在逆向分析中,需要定位Blockinvoke。只有找到invoke,才能找到回調方法中的代碼邏輯

案例1:

查看GlobalBlock的匯編代碼

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

#import "ViewController.h"

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];

   void(^block)(void) = ^() {
       NSLog(@"block");
   };
   
   block();
}

@end

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

使用register read x0命令,查看x0寄存器

x0 = 0x00000001029d8028  002--OC`__block_literal_global
  • x0存儲的是一個GlobalBlock
  • Block內部不使?外部變量,或者只使?靜態變量和全局變量,會形成一個GlobalBlock,它位于全局區Block

使用po 0x00000001029d8028命令,打印內存中的數據

<__NSGlobalBlock__: 0x1029d8028>
signature: "v8@?0"
invoke   : 0x1029d6384 (/private/var/containers/Bundle/Application/2B6AC788-34A7-46BB-A44B-2237AFD48A2A/002--OC.app/002--OC`__29-[ViewController viewDidLoad]_block_invoke)
  • NSGlobalBlockBlockisa
  • invoke : 0x1029d6384是代碼實現的所在位置

來到libclosure源碼

打開Block_private.h文件,找到Block的定義

struct Block_layout {
   void *isa;
   volatile int32_t flags;
   int32_t reserved;
   BlockInvokeFunction invoke;
   struct Block_descriptor_1 *descriptor;
};
  • Block是一個結構體
  • isa8字節,flagsreserved共占8字節
  • 16字節之后,就是invoke,最后是descriptor描述

回到匯編代碼

使用x/8g 0x1029d8028,查看內存中的數據

  • 16字節之后,即:invoke,地址和上面打印的0x1029d6384一致

Hopper中,找到Blockinvoke

使用真機編譯項目,將Mach-O文件拖到Hopper中,找到viewDidLoad函數

  • Hopper直接標記出___block_literal_global

雙擊___block_literal_global,找到Block的結構體對象

雙擊invoke,找到invoke中的代碼邏輯

                    ___29-[ViewController viewDidLoad]_block_invoke:
0000000100006384         sub        sp, sp, #0x20               ; Objective C Block defined at 0x100008028, DATA XREF=0x100008038
0000000100006388         stp        x29, x30, [sp, #0x10]
000000010000638c         add        x29, sp, #0x10
0000000100006390         str        x0, [sp, #0x8]
0000000100006394         str        x0, sp
0000000100006398         adrp       x0, #0x100008000            ; argument #1 for method imp___stubs__NSLog
000000010000639c         add        x0, x0, #0x48               ; @"block"
00000001000063a0         bl         imp___stubs__NSLog
00000001000063a4         ldp        x29, x30, [sp, #0x10]
00000001000063a8         add        sp, sp, #0x20
00000001000063ac         ret
                       ; endp

案例2:

查看StackBlock的匯編代碼

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

- (void)viewDidLoad {
//    [super viewDidLoad];

   int a = 1;
   void(^block)(void) = ^() {
       NSLog(@"block:%i", a);
   };
   
   block();
}

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


標記的第1段代碼

   0x1021e62dc <+28>:  add    x9, sp, #0x8              ; =0x8 
   0x1021e62e0 <+32>:  adrp   x10, 2
   0x1021e62e4 <+36>:  ldr    x10, [x10]
   0x1021e62e8 <+40>:  str    x10, [sp, #0x8]
  • sp + #0x8的地址,賦值給x9
  • adrp指令,計算地址為0x1021e8000,賦值給x10
  • x10尋址,將值再寫入x10
  • x10入棧sp + #0x8的位置,此時x9成為指針,指向x10

使用x/8g 0x1021e8000,查看內存中的數據

0x1021e8000: 0x00000001f0a50830 0x000000019b84ef94
0x1021e8010: 0x0000000000000000 0x0000000000000024
0x1021e8020: 0x00000001021e6a26 0x00000001021e6ad2
0x1021e8030: 0x00000001f101a280 0x00000000000007d0

使用po 0x00000001f0a50830命令,打印第一個8字節數據

__NSStackBlock__
  • 存儲的是一個StackBlock
  • 在內部使?局部變量或者OC屬性,但是不能賦值給強引?或者
    Copy修飾的變量,它位于棧區Block

標記的第2段代碼

   0x1021e62f8 <+56>:  adrp   x10, 0
   0x1021e62fc <+60>:  add    x10, x10, #0x358          ; =0x358
  • 使用adrp + add指令,計算地址為0x1021e6358,賦值x10
  • 此處疑似是Blockinvoke

使用dis -s 0x1021e6358指令,查看invoke的匯編代碼

002--OC方法的本質`__29-[ViewController viewDidLoad]_block_invoke:
   0x1021e6358 <+0>:  sub    sp, sp, #0x30             ; =0x30 
   0x1021e635c <+4>:  stp    x29, x30, [sp, #0x20]
   0x1021e6360 <+8>:  add    x29, sp, #0x20            ; =0x20 
   0x1021e6364 <+12>: stur   x0, [x29, #-0x8]
   0x1021e6368 <+16>: str    x0, [sp, #0x10]
   0x1021e636c <+20>: ldr    w8, [x0, #0x20]
   0x1021e6370 <+24>: mov    x0, x8
   0x1021e6374 <+28>: adrp   x9, 2

Hopper中,查看StackBlock

使用真機編譯項目,將Mach-O文件拖到Hopper中,找到viewDidLoad函數

  • Hopper直接標記出invoke的位置
  • invoke下面是descriptor

雙擊_block_invoke,找到invoke中的代碼邏輯

                    ___29-[ViewController viewDidLoad]_block_invoke:
0000000100006358         sub        sp, sp, #0x30                ; DATA XREF=-[ViewController viewDidLoad]+60
000000010000635c         stp        x29, x30, [sp, #0x20]
0000000100006360         add        x29, sp, #0x20
0000000100006364         stur       x0, [x29, #-0x8]
0000000100006368         str        x0, [sp, #0x10]
000000010000636c         ldr        w8, [x0, #0x20]
0000000100006370         mov        x0, x8
0000000100006374         adrp       x9, #0x100008000
0000000100006378         add        x9, x9, #0x30                ; cfstring_b
000000010000637c         str        x0, [sp, #0x8]
0000000100006380         mov        x0, x9
0000000100006384         mov        x9, sp
0000000100006388         ldr        x10, [sp, #0x8]
000000010000638c         str        x10, x9
0000000100006390         bl         imp___stubs__NSLog
0000000100006394         ldp        x29, x30, [sp, #0x20]
0000000100006398         add        sp, sp, #0x30
000000010000639c         ret
                       ; endp

通過匯編代碼和invoke的位置來看,StackBlock案例1中的GlobalBlock區別很大

總結

OC的反匯編

  • 出現objc_msgSend,表示調用了一個OC方法,讀取x0x1便可知道調用了哪個對象的哪個方法
  • 初始化的allocinit函數,在iOS12.2及更高版本,優化為objc_alloc_init函數
  • objc_storeStrong函數,對一個strong修飾的對象retain,對老對象release。有些情況下,也會直接釋放對象

工具反匯編

  • 運用Hopper工具,查看匯編代碼、流程圖、偽碼模式
  • Hopper可以解析出方法、屬性等名稱,作為注釋標記給開發者,本質上也是通過Mach-O讀取相應數據

Block的反匯編

  • Block的類型:全局區、堆區、棧區。重點掌握全局區和棧區Block的分析
  • 在逆向分析中,需要定位Blockinvoke。只有找到invoke,才能找到回調方法中的代碼邏輯

了解更多匯編指令

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容