前提
眾所周知iOS應(yīng)用在越獄設(shè)備上比較容易被逆向分析泥从,而靜態(tài)字符串的硬編碼比較容易成為逆向者的突破口恍涂。因此有必要做一些字符串硬編碼的混淆右蕊,如加密的對(duì)稱加密key砰奕,md5的key,域名燎含,接口名等宾濒。
混淆前
混淆前代碼
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *string = @"hello world";
NSLog(@"%@",string);
}
@end
直接在源代碼里面編寫字符串,使用hopper可直接找到該串
混淆方案
這里采用的是直接修改源代碼的方式做混淆屏箍。首先對(duì)需要混淆的字符串用宏打上標(biāo)記绘梦,然后使用腳本過濾所有源代碼,生成混淆過的代碼铣除。因?yàn)榛煜^的代碼可讀性極差,不利于維護(hù)鹦付,所以就需要解混淆代碼尚粘,用于把混淆過的代碼做還原。
混淆腳本
#!/usr/bin/env python
# encoding=utf8
# -*- coding: utf-8 -*-
# 本腳本用于對(duì)源代碼中的字符串進(jìn)行加密
# 替換所有字符串常量為加密的char數(shù)組敲长,形式((char[]){1, 2, 3, 0})
import importlib
import os
import re
import sys
# 替換字符串為((char[]){1, 2, 3, 0})的形式郎嫁,同時(shí)讓每個(gè)字節(jié)與0xAA異或進(jìn)行加密
def replace(match):
string = match.group(2) + '\x00'
replaced_string = '((char []) {' + ', '.join(["%i" % ((ord(c) ^ 0xAA) if c != '\0' else 0) for c in list(string)]) + '})'
return match.group(1) + replaced_string + match.group(3)
# 修改源代碼秉继,加入字符串加密的函數(shù)
def obfuscate(file):
with open(file, 'r') as f:
code = f.read()
f.close()
code = re.sub(r'(confusion_NSSTRING\(|confusion_CSTRING\()"(.*?)"(\))', replace, code)
code = re.sub(r'//#define ggh_confusion', '#define ggh_confusion', code)
with open(file, 'w') as f:
f.write(code)
f.close()
#讀取源碼路徑下的所有.h和.m 文件
def openSrcFile(path):
print("開始處理路徑: "+ path +" 下的所有.h和.m文件")
# this folder is custom
for parent,dirnames,filenames in os.walk(path):
#case 1:
# for dirname in dirnames:
# print((" parent folder is:" + parent).encode('utf-8'))
# print((" dirname is:" + dirname).encode('utf-8'))
#case 2
for filename in filenames:
extendedName = os.path.splitext(os.path.join(parent,filename))
if (extendedName[1] == '.h' or extendedName[1] == '.m'):
print("處理源代碼文件: "+ os.path.join(parent,filename))
obfuscate(os.path.join(parent,filename))
#源碼路徑
srcPath = '../hello String'
if __name__ == '__main__':
print("本腳本用于對(duì)源代碼中被標(biāo)記的字符串進(jìn)行加密")
if len(srcPath) > 0:
openSrcFile(srcPath)
else:
print("請(qǐng)輸入正確的源代碼路徑")
sys.exit()
運(yùn)行該腳本 $ python3 confusion.py
把標(biāo)記過的代碼混淆。
生成混淆代碼
/*
* 字符串混淆解密函數(shù)泽铛,將char[] 形式字符數(shù)組和 aa異或運(yùn)算揭秘
* 如果沒有經(jīng)過混淆尚辑,請(qǐng)關(guān)閉宏開關(guān)
*/
extern char* decryptConstString(char* string)
{
char* origin_string = string;
while(*string) {
*string ^= 0xAA;
string++;
}
return origin_string;
}
//字符串混淆加密 和 解密的宏開關(guān)
#define ggh_confusion
#ifdef ggh_confusion
#define confusion_NSSTRING(string) [NSString stringWithUTF8String:decryptConstString(string)]
#define confusion_CSTRING(string) decryptConstString(string)
#else
#define confusion_NSSTRING(string) @string
#define confusion_CSTRING(string) string
#endif
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *string = confusion_NSSTRING(((char []) {194, 207, 198, 198, 197, 138, 221, 197, 216, 198, 206, 0}));
NSLog(@"%@",string);
}
@end
混淆后的代碼已經(jīng)看不到硬編碼了。在內(nèi)聯(lián)函數(shù)中做異或運(yùn)算盔腔。
解混淆腳本
解混淆腳本用于還原代碼杠茬,增加代碼可讀性。
#!/usr/bin/env python
# encoding=utf8
# -*- coding: utf-8 -*-
# 本腳本用于對(duì)源代碼中的字符串進(jìn)行解密
# 替換所有加密的char數(shù)組為字符串常量弛随,""
import importlib
import os
import re
import sys
# 替換((char[]){1, 2, 3, 0})的形式為字符串瓢喉,同時(shí)讓每個(gè)數(shù)組值與0xAA異或進(jìn)行解密
def replace(match):
string = match.group(2)
decodeConfusion_string = ""
for numberStr in list(string.split(',')):
if int(numberStr) != 0:
decodeConfusion_string = decodeConfusion_string + "%c" % (int(numberStr) ^ 0xAA)
# replaced_string = '\"' + "".join(["%c" % ((int(c) ^ 0xAA) if int(c) != 0 else '\0') for c in string.split(',')]) + '\"'
replaced_string = '\"' + decodeConfusion_string + '\"'
print("replaced_string = " + replaced_string)
return match.group(1) + replaced_string + match.group(3)
# 修改源代碼,加入字符串加密的函數(shù)
def obfuscate(file):
with open(file, 'r') as f:
code = f.read()
f.close()
code = re.sub(r'(confusion_NSSTRING\(|confusion_CSTRING\()\(\(char \[\]\) \{(.*?)\}\)(\))', replace, code)
code = re.sub(r'[/]*#define ggh_confusion', '//#define ggh_confusion', code)
with open(file, 'w') as f:
f.write(code)
f.close()
#讀取源碼路徑下的所有.h和.m 文件
def openSrcFile(path):
print("開始處理路徑: "+ path +" 下的所有.h和.m文件")
# this folder is custom
for parent,dirnames,filenames in os.walk(path):
#case 1:
# for dirname in dirnames:
# print((" parent folder is:" + parent).encode('utf-8'))
# print((" dirname is:" + dirname).encode('utf-8'))
#case 2
for filename in filenames:
extendedName = os.path.splitext(os.path.join(parent,filename))
#讀取所有.h和.m 的源文件
if (extendedName[1] == '.h' or extendedName[1] == '.m'):
print("處理代碼文件:"+ os.path.join(parent,filename))
obfuscate(os.path.join(parent,filename))
#源碼路徑
srcPath = '../hello String'
if __name__ == '__main__':
print("字符串解混淆腳本舀透,將被標(biāo)記過的char數(shù)組轉(zhuǎn)為字符串栓票,并和0xAA異或。還原代碼")
if len(srcPath) > 0:
openSrcFile(srcPath)
else:
print("請(qǐng)輸入正確的源代碼路徑愕够!")
sys.exit()
混淆原理
因?yàn)橛簿幋a的字符串是在可執(zhí)行文件 Mach-O 全局的數(shù)據(jù)區(qū)走贪,在符號(hào)表中很容易被搜索到,而字符串?dāng)?shù)組則不會(huì)惑芭。
總結(jié)
混淆方案多種多樣坠狡,個(gè)有優(yōu)缺點(diǎn)。個(gè)人認(rèn)為最好的方式是依據(jù)Clang 抽象語(yǔ)法樹AST强衡,做全局混淆擦秽。另外,該方法還可以做方法名漩勤,類名感挥,屬性名等的混淆,只是全局混淆不能對(duì)所有名稱做混淆越败,如一些系統(tǒng)控件的代理方法等触幼。
之前代碼沒有上傳成功,各位兄弟抱歉了究飞,hello String -> confusion 目錄下為相關(guān)編碼解碼的腳本置谦。另,歡迎兄弟們大賞啊~~