如何防止客户端被破解

117 篇文章 0 订阅
73 篇文章 1 订阅

很多应用都需要用户登录或者签名认证,这可能需要在客户端保存登录信息、签名密钥、加密算法等。如何保证这些重要信息不被窃取,算法不被破解,这些成为应用开发中很重要的内容,同样也是最容易忽视的地方。一个小小的细节可能就成为整个系统的突破口,这里从实际工程角度总结了一些容易忽视的细节和常用的方法。

密钥保存在外部

  • 钥匙扣

    密钥保存在Keychain并不安全,iOS越狱后可以导出Keychain的内容。应该尽量避免存放重要信息(如:token、用户名、密码等)在Keychain中,即使要存放,也一定要加密后存放。参考http://blog.csdn.net/yiyaaixuexi/article/details/18404343

  • 文件

    保存在应用程序bundle、plist等配置文件更不安全,但可以使用隐写术等方式迷惑hackers。有请Lena示范:

     

    两张图片看起来是一模一样的,但是右边的图片里却夹带了一些其他内容,这就是潜伏在Lena中的密码,用diff工具比较下这两张图片,你会发现不同的地方是右边的图片最后附加了一串字符:app秘诀就是“abcdefg123456”这里的隐写方式很简单:cat文件>> Lena.jpg,既不破坏图片原本的信息(或者损失一点点原有信息),又能附加额外的信息,这就是隐写术的原理。这里只是一个简单的例子,没有人真这么使用。有很多更隐蔽的做法,比如把要隐藏的信息分散到图片的每个像素中,例如RGB888的图片,对红蓝分量最后一个bit位进行修改并不会影响图片的质量(因为人眼对对红蓝不敏感),这样一个像素(3byte)就可以存储2bit的信息,4个像素(12byte)就可以夹带1byte的信息了。

    Xcode打包时会对png图片做特殊处理,如果将密码携带在png中,可能会在使用的时候无法复原。当然现在的隐写术非常多,不只是图片能作为载体,视频、音乐等文件都可以,隐写的方法也多种多样,选择适合自己的就行,据说基地组织就是通过岛国电影传递信息的。

写在代码里安全吗?

下面的代码很常见

1 
2 
3 
4
  定义 kSecret  “ABCD1234” 

  //或者
  常量 的char *  kSecret  =  “ABCD1234” ;

这是非常危险的,因为常量会被直接编译到可执行文件的data段,只要对生成的可执行文件使用stringsotool等命令就可以dump出原始字符串。

对密码加密

为了使密码不直接出现在可执行文件中,可以对密码加密存储,使用的时候再解密。例如用AES对密码abcd1234加密,对称密钥为kCipherKey="abcdefgh12345678",加密后的密码用kSecret表示。使用密码时,再通过kCipherKeykSecret计算出来:

snippet1
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 
38 
39 
40 
41 
42
const  char *  kCipherKey = "abcdefgh12345678" ; 
const  char *  kSecret = " \x7e\x77\x64\x3c\xa7\xd4\x6d\x46\x29\x8b\xe3\x23\x9f\x1a\x5c\xdb " ; 

char *  getSecret ()  { 
    char *  buf  =  NULL ; 
    CCCryptorRef  cryptor  =  NULL ; 
    uint8_t  iv [ kCCBlockSizeAES128 ]; 
    memset ( iv ,  0 ,  kCCBlockSizeAES128 ); 

    size_t  bufsize  =  0 ; 
    size_t  moved  =  0 ; 
    size_t  total  =  0 ; 

    size_t  inLength  =  strlen ( kSecret ); 

    CCCryptorCreate ( kCCDecrypt ,  kCCAlgorithmAES128 , 
                    kCCOptionPKCS7Padding , 
                    kCipherKey ,  strlen ( kCipherKey ), 
                    iv ,  & cryptor ); 
    bufsize  =  CCCryptorGetOutputLength ( cryptor ,  inLength ,  true ); 
    buf  =  ( char * ) malloc ( bufsize ); 
    memset ( buf ,  0 ,  bufsize ); 

    CCCryptorUpdate ( cryptor , 
                    kSecret , inLength , 
                    buf ,  bufsize ,  & moved ); 
    total  +=  moved ; 

    CCCryptorFinal ( cryptor , 
                   buf + total , 
                   bufsize - total ,  & moved ); 
    CCCryptorRelease ( cryptor ); 

    return  buf ; 
} 


int  main ( int  argc ,  char  *  argv [])  { 
  char *  secret  =  getSecret (); 
  printf ( "%s \n " ,  secret ); 
  free ( secret ); 
}

上面的代码不再明文出现abcd1234,而是被加密算子kCipherKey和加密后的密钥kSecret替代,密码只是在需要的时候临时计算出来。但是这里仍然有缺陷:加密算子kCipherKey和加密后的密钥kSecret仍然存储在可执行文件的data段中,留下了蛛丝马迹。我们可以给kCipherKey取一个有迷惑性的字符串,比如"network错误, timeout"或者使用非字符值,使其不可读。但这都不完美,不在data段中存储这些信息最好。

参数传递的秘密

上面的代码稍做修改

snippet2
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 
38 
39 
40 
41 
42
//注意这里
的#define kCipherKey((uint8_t有[]){'A','B','C','D','E','F','G','H','1', “2”,“3”,“4”,“5”,“6”,“7”,“8”}) 
#定义kSecret ((uint8_t[]){0x7e,0x77,0x64,0x3c,0xa7,0xd4,0x6d,0x46,0x29,0x8b,0xe3,0x23,0x9f,0x1a,0x5c,0xdb}) 

char *  getSecret ()  { 
    char *  buf  =  NULL ; 
    CCCryptorRef  cryptor  =  NULL ; 
    uint8_t  iv [ kCCBlockSizeAES128 ]; 
    memset ( iv ,  0 ,  kCCBlockSizeAES128 ); 

    size_t  bufsize  =  0 ; 
    size_t  moved  =  0 ; 
    size_t  total  =  0 ; 

    size_t  inLength  =  sizeof ( kSecret ); 

    CCCryptorCreate ( kCCDecrypt ,  kCCAlgorithmAES128 , 
                    kCCOptionPKCS7Padding , 
                    kCipherKey ,  sizeof ( kCipherKey ), 
                    iv ,  & cryptor ); 
    bufsize  =  CCCryptorGetOutputLength ( cryptor ,  inLength ,  true ); 
    buf  =  ( char * ) malloc ( bufsize ); 
    memset ( buf ,  0 ,  bufsize ); 

    CCCryptorUpdate ( cryptor , 
                    kSecret , inLength , 
                    buf ,  bufsize ,  & moved ); 
    total  +=  moved ; 

    CCCryptorFinal ( cryptor , 
                   buf + total , 
                   bufsize - total ,  & moved ); 
    CCCryptorRelease ( cryptor ); 

    return  buf ; 
} 

int  main ( int  argc ,  char  *  argv [])  { 
    char *  secret  =  getSecret (); 
    printf ( "%s \n " ,  secret ); 
    free ( secret ); 
}

看似和上面代码没什么区别,除了传入的参数类型变了,其余没什么变化。正是这一点带来了巨大的变化,对比一下调用CCCryptorCreate时的汇编代码:

snippet1拆机
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
Demo ` getSecret  at  main . m: 58 : 
0x31f04 :   push    { r4 ,  r5 ,  r6 ,  r7 ,  lr } 
0x31f06 :   add     r7 ,  sp ,  # 0xc 
0x31f08 :   push . w  { r8 ,  r10 ,  r11 } 
0x31f0c :   sub     sp ,  # 0x28 
0x31f0e :   movw    r0 ,  # 0x112a 
0x31f12 :   vmov . i32  q8 ,  # 0x0 
0x31f16 :   movt    r0 ,  # 0x0 
0x31f1a :   movw    r8 ,  # 0x16d8 
0x31f1e :   add     r0 ,  pc 
0x31f20 :   movt    r8 ,  # 0x0 
0x31f24 :   add     r8 ,  pc 
0x31f26 :   add     r6 ,  sp ,  # 0x14 
0x31f28 :   ldr . w   r10 ,  [ r0 ] 
0x31f2c :   ldr . w   r0 ,  [ r10 ] 
0x31f30 :   str     r0 ,  [ sp ,  # 0x24 ] 
0x31f32 :   movs    r0 ,  # 0x0 
0x31f34 :   str     r0 ,  [ sp ,  # 0x10 ] 
0x31f36 :   str     r0 ,  [ sp ,  # 0xc ] 
0x31f38 :   ldr . w   r0 ,  [ r8 ] 
0x31f3c :   vst1 .32  { d16 ,  d17 },  [ r6 ] 
0x31f40 :   blx     0x32ffc                    ;  symbol  stub  for :  strlen 
0x31f44 :   mov     r4 ,  r0 
0x31f46 :   movw    r0 ,  # 0x16aa 
0x31f4a :   movt    r0 ,  # 0x0 
0x31f4e :   add     r0 ,  pc 
0x31f50 :   ldr     r5 ,  [ r0 ] 
0x31f52 :   mov     r0 ,  r5 
0x31f54 :   blx     0x32ffc                    ;  symbol  stub  for :  strlen 
0x31f58 :   add     r1 ,  sp ,  # 0x10 
0x31f5a :   stm . w   sp ,  { r0 ,  r6 } 
0x31f5e :   movs    r0 ,  # 0x1 
0x31f60 :   str     r1 ,  [ sp ,  # 0x8 ] 
0x31f62 :   movs    r1 ,  # 0x0 
0x31f64 :   movs    r2 ,  # 0x1 
0x31f66 :   mov     r3 ,  r5 
0x31f68 :   blx     0x32fd4                    ;  symbol  stub  for :  CCCryptorCreate
snippet2拆机
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 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60
Demo ` getSecret  at  main . m: 23 : 
0x4de84 :   push    { r4 ,  r5 ,  r6 ,  r7 ,  lr } 
0x4de86 :   add     r7 ,  sp ,  # 0xc 
0x4de88 :   push . w  { r8 ,  r10 ,  r11 } 
0x4de8c :   sub     sp ,  # 0x3c 
0x4de8e :   movw    r0 ,  # 0x11a6 
0x4de92 :   movs    r1 ,  # 0x0 
0x4de94 :   movt    r0 ,  # 0x0 
0x4de98 :   movs    r6 ,  # 0x64 
0x4de9a :   add     r0 ,  pc 
0x4de9c :   vmov . i32  q8 ,  # 0x0 
0x4dea0 :   ldr     r5 ,  [ r0 ] 
0x4dea2 :   ldr     r0 ,  [ r5 ] 
0x4dea4 :   str     r0 ,  [ r7 ,  # - 28 ] 
0x4dea8 :   sub . w   r0 ,  r7 ,  # 0x2c 
0x4deac :   str     r1 ,  [ r7 ,  # - 80 ] 
0x4deb0 :   str     r1 ,  [ r7 ,  # - 84 ] 
0x4deb4 :   movs    r1 ,  # 0x61 
0x4deb6 :   strb    r1 ,  [ r7 ,  # - 60 ] 
0x4deba :   movs    r1 ,  # 0x62 
0x4debc :   strb    r1 ,  [ r7 ,  # - 59 ] 
0x4dec0 :   movs    r1 ,  # 0x63 
0x4dec2 :   strb    r1 ,  [ r7 ,  # - 58 ] 
0x4dec6 :   movs    r1 ,  # 0x65 
0x4dec8 :   strb    r6 ,  [ r7 ,  # - 57 ] 
0x4decc :   strb    r1 ,  [ r7 ,  # - 56 ] 
0x4ded0 :   movs    r1 ,  # 0x66 
0x4ded2 :   strb    r1 ,  [ r7 ,  # - 55 ] 
0x4ded6 :   movs    r1 ,  # 0x67 
0x4ded8 :   strb    r1 ,  [ r7 ,  # - 54 ] 
0x4dedc :   movs    r1 ,  # 0x68 
0x4dede :   strb    r1 ,  [ r7 ,  # - 53 ] 
0x4dee2 :   movs    r1 ,  # 0x31 
0x4dee4 :   strb    r1 ,  [ r7 ,  # - 52 ] 
0x4dee8 :   movs    r1 ,  # 0x32 
0x4deea :   strb    r1 ,  [ r7 ,  # - 51 ] 
0x4deee :   movs    r1 ,  # 0x33 
0x4def0 :   strb    r1 ,  [ r7 ,  # - 50 ] 
0x4def4 :   movs    r1 ,  # 0x34 
0x4def6 :   strb    r1 ,  [ r7 ,  # - 49 ] 
0x4defa :   movs    r1 ,  # 0x35 
0x4defc :   strb    r1 ,  [ r7 ,  # - 48 ] 
0x4df00 :   movs    r1 ,  # 0x36 
0x4df02 :   strb    r1 ,  [ r7 ,  # - 47 ] 
0x4df06 :   movs    r1 ,  # 0x37 
0x4df08 :   strb    r1 ,  [ r7 ,  # - 46 ] 
0x4df0c :   movs    r1 ,  # 0x38 
0x4df0e :   vst1 .32  { d16 ,  d17 },  [ r0 ] 
0x4df12 :   strb    r1 ,  [ r7 ,  # - 45 ] 
0x4df16 :   sub     sp ,  # 0xc 
0x4df18 :   movs    r2 ,  # 0x10 
0x4df1a :   sub . w   r1 ,  r7 ,  # 0x50 
0x4df1e :   sub . w   r3 ,  r7 ,  # 0x3c 
0x4df22 :   str     r2 ,  [ sp ] 
0x4df24 :   str     r0 ,  [ sp ,  # 0x4 ] 
0x4df26 :   movs    r0 ,  # 0x1 
0x4df28 :   str     r1 ,  [ sp ,  # 0x8 ] 
0x4df2a :   movs    r1 ,  # 0x0 
0x4df2c :   movs    r2 ,  # 0x1 
0x4df2e :   blx     0x4efdc                    ;  symbol  stub  for :  CCCryptorCreate

注意CCCryptorCreate的第四个参数,对应寄存器r3,第一段代码的r3的值是从text段直接获取,因为这只是data段的相对地址,编译时就确定了。而再看第二段代码,出现了大量的strb指令,分析知这段指令是把abcdefgh12345678每个字符逐个压进执行栈的连续地址中,然后r3取相应的连续地址的首地址。也就是说kCipherKey不再直接存储在data段,而是打散到多个指令中,成为指令的一部分(指令在text段),当代码运行时,这些指令再把kCipherKey原始内容逐个压入执行栈中构成字符串,然后用栈中字符串首地址作为参数传给CCCryptorCreate,这使得每次调用时传入的字符串地址都不同。函数CCCryptorUpdate原理也是一样。函数getSecret()执行完之后,他的执行栈被清空,kCipherKeykSecret原始信息也一起从栈中清楚,这样重要信息不会常驻内存,只是用到时才进入内存,用完立即清除,这可以有效预防内存扫描器。

上面的代码仍然不够完美,首先getSecret是函数形式、而且密码通过返回值传递,容易被分析破解;其次返回的密码的buffer内存需要调用者释放,代码不够整洁,而且调用者容易忘记。

宏改造

snippet3
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 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65
#定义kCipherKey((uint8_t有[]){'A','B','C','D','E','F','G','H',“1”,“2”, “3”,“4”,“5”,“6”,“7”,“8”}) 
#定义kSecret 

kAppSecret \ 
({\ 
    为size_t outLength = 0; \ 
    char *的BUF = getSecret(outLength); \ 
    [[NSString的页头] initWithBytes:BUF \ 
                            长度:outLength \ 
                          编码:NSASCIIStringEncoding]; \ 
})#定义_CHK_CCSUCC(状态,outLength) \ 


    如果{\((状态)= kCCSuccess!)
    outLength = 0; \ 
    转到结束; \ 
} 

#定义getSecret(outLength)\ 
({\ 
    __label__结束; \ 
    char *的BUF = NULL; \ 
                                                            \ 
    CCCryptorRef cryptor = NULL; \ 
    uint8_t有四[kCCBlockSizeAES128] \ 
    memset的(IV,0,kCCBlockSizeAES128); \ 
                                                            \ 
    为size_t BUFSIZE = 0; \ 
    为size_t移动= 0; \ 
    为size_t总= 0; \ 
    为size_t inLength = sizeof运算(kSecret); \ 
                                                            \ 
    _CHK_CCSUCC(CCCryptorCreate(kCCDecrypt,\ 
                kCCAlgorithmAES128,\ 
                kCCOptionPKCS7Padding,\ 
                kCipherKey,大小(kCipherKey),\ 
                四,与cryptor ),outLength); \ 
    BUFSIZE = CCCryptorGetOutputLength(cryptor,\ 
                                       inLength,TRUE); \ 
    BUF =(char *)的了alloca(BUFSIZE); \ 
    memset的(BUF,0,BUFSIZE); \ 
                                                            \ 
    _CHK_CCSUCC(CCCryptorUpdate(cryptor,\ 
                                kSecret ,inLength,\ 
                                BUF,BUFSIZE,和移动),\ 
                                outLength); \ 
    总= +感动; \ 
                                                            \ 
    _CHK_CCSUCC(CCCryptorFinal(cryptor,\ 
                               BUF +总,\ 
                               BUFSIZE计,和移动),\ 
                               outLength); \ 
    总= +感动; \ 
                                                            \ 
    outLength =总; \ 
结束:\ 
    如果(cryptor){\ 
        CCCryptorRelease(cryptor); \ 
    } \ 
    BUF; \ 
})

INT  主要INT  ARGC  字符 *  的argv []) { 
    的NSLog @“%@ “ kAppSecret ); 
}

这段代码稍微改造了一下,加入了一些必要的检测,让调用者更加简单,宏kAppSecret将密码包装成NSString对象。更重要的是,buf的内存不再是malloc到堆上,而是alloca到栈上(或者使用C99的变长数组),确切的说是调用者的栈,调用者不再需要手动释放内存;另外,因为kAppSecret是宏,没有有明确的入口,静态分析更加困难。

这里用了宏定义的两个技巧:

  • 带返回值的宏
1 
2 
3 
4
#定义SOME_MACRO \ 
({\ 
  表达; \ 
})\

最后一个表达式的值就是宏的返回值,使用时更像函数的返回值。

  • 局部标签

局部标签用__label__定义。如果标签end没有__label__修饰,在同一个函数中多次使用kAppSecret将产生编译错误,因为宏展开后相当于定义了多个end标签,标签重复定义。

函数指针

在客户端访问网络Server的时候,Server往往要验证请求是否来自合法的客户端,而不是攻击者伪造的请求,这就需要客户端签名。例如OAuth的签名算法。如果自己定义签名算法,不希望别人知道签名的过程,就需要保护算法不被破解。例如签名算法是HMAC-SHA1(key,MD5(data))

signature1
1 
2 
3 
4 
5 
6 
7 
8
-  ( NSString * )  signatureData: ( NSString * ) data  byKey: ( NSString * ) key  { 
    unsigned  char  md [ 16 ]; 
    CC_MD5 ( data . UTF8String ,  ( CC_LONG ) data . length ,  md ); 

    char  mac [ CC_SHA1_DIGEST_LENGTH ]; 
    CCHmac ( kCCHmacAlgSHA1 ,  key . UTF8String ,  key . length ,  md ,  16 ,  mac ); 
    return  [ self  hexStringFromBytes: mac  length: CC_SHA1_DIGEST_LENGTH ]; 
}

这段代码本身没有问题,但是对系统函数的直接调用导致代码容易被静态分析,用IDA、otool等静态分析工具可以很容易的知道这个函数的workflow,签名过程被轻易破解。为了防备静态分析,可以使用函数指针间接调用函数:

签名2
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 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56
@interface  SecurityService : NSObject 

-  ( id )  initWithMD5Function: ( void * ) md5  HMACFunction: ( void * ) hmac ; 
-  ( NSString * )  signatureData: ( NSString * ) data  byKey: ( NSString * ) key ; 

@end 

@implementation  SecurityService  { 
    void *    _md5Funcation ; 
    void *    _hmacFuncation ; 
} 

-  ( id )  initWithMD5Function: ( void * ) md5  HMACFunction: ( void * ) hmac  { 
    if  ( self  =  [ super  init ])  { 
        _md5Funcation  =  ( void * )( unsigned  long )(( uint ) & _md5Funcation ^ ( uint ) md5 ); 
        _hmacFuncation  =  ( void * )( unsigned  long )(( uint ) & _hmacFuncation ^ ( uint ) hmac ); 

    } 
    return  self ; 
} 


-  ( NSString * )  signatureData: ( NSString * ) data  byKey: ( NSString * ) key  { 
    unsigned  char  md [ 16 ]; 
    void *  func  =  ( void * )( unsigned  long )(( uint ) & _md5Funcation  ^  ( uint ) _md5Funcation ); 
    (( unsigned  char  ( * )( const  void * ,  CC_LONG ,  unsigned  char * )) func )( data . UTF8String , 
                                                                    ( CC_LONG ) data . length , 
                                                                    md ); 

    char  mac [ CC_SHA1_DIGEST_LENGTH ]; 
    func  =  ( void * )( unsigned  long )(( uint ) & _hmacFuncation  ^  ( uint ) _hmacFuncation ); 
    (( void ( * )( CCHmacAlgorithm ,  const  void * ,  size_t ,  const  void * ,  size_t ,  void * )) func )( kCCHmacAlgSHA1 , 
                                                                                      key . UTF8String , 
                                                                                      key . length , 
                                                                                      md , 
                                                                                      16 , 
                                                                                      mac ); 
    return  [ self  hexStringFromBytes: mac  length: CC_SHA1_DIGEST_LENGTH ]; 
} 

-  ( NSString * )  hexStringFromBytes: ( char * ) bytes  length: ( NSUInteger ) length  { 
    NSMutableString  * hexStr = [[ NSMutableString  alloc ]  initWithCapacity: 2 * length ]; 

    for ( int  i = 0 ; i < length ; i ++ )  { 
        [ hexStr  appendFormat: @"%02x" ,  bytes [ i ] & 0xff ]; 
    } 

    return  [ NSString  stringWithString: hexStr ]; 
} 
@end 

int  main ( int  argc ,  char  *  argv [])  { 
    SecurityService *  ss  =  [[ SecurityService  alloc ]  initWithMD5Function: CC_MD5  HMACFunction: CCHmac ]; 
    NSString *  sign  =  [ ss  signatureData: @"1234"  byKey: kAppSecret ]; 
    NSLog ( @"%@" , sign ); 
}

签名类初始化的时候,保存了HASH函数的地址值,执行签名的时,通过HASH函数的地址间接调用,这样静态分析工具分析到这里的时候,只能看到调用了某个地址,而不知道调用的具体函数,隐藏了真实目的。

这里不是直接将函数地址赋值给对象属性,而是用属性的地址与函数的地址做抑或运算。这样做主要有两个原因:

  • 直接赋值可能被编译器优化,编译器自动将使用该属性的地方替换成函数本身;

  • 类实例的创建有随机性,属性的内存地址也具有随机性,用属性地址加密函数地址,这样属性值在每次运行时都不一样;

在安卓或其他平台还可以用对dlsym来获取函数地址:

伪代码
1 
2 
3 
4 
5 
6 
7
char  data []  =  { 0x32 ,  0x71 ,  0x0b ,  0x48 ,  0xe3 ,  0xbc ,  0x6a ,  0x27 ,  0x8e ,  0xca ,  0x3b ,  0x0e }; 
char  sym [ sizeof ( data ) / 2  +  1 ]  =  { 0 }; 
for  ( int  i = 0 ;  i < sizeof ( data );  i += 2 )  { 
    sym [ i / 2 ]  =  data [ i ]  ^  data [ i + 1 ]; 
} 
//符号=“CC_MD5” 
无效*  MD5  =  对dlsym 手柄 符号);

代码混淆

因为objc代码的动态性,编译器会在binary中留下类名、函数名等信息,这些信息是可以被class-dump-z等工具提取的,友好的命名让程序猿更方便,但同时也方便了破解者。对安全相关的重要模块类,可以故意混淆类名,让人不容易轻易联想到该的真实目的。比如把类名SecurityService改为FIFA。一些重要模块可以使用C/C++语言实现,编译器对C/C++并不会保留类名、方法名等信息。

使用混淆的名字对使用者很不方便,例如[[国际足联页头] initWithMD5Function:CC_MD5SecurityService国际足联

动态调试

除了静态分析,破解者还可以使用gdb的动态调试,西奥斯hook来分析代码,常用的系统加密函数、HASH函数都可能成为监控的对象,只要监控传递给他们的参数、调用栈就能轻松分析出密钥、算法等。所以使用系统的加密函数虽然节省开发时间、执行效率高,但并不是很安全,有些算法可能需要自己重写。

反调试

可以用ptrace等方法阻止gdb注入,但ptrace本身也可以被静态修改或hook。只好从多方面考虑,尽量提高安全性,比如检查binary签名是否匹配;检查手机是否越狱,越机做特殊处理等。参考http://blog.csdn.net/yiyaaixuexi/article/details/20286929

代码加密

类似UPX等加壳技术在iOS中无法使用,因为iOS堆、栈内存都没有执行权限,这也是jit技术无法在iOS中使用的原因(除非苹果自己或越狱系统)。

脚本

将算法用脚本实现,脚本被编译成bytecode后,app解释执行bytecode指令,可以有效的防止动态调试,因为hackers看到的将是一条条的指令在switch case中执行,就像把图片的像素逐个地放给别人看,当他看完全部的像素后也不一定知道整张图片是什么样子。当然用脚本方式会增大开发成本,对执行效率也有一定影响,需要开发者在安全、开发成本、性能三者之间找个平衡点。

最后

软件保护技术多种多样,比如构造花指令,甚至有硬件级的加密模块TPM(Trusted平台Module)。总之没有绝对的安全,但危险显然也只是相对的,只要提高编码意识,注意防护就可以把风险降到最低。

转自:http://tanqisen.github.io/blog/2014/06/06/how-to-prevent-app-crack/

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值