diff --git a/README.md b/README.md index 2bfba48..f0180c7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,52 @@ # restore-symbol A tool to restore symbol table for iOS app. + +Example: restore symbol for Alipay +![](https://raw.githubusercontent.com/tobefuturer/restore-symbol/master/picture/after_restore.jpeg) + + +## How to make +``` +git clone --recursive https://github.com/tobefuturer/restore-symbol.git +cd restore-symbol && make +./restore-symbol +``` + +## How to use +- 1. Scan all oc method using class-dump. +- 2. Search block symbol in IDA to get json symbol file, using script([`search_oc_block/ida_search_block.py`](https://github.com/tobefuturer/restore-symbol/blob/master/search_oc_block/ida_search_block.py)) . + +![](http://blog.imjun.net/2016/08/25/iOS%E7%AC%A6%E5%8F%B7%E8%A1%A8%E6%81%A2%E5%A4%8D-%E9%80%86%E5%90%91%E6%94%AF%E4%BB%98%E5%AE%9D/ida_result_position.png) + +![](http://blog.imjun.net/2016/08/25/iOS%E7%AC%A6%E5%8F%B7%E8%A1%A8%E6%81%A2%E5%A4%8D-%E9%80%86%E5%90%91%E6%94%AF%E4%BB%98%E5%AE%9D/ida_result_sample.jpg) + +- 3. Use restore-symbol to inject symbols into mach o file. +``` +./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json +``` + + +## Command Line Usage +``` +Usage: restore-symbol -o [-j ] + + where options are: + -o New mach-o-file path + --disable-oc-detect Disable auto detect and add oc method into symbol table, + only add symbol in json file + -j Json file containing extra symbol info, the key is "name","address" + like this: + + [ + { + "name": "main", + "address": "0xXXXXXX" + }, + { + "name": "-[XXXX XXXXX]", + "address": "0xXXXXXX" + }, + .... + ] + +``` diff --git a/makefile b/makefile index ea7bd60..25f5168 100644 --- a/makefile +++ b/makefile @@ -1,11 +1,14 @@ .PHONY:restore-symbol + +TMP_FILE := libMachObjC.a restore-symbol.dSYM/ build/ + restore-symbol: - rm restore-symbol + rm -f restore-symbol xcodebuild -project "restore-symbol.xcodeproj" -target "restore-symbol" -configuration "Release" CONFIGURATION_BUILD_DIR="$(shell pwd)" -jobs 4 build - rm -rf libMachObjC.a restore-symbol.dSYM/ + rm -rf $(TMP_FILE) clean: - rm -rf restore-symbol libMachObjC.a restore-symbol.dSYM/ + rm -rf restore-symbol $(TMP_FILE) diff --git a/picture/after_restore.jpeg b/picture/after_restore.jpeg new file mode 100644 index 0000000..77e042b Binary files /dev/null and b/picture/after_restore.jpeg differ diff --git a/restore-symbol.xcodeproj/project.pbxproj b/restore-symbol.xcodeproj/project.pbxproj index 396b975..e4d4b28 100644 --- a/restore-symbol.xcodeproj/project.pbxproj +++ b/restore-symbol.xcodeproj/project.pbxproj @@ -384,6 +384,7 @@ isa = XCBuildConfiguration; buildSettings = { GCC_PREFIX_HEADER = "$(SRCROOT)/source/restore-symbol.pch"; + OTHER_LDFLAGS = "-all_load"; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/class-dump/Source/**"; }; @@ -393,6 +394,7 @@ isa = XCBuildConfiguration; buildSettings = { GCC_PREFIX_HEADER = "$(SRCROOT)/source/restore-symbol.pch"; + OTHER_LDFLAGS = "-all_load"; PRODUCT_NAME = "$(TARGET_NAME)"; USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/class-dump/Source/**"; }; diff --git a/search_oc_block/ida_search_block.py b/search_oc_block/ida_search_block.py new file mode 100644 index 0000000..02d22d6 --- /dev/null +++ b/search_oc_block/ida_search_block.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- + +import idautils +import idc +from idaapi import PluginForm +import operator +import csv +import sys +import json + + +IS32BIT = not idaapi.get_inf_structure().is_64bit() + + + +def isInText(x): + return SegName(x) == '__text' + + +GlobalBlockAddr = LocByName("__NSConcreteGlobalBlock") + +class GlobalBlockInfo: + pass + +AllGlobalBlockMap = {} +for struct in list(DataRefsTo(GlobalBlockAddr)): + func = 0L + FUNC_OFFSET_IN_BLOCK = 12 if IS32BIT else 16 + if IS32BIT: + func = Dword(struct + FUNC_OFFSET_IN_BLOCK) + else: + func = Qword(struct + FUNC_OFFSET_IN_BLOCK) + + + info = GlobalBlockInfo() + info.func = func + info.struct = struct + if len(list(DataRefsTo(struct))) == 0: + continue + refTo = list(DataRefsTo(struct))[0] + info.superFuncName = GetFunctionName(refTo) + info.superFunc = LocByName(info.superFuncName) + + AllGlobalBlockMap[func] = info + +def funcIsGlobalBlockFunc(block_func): + return block_func in AllGlobalBlockMap + + +def isPossibleStackBlockForFunc(block_func): +# def superFuncForStackBlock(block_func): + + if not isInText(block_func): + return False + + if GetFunctionAttr(block_func,FUNCATTR_START) != (block_func & ~ 1): + return False + + #block addr cannot be called directly + if len(list(CodeRefsTo(block_func, 0))) !=0 : + # print '%x is not block because be call by %x' % (block_func ,list(CodeRefsTo(block_func, 0))[0]) + return False + + # ref to block should be in text section + refsTo = list(DataRefsTo(block_func)) + for addr in refsTo: + if not isInText(addr): + # print '%x is not block because be ref from %x' % (block_func, addr) + return False + + # block func should be ref in only 1 function + superFuncs = [GetFunctionAttr(x,FUNCATTR_START) for x in refsTo] + superFuncs = list (set (superFuncs)) + if len(superFuncs) != 1: + # print '%x is not block because be not ref from 1 function' % block_func + return False + + return True + +def superFuncForStackBlock(block_func): + refsTo = list(DataRefsTo(block_func)) + superFuncs = [GetFunctionAttr(x,FUNCATTR_START) for x in refsTo] + superFuncs = list (set (superFuncs)) + if len(superFuncs) != 1: + return None + super_func_addr = superFuncs[0] + return super_func_addr | GetReg(super_func_addr, "T") # thumb + + +def superFuncForBlockFunc(block_func): + if funcIsGlobalBlockFunc(block_func): + return AllGlobalBlockMap[block_func].superFunc + + superStackFunc = superFuncForStackBlock(block_func) + return superStackFunc # maybe None + + + +resultDict = {} + + +def findBlockName(block_func): + # print "find block name %X" % block_func + funcName = GetFunctionName(block_func) + + if len(funcName) != 0 and funcName[0] in ('-', '+'): + return funcName + + # maybe nested block + superBlockFuncAddr = superFuncForBlockFunc(block_func) + if superBlockFuncAddr == None: + return ""; + + superBlockFuncAddr = superBlockFuncAddr | GetReg(superBlockFuncAddr, "T") # thumb + superBlockName = findBlockName(superBlockFuncAddr) + + if len(superBlockName) == 0: + return "" + else: + return superBlockName + "_block" + + + +#find all possible Stack Block +allPossibleStackBlockFunc = [] +allRefToBlock=[] +if IS32BIT: + allRefToBlock = list(DataRefsTo(LocByName("__NSConcreteStackBlock"))) +else: + allRefToBlock = list(DataRefsTo(LocByName("__NSConcreteStackBlock_ptr"))) + allRefToBlock.sort() + + ''' + 2 ref (@PAGE , @PAGEOFF) to __NSConcreteStackBlock_ptr , + but once actual + filter the list + __text:0000000102D9979C ADRP X8, #__NSConcreteStackBlock_ptr@PAGE + __text:0000000102D997A0 LDR X8, [X8,#__NSConcreteStackBlock_ptr@PAGEOFF] + ''' + tmp_array = allRefToBlock[:1] + for i in range(1, len(allRefToBlock)): + if allRefToBlock[i] - allRefToBlock[i - 1] <= 8: + pass + else: + tmp_array.append(allRefToBlock[i]) + allRefToBlock = tmp_array + +allRefToBlock = filter(lambda x:isInText(x), allRefToBlock) + +for addr in allRefToBlock: + LineNumAround = 30 #Around 30 arm instruction + scan_addr_min= max (addr - LineNumAround * 4, GetFunctionAttr(addr,FUNCATTR_START)) + scan_addr_max= min (addr + LineNumAround * 4, GetFunctionAttr(addr,FUNCATTR_END)) + for scan_addr in range(scan_addr_min, scan_addr_max): + allPossibleStackBlockFunc += list(DataRefsFrom(scan_addr)) # all function pointer used around __NSConcreteStackBlock + +allPossibleStackBlockFunc = list (set (allPossibleStackBlockFunc)) + +allPossibleStackBlockFunc = filter(lambda x:isPossibleStackBlockForFunc(x) , allPossibleStackBlockFunc ) + + + + +#process all Global Block +for block_func in AllGlobalBlockMap: + block_name = findBlockName(block_func) + resultDict[block_func] = block_name + +for block_func in allPossibleStackBlockFunc: + block_name = findBlockName(block_func) + resultDict[block_func] = block_name + + +output_file = './block_symbol.json' +list_output = [] +error_num = 0 +for addr in resultDict: + name = resultDict[addr] + if len(name) == 0 or name[0] not in ('-', '+'): + error_num += 1 + continue + + list_output += [{"address":("0x%X" % addr), "name":name}] + + +encodeJson = json.dumps(list_output, indent=1) +f = open(output_file, "w") +f.write(encodeJson) +f.close() + +print 'restore block num %d ' % len(list_output) +print 'origin block num: %d(GlobalBlock: %d, StackBlock: %d)' % (len(allRefToBlock) + len(AllGlobalBlockMap), len(AllGlobalBlockMap), len(allRefToBlock)) diff --git a/source/RSSymbol.m b/source/RSSymbol.m index dfa02e7..4543895 100644 --- a/source/RSSymbol.m +++ b/source/RSSymbol.m @@ -17,6 +17,7 @@ @implementation RSSymbol NSArray *symbols = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingMutableContainers error:&e]; if (!symbols) { + fprintf(stderr,"Parse json error!\n"); fprintf(stderr,"%s\n", e.description.UTF8String); return nil; } diff --git a/source/main.m b/source/main.m index 0cfbf0b..6d96d4c 100644 --- a/source/main.m +++ b/source/main.m @@ -39,7 +39,7 @@ void print_usage(void) " where options are:\n" " -o New mach-o-file path\n" " --disable-oc-detect Disable auto detect and add oc method into symbol table,\n" - " just add symbol in json file\n" + " only add symbol in json file\n" " -j Json file containing extra symbol info, the key is \"name\",\"address\"\n like this:\n \n" " [\n {\n \"name\": \"main\", \n \"address\": \"0xXXXXXX\"\n }, \n {\n \"name\": \"-[XXXX XXXXX]\", \n \"address\": \"0xXXXXXX\"\n },\n .... \n ]\n" diff --git a/source/restore-symbol.m b/source/restore-symbol.m index 63a5001..4b955bb 100644 --- a/source/restore-symbol.m +++ b/source/restore-symbol.m @@ -43,6 +43,18 @@ void restore_symbol(NSString * inpath, NSString *outpath, NSString *jsonPath, bo exit(1); } + if (jsonPath.length != 0 && ![[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) { + fprintf(stderr, "Error: Json file doesn't exist!\n"); + exit(1); + } + + + + if ([outpath length] == 0) { + fprintf(stderr, "Error: No output file path!\n"); + exit(1); + } + fprintf(stderr, "=========== Start =============\n"); @@ -69,7 +81,7 @@ void restore_symbol(NSString * inpath, NSString *outpath, NSString *jsonPath, bo collector.machOFile = machOFile; if (oc_detect_enable) { - fprintf(stderr, "Scan OC methoc in mach-o-file.\n"); + fprintf(stderr, "Scan OC method in mach-o-file.\n"); CDClassDump *classDump = [[CDClassDump alloc] init]; CDArch targetArch; @@ -95,13 +107,18 @@ void restore_symbol(NSString * inpath, NSString *outpath, NSString *jsonPath, bo } - fprintf(stderr, "Scan OC methoc finish.\n"); + fprintf(stderr, "Scan OC method finish.\n"); } if (jsonPath != nil && jsonPath.length != 0) { fprintf(stderr, "Parse symbols in json file.\n"); - NSArray *jsonSymbols = [RSSymbol symbolsWithJson:[NSData dataWithContentsOfFile:jsonPath]]; + NSData * jsonData = [NSData dataWithContentsOfFile:jsonPath]; + if (jsonData == nil) { + fprintf(stderr, "Can't load json data.\n"); + exit(1); + } + NSArray *jsonSymbols = [RSSymbol symbolsWithJson:jsonData]; if (jsonSymbols == nil) { fprintf(stderr,"Error: Json file cann't parse!"); exit(1);