混淆內容
1、混淆思路:利用宏定義進行混淆,混淆文件需要在需要混淆的文件中引用(或全局引用)
2、混淆內容:(橘黃色)
公開變量 @property (nonatomic, strong) NSString *gameName;
私有變量 @interface XXXX () {
NSString *gameName;
}
類名 @interface ClassName ()
方法名
公開 - (void)getUserName:(NSString *)name age:(NSInteger)age;
私有 - (void)openFile:(NSString *)filePath size:(NSInteger)size {
}
文件名混淆(只混淆 .m 文件)
UserInfo.h
UserInfo.m
字典中的key值(用於過濾混淆內容)
[result objectForKey:@"status"]
result[@"status"]
以上是需要混淆的內容,接下來說注意事項
公開變量: @property (nonatomic, strong) NSString *gameName;
我們使用時會用到 self.gameName 、 _gameName 、- (void)setGameName:(NSString *)gameName.
那么混淆時一個公開變量需要對應進行三次混淆。假定將 gameName 混淆為 gjkgnshd
1、gjkgnshd
2、_gjkgnshd
3、setGjkgnshd (這里需要注意,gameName 的 set 方法為 setGameName , 變量名的首字母需大寫,之前大寫則不變)
私有變量: 一次直接混淆
類名: 一次直接混淆
方法名: 一次直接混淆 (這里需要注意 set 方法需要過濾掉,如果這里進行set方法混淆,會造成重復混淆,99.99%產生錯誤,因為在混淆公開變量的時候你已經混淆過了,但公開變量的set方法和參數有特定聯系,所以必須在混淆公開變量時進行混淆)
文件名: 批量重命名即可
字典中的key值:混淆時一定要過濾掉字典中的key值 :(舉個栗子 )
私有變量 @property (nonatomic, strong) NSString *gameName;
字典key值 dict = @{@"gameName" : @"大逃殺"}
在你混淆 gameName -> gjkgnshd 時 ,下面為混淆結果
@property (nonatomic, strong) NSString *gjkgnshd;
dict = @{@"gjkgnshd" : @"大逃殺"}
當dict為客戶端自定義的字典時不會產生影響,但當dict為服務器返回數據時,輕則取不到數據,重則崩潰。
因為:返回結果為 {@"gameName" : @"大逃殺"} 客戶端取值時為 dict[@"gjkgnshd"] key不存在而引起錯誤。
上個圖先看看效果:
類名混淆前: 類名混淆后:

代碼混淆前: 代碼混淆后:

抱怨幾句:這是我第一次做代碼混淆,網上沒有搜索到有關oc的混淆有實際意義的腳本或者軟件,所以自己利用自己膚淺的python,勉強完成了這次混淆任務。這其中的坑有很多,相信看了這篇博客后,可以讓初次嘗試混淆的同行們躲過幾個。話不多說,上代碼。
#!/usr/bin/env python #coding=utf8 import os import re import random #規避文件夾 exclude_dirs = ["JBSDKOtherFrames"] #規避文件 exclude_files = [] #屬性名 property_name_list = [] #普通屬性名 normal_pro_name_list = [] #類名 class_name_list = [] #字典key值 dict_key_list = [] #方法名提取 method_name_list = [] #隨機字符串 (此處采取隨機8位小寫字母進行混淆,每次混淆后加入到此list,方便之后混淆查重) define_rand_list = [] #工作目錄 confound_path = "/xxx/xxx/xxx/xxx" #獲取路徑下的所有需要混淆的文件 def get_all_files(): path = confound_path print "獲取文件夾內容" if not os.path.isdir(path): print "非文件夾路徑" return #過濾相關文件夾后的文件夾 confound_dirs = [] for root, dirs, list in os.walk(path): confound_dirs.append(root) #過濾相關文件后的所有文件 confound_files = [] for item_path in confound_dirs: for root, dirs, list in os.walk(item_path): for item_file in list: if (os.path.splitext(item_file)[1] == '.h' or os.path.splitext(item_file)[1] == '.m' or os.path.splitext(item_file)[1] == '.mm') and not item_file in exclude_files: file_path = root + '/' + item_file if not file_path in confound_files: if len(exclude_dirs): item_include = False for exc_item in exclude_dirs: if exc_item in file_path: item_include = True break if item_include == False: confound_files.append(file_path) return confound_files #查找混淆內容 正則匹配所需混淆內容 def find_variable_name(file_path): with open(file_path) as file: lines = file.readlines() for line_content in lines: if "@property" in line_content: #@property 變量名 find_list = re.search("^@property\s*\(.+?\)\s*\w+\s*\*?\s*(\w+?);", line_content) if not find_list == None: if not find_list.group(1) == None: # print '屬性名',find_list.group(1) if not find_list.group(1) in property_name_list: property_name_list.append(find_list.group(1)) elif '@interface' in line_content: #類名 @interface JBSDKPopOption : UIView find_list = re.search("^@interface\s+(\w+?)\s*:\s*\w+$", line_content) if not find_list == None: if not find_list.group(1) == None: # print '類名',find_list.group(1) if not find_list.group(1) in class_name_list: class_name_list.append(find_list.group(1)) else: #普通屬性 UIImageView *arrowView; find_list = re.search("^\s*(\w+?)\s*\*\s*(\w+?);$", line_content) if not find_list == None: if not find_list.group(1) == None and not find_list.group(2) == None: if not find_list.group(1) == 'return': normal_pro_name = find_list.group(2) if normal_pro_name[0] == '_': normal_pro_name = normal_pro_name.replace('_','') if not normal_pro_name in normal_pro_name_list: normal_pro_name_list.append(normal_pro_name) #查字典key值 find_list = re.search("@\"([\w\d]+?)\"", line_content) if not find_list == None: if not find_list.group(1) == None: if not find_list.group(1) in dict_key_list: dict_key_list.append(find_list.group(1)) #方法名 無參數或一個參數 - (void)JBSDKLoginCallBack; find_list = re.search("^\s*-\s*\(\w+?\)\s*(\w+)", line_content) if not find_list == None: if not find_list.group(1) == None: if not find_list.group(1) in method_name_list: method_name_list.append(find_list.group(1)) # 方法名 兩個參數 - (void)JBSDKLoginCallBack:(BOOL)loginState uid:(NSString *)uid token:(NSString *)token; find_list = re.search("^\s*-\s*\(.+?\)\s*\w+?\s*:\s*\(\w+?\)\s*\w+?\s+?(\w+?):\(.*?\)\s*\w+?\s*[;{]$", line_content) if not find_list == None: if not find_list.group(1) == None: if not find_list.group(1) in method_name_list: method_name_list.append(find_list.group(1)) #換行后的方法名 # + (void)phoneRegister:(NSString *)phoneNum # password:(NSString *)password # code:(NSString *)code find_list = re.search("^\s*(\w+?)\s*:\s*\(.+?\)\s*\w+\s*;?$",line_content) if not find_list == None: if not find_list.group(1) == None: if not find_list.group(1) in method_name_list: method_name_list.append(find_list.group(1)) #將參數寫入宏文件 def writeDefineFile(): write_file_path = 'xxx/xxx/xxx/xxx/ConfuseDefine.h' define_ok_list = []
#一下混淆過濾內容除系統外,只略微寫幾個(意思一下)。針對不同工程需自行調整 #系統方法 過濾系統方法名(需要根據自己的工程添加) ex_clude_list = ['allocWithZone','copyWithZone','dealloc','viewDidLoad','shouldAutorotate','supportedInterfaceOrientations','preferredInterfaceOrientationForPresentation', 'didReceiveMemoryWarning','prefersStatusBarHidden','viewDidAppear','textFieldShouldReturn','touchesBegan','viewWillAppear','viewWillDisappear','alertView', 'tableView','initWithStyle','reuseIdentifier','numberOfSectionsInTableView','layoutSubviews','setSelected','animated','setValue','numberOfComponentsInPickerView', 'layout','initWithFrame','init','textFieldWillEditing','webViewDidFinishLoad','image','show','webView','webViewDidStartLoad','length','charset','srcLen', 'destBytes','destLen','textViewShouldBeginEditing','option_setupPopOption','_setupParams','_tapGesturePressed','JSONObject','password','description','pickView', 'pickerView','state','array','rightView','leftViewRectForBounds','rightViewRectForBounds','textRectForBounds'] #自定義方法 (因為我的工程是SDK,所以暴露的方法不能混淆,需要過濾掉) ex_clude_list += ['xxx','xxx','xxx','xxx','xxx'] #變量 (有些自定義變量和系統變量沖突需要過濾,例如:UIButton 的 titleLabel 我們自定義時很可能會出現同名的變量,當你在設置UIButton.titleLabel時會報錯,因為titleLabel已經被混淆掉了) ex_clude_list += ['imageView','titleLabel'] #私有變量 (同上) ex_clude_list += ['font','leftView','error','scrollView','label'] #類名 (SDK對外暴露的類名不能混淆,需過濾掉。非SDK應該可以忽略掉) ex_clude_list += ['xxx','xxx','xxx','xxx','xxx'] if os.path.exists(write_file_path): os.remove(write_file_path) with open(write_file_path, 'w+') as define_file: #property 變量 for property_name in property_name_list: if not property_name in ex_clude_list and not property_name in dict_key_list and not property_name in define_ok_list: print "混淆property 變量 == ", property_name define_ok_list.append(property_name) define_ok_list.append('_' + property_name) rand_name = randString() define_content = "# ifndef " + property_name + "\n" + "# define " + property_name + " " + rand_name + "\n" + "# endif" + "\n" define_content += "# ifndef " + '_' + property_name + "\n" + "# define " + '_' + property_name + ' ' + "_" + rand_name + "\n" + "# endif" + "\n" property_name = uperFirstString(property_name) rand_name = uperFirstString(rand_name) define_ok_list.append('set' + property_name) define_content += "# ifndef " + 'set' + property_name + "\n" + "# define " + 'set' + property_name + " " + 'set' + rand_name + "\n" + "# endif" + "\n\r" define_file.write(define_content) #私有變量 for private_name in normal_pro_name_list: if not private_name in ex_clude_list and not private_name in dict_key_list and not private_name in define_ok_list: print "私有變量 == ", private_name define_ok_list.append(private_name) define_content = "# ifndef " + private_name + "\n" + "# define " + private_name + " " + randString() + "\n" + "# endif" + "\n\r" define_file.write(define_content) #類名 for class_name in class_name_list: if not class_name in ex_clude_list and not class_name in dict_key_list and not class_name in define_ok_list: print "類名 == ", class_name define_ok_list.append(class_name) rand_name = randString() define_content = "# ifndef " + class_name + "\n" + "# define " + class_name + " " + rand_name + "\n" + "# endif" + "\n\r" define_content += "# ifndef " + '_' + class_name + "\n" + "# define " + '_' + class_name + ' ' + "_" + rand_name + "\n" + "# endif" + "\n\r" define_file.write(define_content) # 方法名 for method_name in method_name_list: if not method_name in ex_clude_list and not method_name in dict_key_list and not method_name in define_ok_list: print "混淆方法 == ", method_name define_content = "# ifndef " + method_name + "\n" + "# define " + method_name + " " + randString() + "\n" + "# endif" + "\n\r" define_file.write(define_content) #隨機字符串(此處隨機8位字母,根據自身需求進行調整) def randString(): rand_list = ['a','b','c','d','e','f','g','h','i','z','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'] ran_str = ''.join(random.sample(rand_list, 8)) while ran_str in define_rand_list: ran_str = ''.join(random.sample(rand_list, 8)) define_rand_list.append(ran_str) return ran_str #首字母轉大寫 (公開變量混淆 set 方法時,變量首字母需大寫) def uperFirstString(up_string): first_zm = up_string[0] up_string = first_zm.upper() + up_string[1:] return up_string #.m文件混淆 (.m文件重命名為無意義字符,因為翻遍后可以清楚看到.m文件的名字) 注意:.h 文件無需重命名,.m 文件什么名字無所謂,只要其中引入對應的.h文件,就不會產生任何影響(只是多了一很多文件名不匹配的警告) def confoundMFile(): all_m_files = get_all_files() new_file_index = 1 old_name_list = [] new_name_list = [] file_count = 0 for m_file in all_m_files: if os.path.splitext(m_file)[1] == '.m' or os.path.splitext(m_file)[1] == '.mm': if not 'Test' in m_file: file_count += 1 old_file_name = re.search("/(\w+?\+?\w+?)\.m{1,2}$", m_file) if not old_file_name == None: old_re_name = old_file_name.group(1) +'.m' new_name = "aaaaaaaa" + str(new_file_index) +'.m' old_name_list.append(old_re_name) new_name_list.append(new_name) new_file_name = m_file.replace(old_re_name, new_name) try: os.rename(os.path.join(m_file), os.path.join(new_file_name)) except: print '重命名失敗',m_file print new_file_name print old_re_name print new_name new_file_index += 1 else: print '正則未匹配',m_file #修改配置文件 (當文件重命名后,打開工程時,.m文件會變紅,指配置文件中的文件不存在,所以需要根據重命名后的文件名對配置文件進行修改) find_file = False config_file = '' for root, dirs, files in os.walk(confound_path): for dir_item in dirs: if dir_item == 'JBSDK.xcodeproj': value_dir = os.path.join(root, dir_item) config_file = value_dir + '/project.pbxproj' find_file = True break if find_file == True: break with open(config_file) as config_content_file: config_content = config_content_file.read() for index, old_name in enumerate(old_name_list): print 'old_name',old_name print 'new_name',new_name_list[index] config_content = config_content.replace(old_name, new_name_list[index]) file_object = open(config_file, 'w') file_object.write(config_content) file_object.close() if __name__ == "__main__": file_list = get_all_files() for path in file_list: # print path find_variable_name(path) writeDefineFile() # print '屬性名' # print property_name_list # print len(property_name_list) # # print '私有屬性名' # print normal_pro_name_list # print len(normal_pro_name_list) # # print '類名' # print class_name_list # print len(class_name_list) # # print '字典key值' # print dict_key_list # print len(dict_key_list) # # print '方法名' # print method_name_list # print len(method_name_list) confoundMFile()
總結:此次混淆最大的收獲居然是 “正則表達式” ,如果你也想學正則無從下手時,你也可以試試,定會有所收獲。
