天天看點

保護App重要資料,防止Cycript/Runtime修改

這一篇文章着重于保護重要資料不被攻擊者使用cycript或者runtime修改,概要内容如下:

防止choose(類名)

禁忌,二重存在

自己的記憶體塊

虛僞的setter/getter

加密記憶體資料

<a target="_blank" href="https://blog.0xbbc.com/2015/05/protection-against-cycriptruntime/">english version is here</a>

以下内容均以此假想情況為基礎: 我們有一個person類,它的定義如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

<code>@interface  person : nsobject {</code>

<code>    </code><code>nsstring * _name;</code>

<code>    </code><code>int _age;</code>

<code>}</code>

<code>@property (strong, nonatomic, readonly) nsstring * name;</code>

<code>@property (nonatomic, readonly) int age;</code>

<code> </code> 

<code>- (instancetype)initwithname:(nsstring *)name age:(int)age;</code>

<code>@end</code>

<code>@implementation person</code>

<code>@synthesize name = _name;</code>

<code>@synthesize age = _age;</code>

<code>- (instancetype)initwithname:(nsstring *)name age:(int)age{</code>

<code>    </code><code>self = [self init];</code>

<code>    </code><code>if</code> <code>(self) {</code>

<code>        </code><code>_name = name;</code>

<code>        </code><code>_age = age;</code>

<code>    </code><code>}</code>

<code>    </code><code>return</code> <code>self;</code>

<code>- (void)setname:(nsstring *)name {</code>

<code>    </code><code>if</code> <code>(name != _name) {</code>

<code>        </code><code>_name = name ;</code>

<code>- (void)setage:(int)age {</code>

<code>    </code><code>_age = age;</code>

<code>- (nsstring *)name {</code>

<code>    </code><code>return</code> <code>_name;</code>

<code>- (int)age {</code>

<code>    </code><code>return</code> <code>_age;</code>

現在我們需要保護這個類的資料,雖然我們在@property裡聲明了這兩個都是readonly,但是因為objective-c的runtime特性,這個屬性說了基本等于沒說(對于破解者而言)。 那麼我們要怎麼做才能保護呢?

我們知道,在cycript中可以很友善的使用choose(類名)來擷取到app中該類所有的執行個體變量(圖1),那麼我們就先從這裡下手吧!

保護App重要資料,防止Cycript/Runtime修改

解決方案: 重載- (nsstring *)description方法。效果如圖2所示。

<code>- (nsstring *)description {</code>

<code>    </code><code>return</code> <code>[nsstring stringwithformat:@</code><code>"this person is named %@, aged %d."</code><code>, self.name, self.age];</code>

保護App重要資料,防止Cycript/Runtime修改

上面雖然在cycript中用choose函數拿不到了,但是如果一開始就被hook了init方法怎麼辦呢?

解決方案:memcpy一份。

首先确定person類執行個體的大小:(類指針大小+所有成員變量大小)

<code>ssize_t object_size = sizeof(person *) + sizeof(nsstring *) + sizeof(int);</code>

然後就可以愉快的memcpy了:

<code>person * normal_man = [[person alloc] initwithname:@</code><code>"nobody"</code> <code>age:0];</code>

<code>void * superman = malloc(object_size);</code>

<code>memcpy(superman, (__bridge void *)normal_man, object_size);</code>

在用的時候,通過__bridge轉換:

<code>[(__bridge person *)superman setname:@</code><code>"superman"</code><code>];</code>

代碼片段:

<code>    </code><code>person * normal_man = [[person alloc] initwithname:@</code><code>"nobody"</code> <code>age:0];</code>

<code>    </code><code>ssize_t object_size = sizeof(person *) + sizeof(nsstring *) + sizeof(int);</code>

<code>    </code><code>void * superman = malloc(object_size);</code>

<code>    </code><code>memcpy(superman, (__bridge void *)normal_man, object_size);</code>

<code>    </code><code>[(__bridge person *)superman setname:@</code><code>"superman"</code><code>];</code>

<code>    </code><code>[(__bridge person *)superman setage:20];</code>

<code>    </code><code>/**</code>

<code>     </code><code>*  @brief  為了示範友善加的while</code>

<code>     </code><code>*/</code>

<code>    </code><code>while</code> <code>(1) {</code>

<code>        </code><code>nslog(@</code><code>"normal:   %p %@"</code><code>,normal_man, [normal_man name]);</code>

<code>        </code><code>nslog(@</code><code>"superman: %p %@"</code><code>,superman, [(__bridge person *)superman name]);</code>

<code>        </code><code>sleep(2);</code>

那麼為了模拟實際情況(即init方法被hook,拿到了normal_man的位址),我們直接在nslog裡輸出。

使用cycript攻擊的實際效果如圖3、圖4:

通過hook init方法,拿到了normal_man的位址0x7fbffbe06b00。

保護App重要資料,防止Cycript/Runtime修改

在cycript中使用choose,隻能看見兩個字元串。現在直接調用[#0x7fbffbe06b00 setname:@"cracker"];更改name屬性。

保護App重要資料,防止Cycript/Runtime修改

可以看到normal_man的name的确被更改了。而我們memcpy的superman表示無壓力。

那麼superman的位址也被找到了的話,怎麼辦呢?如圖5

保護App重要資料,防止Cycript/Runtime修改

p.s 事實上,它也的确被找到了,cycript會檢索所有malloc的記憶體,圖4、圖5裡,choose執行後的兩句nsstring就是證明,隻不過因為我們重載了description方法,才沒有直接看到位址。

那麼我們把這個normal_man複制到自己的一個記憶體區塊如何呢?正好借用之前寫的memoryregion。試試看吧!

代碼片段:(其餘部分與上面的相同)

<code>    </code><code>memoryregion mmgr = memoryregion(1024);</code>

<code>    </code><code>void * superman = mmgr.malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size);</code>

實際效果(圖6):

保護App重要資料,防止Cycript/Runtime修改

可以看到,現在choose找不到處于memoryregion中的superman。

不過就算找不到,cracker還可以hook這個類的setter和getter呀!我們又要如何應對呢?

讓我們把setter和getter改成這個樣子:

<code>    </code><code>_name = @</code><code>"naive"</code><code>;</code>

<code>    </code><code>_age = int32_max;</code>

<code>    </code><code>return</code> <code>@</code><code>"233"</code><code>;</code>

<code>}   </code>

<code>    </code><code>return</code> <code>int32_min;</code>

這樣cracker們通過setter方法就改不了了,也不能通過getter來擷取,隻能hookivar了。當然我們也是,那麼我們自己要怎麼修改呢?添加兩個c函數吧!

<code>__attribute__((always_inline)) void setname(void * obj, nsstring * newname) {</code>

<code>    </code><code>void * ptr = (void *)((long)(long *)(obj) + sizeof(person *));</code>

<code>    </code><code>memcpy(ptr, (void*) &amp;newname, sizeof(char) * newname.length);</code>

<code>__attribute__((always_inline)) void setage(void * obj, int newage) {</code>

<code>    </code><code>void * ptr = (void *)((long)(long *)obj + sizeof(person *) + sizeof(nsstring *));</code>

<code>    </code><code>memcpy(ptr, &amp;newage, sizeof(int));</code>

在修改的時候使用:

<code>setname(superman, @</code><code>"superman"</code><code>);</code>

<code>setage (superman, 20);</code>

在擷取的時候:

<code>nslog(@</code><code>"this person is named %@, aged %d"</code><code>, *((cfstringref *)(void*)((long)(long *)(superman) + sizeof(person *))), *((int *)((long)(long *)superman + sizeof(person *) + sizeof(nsstring *))));</code>

加密記憶體區塊

在我們把person類改成上面那個樣子之後,已經能阻止大部分隻用cycript就想調戲我們的app的人了。

然而,如果cracker們搜尋記憶體的話,還是有可能找到一些資料的,比如這裡superman的年齡,

superman的記憶體位址是0x102800f00,_age在(0x102800f00 + sizeof(person *) + sizeof(nsstring *)),也就是0x102800f10,如圖7。

保護App重要資料,防止Cycript/Runtime修改

那麼我們不用的時候加密這塊記憶體,用的時候再解密,示範用的加密、解密函數如下,

<code>__attribute__((always_inline)) void encryptsuperman(void ** data_ptr, ssize_t length) {</code>

<code>    </code><code>char * data = (char *) * data_ptr;</code>

<code>    </code><code>for</code> <code>(ssize_t i = 0; i &lt; length; i++) {</code>

<code>        </code><code>data[i] ^= 0xbbc - i;</code>

<code>__attribute__((always_inline)) void decryptsuperman(void ** data_ptr, ssize_t length) {</code>

使用代碼:

<code>    </code><code>void * superman = mmgr.malloc(object_size);</code>

<code>encryptsuperman(&amp;superman, object_size);</code>

<code>        </code><code>nslog(@</code><code>"normal:   %p %@"</code><code>,normal_man,[normal_man name]);</code>

<code>        </code><code>nslog(@</code><code>"superman: %p"</code><code>,superman);</code>

<code>        </code><code>decryptsuperman(&amp;superman, object_size);</code>

<code>        </code><code>nslog(@</code><code>"this person is named %@, aged %d"</code><code>,*((cfstringref *)(void*)((long)(long *)(superman) + sizeof(person *))), *((int *)((long)(long *)superman + sizeof(person *) + sizeof(nsstring *))));</code>

<code>        </code><code>encryptsuperman(&amp;superman, object_size);</code>

<code>        </code><code>sleep(5);</code>

現在再來看看記憶體裡的資料(圖8):

保護App重要資料,防止Cycript/Runtime修改

嗯,似乎是沒問題了呢~

http://www.cocoachina.com/ios/20150511/11801.html

繼續閱讀