你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 保護App重要數據,防止Cycript

保護App重要數據,防止Cycript

編輯:IOS開發基礎

1.jpg

這一篇文章著重於保護重要數據不被攻擊者使用Cycript或者Runtime修改,概要內容如下:

  • 防止choose(類名)

  • 禁忌,二重存在

  • 自己的內存塊

  • 虛偽的setter/getter

  • 加密內存數據

English version is here

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

@interface  Person : NSObject {
    NSString * _name;
    int _age;
}
@property (strong, nonatomic, readonly) NSString * name;
@property (nonatomic, readonly) int age;
 
- (instancetype)initWithName:(NSString *)name age:(int)age;
@end
@implementation Person
 
@synthesize name = _name;
@synthesize age = _age;
 
- (instancetype)initWithName:(NSString *)name age:(int)age{
    self = [self init];
    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}
- (void)setName:(NSString *)name {
    if (name != _name) {
        _name = name ;
    }
}
- (void)setAge:(int)age {
    _age = age;
}
- (NSString *)name {
    return _name;
}
- (int)age {
    return _age;
}
@end

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

防止choose(類名)

我們知道,在Cycript中可以很方便的使用choose(類名)來獲取到App中該類所有的實例變量(圖1),那麼我們就先從這裡下手吧!

1431308375706144.png

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

- (NSString *)description {
    return [NSString stringWithFormat:@"This person is named %@, aged %d.", self.name, self.age];
}

1431308403659869.png

禁忌,二重存在

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

解決方案:memcpy一份。

首先確定Person類實例的大小:(類指針大小+所有成員變量大小)

ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);

然後就可以愉快的memcpy了:

Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
void * superman = malloc(object_size);
memcpy(superman, (__bridge void *)normal_man, object_size);

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

[(__bridge Person *)superman setName:@"Superman"];

代碼片段:

    Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
    ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
    void * superman = malloc(object_size);
    memcpy(superman, (__bridge void *)normal_man, object_size);
    [(__bridge Person *)superman setName:@"Superman"];
    [(__bridge Person *)superman setAge:20];
    /**
     *  @brief  為了演示方便加的while
     */
    while (1) {
        NSLog(@"Normal:   %p %@",normal_man, [normal_man name]);
        NSLog(@"Superman: %p %@",superman, [(__bridge Person *)superman name]);
        sleep(2);
    }

那麼為了模擬實際情況(即init方法被Hook,拿到了normal_man的地址),我們直接在NSLog裡輸出。

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

通過Hook init方法,拿到了normal_man的地址0x7fbffbe06b00。

1431308455143487.png

在Cycript中使用choose,只能看見兩個字符串。現在直接調用[#0x7fbffbe06b00 setName:@"Cracker"];更改name屬性。

1431308475318686.png

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

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

1431308494680358.png

P.S 事實上,它也的確被找到了,cycript會檢索所有malloc的內存,圖4、圖5裡,choose執行後的兩句NSString就是證明,只不過因為我們重載了description方法,才沒有直接看到地址。

自己的內存塊

那麼我們把這個normal_man復制到自己的一個內存區塊如何呢?正好借用之前寫的MemoryRegion。試試看吧!

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

    ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
    MemoryRegion mmgr = MemoryRegion(1024);
    void * superman = mmgr.malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size);

實際效果(圖6):

1431308525455461.png

可以看到,現在choose找不到處於MemoryRegion中的superman。

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

虛偽的setter/getter

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

- (void)setName:(NSString *)name {
    _name = @"Naive";
}
- (void)setAge:(int)age {
    _age = INT32_MAX;
}
- (NSString *)name {
    return @"233";
}   
- (int)age {
    return INT32_MIN;
}

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

__attribute__((always_inline)) void setName(void * obj, NSString * newName) {
    void * ptr = (void *)((long)(long *)(obj) + sizeof(Person *));
    memcpy(ptr, (void*) &newName, sizeof(char) * newName.length);
}
__attribute__((always_inline)) void setAge(void * obj, int newAge) {
    void * ptr = (void *)((long)(long *)obj + sizeof(Person *) + sizeof(NSString *));
    memcpy(ptr, &newAge, sizeof(int));
}

在修改的時候使用:

setName(superman, @"Superman");
setAge (superman, 20);

在獲取的時候:

NSLog(@"This person is named %@, aged %d", *((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *))));

加密內存區塊

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

然而,如果Cracker們搜索內存的話,還是有可能找到一些數據的,比如這裡superman的年齡,

superman的內存地址是0x102800f00,_age在(0x102800f00 + sizeof(Person *) + sizeof(NSString *)),也就是0x102800f10,如圖7。

1431308580600870.png

那麼我們不用的時候加密這塊內存,用的時候再解密,演示用的加密、解密函數如下,

__attribute__((always_inline)) void encryptSuperman(void ** data_ptr, ssize_t length) {
    char * data = (char *) * data_ptr;
    for (ssize_t i = 0; i < length; i++) {
        data[i] ^= 0xBBC - i;
    }
}
__attribute__((always_inline)) void decryptSuperman(void ** data_ptr, ssize_t length) {
    char * data = (char *) * data_ptr;
    for (ssize_t i = 0; i < length; i++) {
        data[i] ^= 0xBBC - i;
    }
}

使用代碼:

    Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0];
    ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int);
    MemoryRegion mmgr = MemoryRegion(1024);
    void * superman = mmgr.malloc(object_size);
    memcpy(superman, (__bridge void *)normal_man, object_size);
setName(superman, @"Superman");
setAge (superman, 20);
encryptSuperman(&superman, object_size);
    /**
     *  @brief  為了演示方便加的while
     */
    while (1) {
        NSLog(@"Normal:   %p %@",normal_man,[normal_man name]);
        NSLog(@"Superman: %p",superman);
        decryptSuperman(&superman, object_size);
        NSLog(@"This person is named %@, aged %d",*((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *))));
        encryptSuperman(&superman, object_size);
        sleep(5);
}

現在再來看看內存裡的數據(圖8):

1431308614750175.png

嗯,似乎是沒問題了呢~

完整示例代碼,https://github.com/BlueCocoa/HookMeIfYouCan

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved