Skip to content

Commit

Permalink
README
Browse files Browse the repository at this point in the history
  • Loading branch information
eugeneyang authored and eugeneyang committed Aug 27, 2016
1 parent 5c86d83 commit f49cbbf
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 7 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <output-file> [-j <json-symbol-file>] <mach-o-file>
where options are:
-o <output-file> 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-symbol-file> Json file containing extra symbol info, the key is "name","address"
like this:
[
{
"name": "main",
"address": "0xXXXXXX"
},
{
"name": "-[XXXX XXXXX]",
"address": "0xXXXXXX"
},
....
]
```
9 changes: 6 additions & 3 deletions makefile
Original file line number Diff line number Diff line change
@@ -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)

Binary file added picture/after_restore.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions restore-symbol.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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/**";
};
Expand All @@ -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/**";
};
Expand Down
192 changes: 192 additions & 0 deletions search_oc_block/ida_search_block.py
Original file line number Diff line number Diff line change
@@ -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))
1 change: 1 addition & 0 deletions source/RSSymbol.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion source/main.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void print_usage(void)
" where options are:\n"
" -o <output-file> 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-symbol-file> 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"

Expand Down
23 changes: 20 additions & 3 deletions source/restore-symbol.m
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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;
Expand All @@ -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);
Expand Down

0 comments on commit f49cbbf

Please sign in to comment.