def process_invoke_static_statement(line): """处理invoke static语句,获取解密函数相关信息 Args: line (str): Description of parameter `line`. Returns: tupple: None 表示跳过,非None(类名、方法名、参数类型、返回值类型、参数寄存器) 表示不跳过。 """ # 获取函数相关信息 cname, mname, ptypes, rtype, rnames = SmaliLine.parse(line) # 调用类 if cname in {'Ljava/lang/reflect/Method;', 'Ljava/lang/String;'}: return # 返回值如果为对象,那么不能为非字符串类型 if rtype[0] == 'L' and rtype != 'Ljava/lang/String;': return if rtype in ['V', 'Z']: return # 解密参数 # TODO 也许可以指定任意参数,目前暂时不支持。 itypes = set(ptypes) & { 'Landroid/content/Context;', 'Landroid/app/Activity;', 'Ljava/lang/Throwable;' } if itypes: return return cname, mname, ptypes, rtype, rnames
def process_xget_statement(self, line): """处理opcode形如*get相关的语句,如sget、iget等。 这类语句通常都是从类的字段中获取值。 iget-object v3, p0, La/b/c/d;->q:Ljava/lang/String; Args: line (str): smali语句 Returns: None: """ desc = line.split()[-1] # La/b/c/d;->q:Ljava/lang/String; if desc in self.fields: return cname, fname, rtype, rname = SmaliLine.parse(line) types = { 'B', 'S', 'C', 'I', 'J', 'F', 'D', 'Ljava/lang/String;', '[B', '[S', '[C', '[I', '[J', '[F', '[D', '[Ljava/lang/String;' } if rtype not in types: return self.json_list = {'type': 'field', 'data': []} json_item = {'className': cname, 'fieldName': [fname]} self.json_list['data'].append(json_item) result = self.get_field_value() # if DEBUG_MODE: # print('FeildValue:', result) if not result: return value = result[fname] if rtype in {'B', 'S', 'C', 'I', 'J', 'F', 'D'}: value = int(value) elif rtype.startswith('['): value = ast.literal_eval(value) self.fields[desc] = value
def _process_mtd(self, mtd): if DEBUG: from colorclass.color import Color print('\n', '+' * 100) print('Starting to decode ...') print(Color.green(mtd)) # 如果存在数组 array_data_content = [] arr_res = self.arr_data_ptn.search(mtd.get_body()) if arr_res: array_data_content = re.split(r'\n\s', arr_res.group()) lines = re.split(r'\n\s*', mtd.get_body()) old_body = lines.copy() # 存放原始方法体 new_body = [] # 存放解密后的方法体 snippet = [] # 存放smali代码,用于模拟执行 args = {} # 存放方法参数,用于smaliemu执行 index = -1 # 用于计数 for line in lines: snippet.append(line) new_body.append(line) # 解密结果,直接放后面即可 index += 1 if 'invoke-' not in line and 'iget-' not in line: continue from smafile import SmaliLine # 函数有两种类型: # 1. 字符串内置类型 - smaliemu能直接执行 # 2. 其他类型 - 需要发射调用 if DEBUG: print('LINE:', Color.red(line)) if 'Ljava/lang/String;->' in line: if '<init>' not in line: continue if DEBUG: print(Color('{autoyellow}process new string...{/yellow} ')) # 如果是字符串函数,参数为[B/[C/[I,则考虑 rtn_name, rnames = SmaliLine.parse_string(line) if not rnames: continue # 直接执行emu解密 try: ll = args[rnames[1]] # print(ll) except KeyError: continue if not isinstance(ll, list): continue no_str = False for i in ll: if i < 0: no_str = True break if no_str: continue result = ''.join(chr(x) for x in ll) if DEBUG: print(result) # 更新寄存器 args[rtn_name] = result # 更新代码 new_line = 'const-string {}, "{}"'.format(rtn_name, result) new_body = new_body[:-1] new_body.append(new_line) self.make_changes = True mtd.set_modified(True) continue elif 'invoke-static' in line: # 获取函数相关信息 cname, mname, ptypes, rtype, rnames = SmaliLine.parse_invoke_static( line) if cname in ['Ljava/lang/reflect/Method;']: print(cname, 'will skip') continue # 返回值不能其他引用类型 if rtype[0] == 'L' and rtype != 'Ljava/lang/String;': continue if rtype in ['V', 'Z']: continue flagx = False for i in [ 'Landroid/content/Context;', 'Landroid/app/Activity;' ]: if i in ptypes: flagx = True break if flagx: continue elif 'iget-object' in line: # iget-object v3, p0, Lcom/fmsd/a/d;->q:Ljava/lang/String; # 这种情况,需要直接通过反射获取 # clz_name, field_name, rname = cname, fname, rtype, rname = SmaliLine.parse_iget_object(line) if rtype != 'Ljava/lang/String;': continue self.json_list = {'type': 'field', 'data': []} json_item = {'className': cname, 'fieldName': [fname]} # print(json_item) self.json_list['data'].append(json_item) result = self.get_field_value() if not result: continue value = result[fname] args[rname] = value continue else: continue # print('>', cname) # print('>', mname) # print('>', ptypes) # print('>', rnames) # print(">", 'return Type:', rtype) # 参数名(寄存器的名),类名,方法名,proto(简称) # register_name, class_name, mtd_name, ptypescc # ('v1, v2, v3', 'Lcom/game/pay/sdk/y', 'a', 'ISB') # 解密参数的寄存器名 # 初始化所有寄存器 del snippet[-1] snippet.extend(array_data_content) try: args.update(self.pre_process(snippet)) except TIMEOUT_EXCEPTION: pass try: # for lx in snippet: # print(lx) self.emu.call(snippet, args=args, cv=True, thrown=False) registers = self.emu.vm.variables # registers = self.get_vm_variables(snippet, args, rnames) # print('smali执行后,寄存器内容', registers) # args = registers if registers else args if registers: for k, v in registers.items(): if v is None: continue args[k] = v registers = args except TIMEOUT_EXCEPTION: snippet.clear() continue # print('更新寄存器内容', args) # if not registers: # registers = args obj_flag = False if len(ptypes) == 1 and ptypes[0][0] == 'L' and ptypes != [ 'Ljava/lang/String;' ]: # 单独处理参数为对象的情况 obj_flag = True # 已经执行过的代码,不再执行 snippet.clear() if not registers and not obj_flag: continue # print('----->>>>>>>>>>') # 从寄存器中获取对应的参数 # 参数获取 "arguments": ["I:198", "I:115", "I:26"]} arguments = [] # args = {} # the parameter of smali method if not obj_flag: ridx = -1 for item in ptypes: ridx += 1 rname = rnames[ridx] if rname not in registers: break value = registers[rnames[ridx]] argument = self.convert_args(item, value) if argument is None: break arguments.append(argument) else: arguments.append('Object:' + self.smali2java(ptypes[0])) # print('参数类型', ptypes) # print('参数值', arguments) if len(arguments) != len(ptypes): continue json_item = self.get_json_item(cname, mname, arguments) # print('生成json item') # {id}_{rtn_name} 让这个唯一化,便于替换 old_content = '# %s' % json_item['id'] # 如果 move_result_obj 操作存在的话,解密后一起替换 find = self.move_result_obj_ptn.search(lines[index + 1]) # 必须要找到返回值操作,用于更新寄存器 if not find: # print('找不到返回寄存器') continue rtn_name = find.groups()[0] # 为了避免 '# abc_v10' 替换成 '# abc_v1' old_content = old_content + '_' + rtn_name + 'X' self.append_json_item(json_item, mtd, old_content, rtn_name) # print(json_item) result = self.get_result(rtype) # print("解密结果", result) self.json_list.clear() if result: new_body = new_body[:-1] else: continue if not args: args = {} if rtype == 'Ljava/lang/String;': result = list(result.values())[0][0] # 更新寄存器 args[rtn_name] = result # 更新代码 new_line = 'const-string {}, "{}"'.format(rtn_name, result) new_body.append(new_line) self.make_changes = True # print(args) mtd.set_modified(True) elif rtype.startswith('['): # print("返回值为数组,更新寄存器内容") # print(result) args[rtn_name] = result # print(args) else: print("返回值并非字符串,也不是B/C数组") # print('-' * 100) mtd.set_body('\n'.join(new_body))
def process_body(self, body): ''' 返回(结果、新的方法体) ''' array_snippet = self.get_array_snippet(body) lines = re.split(r'\n', body) flag = False new_body = [] snippet = [] args = {} for line in lines: if not line: continue if 'move-result-object' in line and 'const-string' in new_body[-1]: # 这种情况会导致反编译工具反编译失败 # const-string v9, "bytes=" # move-result-object v9 v0 = SmaliLine.parse(line) vx, string_id = SmaliLine.parse(new_body[-1]) if v0 != vx: new_line = 'const-string {}, "{}"'.format(v0, string_id) new_body[-1] = new_line continue snippet.append(line) for _, prog in self.progs.items(): result = prog.search(line) if result: break else: # 如果smali代码存在非字符串调用,则清理所有代码 if 'invoke-' in line and ', Ljava/lang/String' not in line: snippet.clear() new_body.append(line) continue rtname = result.groups()[0] snippet.append('return-object {}'.format(rtname)) snippet.extend(array_snippet) args.update(self.pre_process(snippet)) self.emu.call(snippet, args=args, thrown=False) args = self.emu.vm.variables result = self.emu.vm.result if result: flag = True if not isinstance(result, str): result = str(result) new_line = 'const-string {}, "{}"'.format(rtname, result) if 'array' in new_body[-2]: del new_body[-1] del new_body[-1] new_body.append(new_line) else: new_body.append(line) snippet.clear() return (flag, new_body)
def _process_mtd(self, mtd): # if DEBUG_MODE: # print('\n', '+' * 100) # print('Starting to decode ...') # print(Color.green(mtd)) # 如果存在数组 array_data_content = [] arr_res = self.arr_data_ptn.search(mtd.get_body()) if arr_res: array_data_content = re.split(r'\n\s', arr_res.group()) lines = re.split(r'\n\s*', mtd.get_body()) old_body = lines.copy() # 存放原始方法体 new_body = [] # 存放解密后的方法体 snippet = [] # 存放smali代码,用于模拟执行 args = {} # 存放方法参数,用于smaliemu执行 index = -1 # 用于计数 xget_opcodes = {'iget', 'iget-object', 'sget', 'sget-object'} block_args = {'first': {}} # 保存所有分支的执行结果 last_block_key = 'first' # 上一个分支-关键字 this_block_key = 'first' # 当前分支,默认第一分支 keys = ['first'] # 默认执行第一分支 for line in lines: index += 1 if not line: continue new_body.append(line) # 解密结果,直接放后面即可 # if DEBUG_MODE: # print(Color.blue(line)) parts = line.split() opcode = parts[0] # smali 代码分块执行 # 命中下述关键字,则表示当前分支结束 # 并根据上一个分支的情况,判断之前的分支是否可用 if 'if-' in opcode: # if DEBUG_MODE: # print('>' * 10, opcode) # print('this_block_key', this_block_key) # print('last_block_key', last_block_key) # print('block_args', block_args) # 存在两种情况 # 1. 当前代码片段(if语句之前的代码),还没执行;全部执行一次 # 2. 当前代码片段,已经执行了一部分,因为解密;从执行后的地方开始执行 pre_args = {} if this_block_key in last_block_key: for key in reversed(keys): if this_block_key not in key: pre_args = block_args[key].copy() break else: pre_args = block_args[last_block_key].copy() if this_block_key in block_args: pre_args.update(block_args[this_block_key]) snippet.extend(array_data_content) self.emu.call(snippet, args=pre_args, cv=True, thrown=False) block_args[this_block_key] = self.emu.vm.variables snippet.clear() last_block_key = this_block_key this_block_key = 'if' + parts[-1] # 表示接下来跑的代码块是这个语句的 keys.append(this_block_key) # if DEBUG_MODE: # print('block_args - 运行后', block_args) continue elif 'goto' in opcode: # 跳转语句,直接跳过 continue elif opcode.startswith(':cond_')\ or opcode.startswith(':try_start')\ or opcode.startswith('.catch_'): # if DEBUG_MODE: # print('>' * 10, opcode) # print('this_block_key', this_block_key) # print('last_block_key', last_block_key) # print('block_args', block_args) # 存在两种情况 # 1. 当前代码片段,还没执行;全部执行一次 # 2. 当前代码片段,已经执行了一部分,因为解密;从执行后的地方开始执行 pre_args = block_args[last_block_key].copy() if this_block_key in block_args: pre_args.update(block_args[this_block_key]) snippet.extend(array_data_content) self.emu.call(snippet, args=pre_args, cv=True, thrown=False) block_args[this_block_key] = self.emu.vm.variables snippet.clear() last_block_key = this_block_key this_block_key = opcode # 表示接下来跑的代码块是这个语句的 keys.append(this_block_key) # if DEBUG_MODE: # print('block_args - 运行后', block_args) continue elif opcode.startswith(':try_start'): pass elif '.catch_' in opcode: # 前面代码当成一块处理 continue snippet.append(line) is_static = True if opcode == 'invoke-static': result = self.process_invoke_static_statement(line) if result: cname, mname, ptypes, rtype, rnames = result else: continue # elif opcode == 'invoke-virtual': # TODO 实例方法,目前只考虑无参实例化。 # result = self.process_invoke_static_statement(line) # if result: # cname, mname, ptypes, rtype, rnames = result # print(result) # # 判断类的构造函数是否为<init>()V # clz = self.smalidir.get_method( # java2smali(cname), '<init>()V') # if not clz: # continue # is_static = False # else: # continue elif opcode in xget_opcodes: self.process_xget_statement(line) continue elif 'Ljava/lang/String;-><init>([B)V' in line: if 'move-result-object' in snippet[0]: snippet = snippet[1:] self.emu.call(snippet, args=args, cv=True, thrown=False) if not self.emu.vm.result: continue # 如果有结果,则替换 vx, _ = SmaliLine.parse(line) new_line = 'const-string {}, "{}"'.format( vx, self.emu.vm.result) del new_body[-1] new_body.append(new_line) self.make_changes = True mtd.set_modified(True) snippet.clear() continue else: continue # 模拟执行,获取解密参数 del snippet[-1] snippet.extend(array_data_content) try: snippet = self.process_if_statement(snippet) # if DEBUG_MODE: # print(Color.red('开始处理解密参数 {}'.format(line))) # for l in snippet: # print(Color.red(l)) # print('args', args) # print(block_args) # print(keys) # print(this_block_key) # print('-' * 80) pre_args = block_args[last_block_key].copy() args.update(pre_args) if this_block_key in block_args: args.update(block_args[this_block_key]) args.update(self.fields) self.emu.call(snippet, args=args, cv=True, thrown=False) registers = self.emu.vm.variables block_args[this_block_key] = registers # if DEBUG_MODE: # print(snippet) # print('args:', args) # print('smali执行后,寄存器内容', registers) if registers: for k, v in registers.items(): if v is None: continue args[k] = v registers = args except TIMEOUT_EXCEPTION: snippet.clear() continue print(Color.red('->')) obj_flag = False if len(ptypes) == 1 and ptypes[0][0] == 'L' and ptypes != [ 'Ljava/lang/String;' ]: # 单独处理参数为对象的情况 obj_flag = True snippet.clear() # 已经执行过的代码,不再执行 if not registers and not obj_flag: continue print(Color.red('->>')) # 从寄存器中获取对应的参数 # 参数获取 "arguments": ["I:198", "I:115", "I:26"]} arguments = [] # args = {} # the parameter of smali method if not obj_flag: ridx = -1 for item in ptypes: ridx += 1 rname = rnames[ridx] if rname not in registers: break value = registers[rnames[ridx]] argument = self.convert_args(item, value) if argument is None: break arguments.append(argument) else: arguments.append('Object:' + smali2java(ptypes[0])) # if DEBUG_MODE: # print(Color.red('->>')) # print('参数类型', ptypes) # print('参数值', arguments) if len(arguments) != len(ptypes): print(Color.red('->> 参数对不上')) continue json_item = self.get_json_item(cname, mname, arguments) print(json_item) # print('生成json item') # {id}_{rtn_name} 让这个唯一化,便于替换 old_content = '# %s' % json_item['id'] # 如果 move_result_obj 操作存在的话,解密后一起替换 find = self.move_result_obj_ptn.search(lines[index + 1]) print(Color.red('->> not fount')) # 必须要找到返回值操作,用于更新寄存器 if not find: print('找不到返回寄存器') continue print(Color.red('->>>')) rtn_name = find.groups()[0] # 为了避免 '# abc_v10' 替换成 '# abc_v1' old_content = old_content + '_' + rtn_name + 'X' self.append_json_item(json_item, mtd, old_content, rtn_name) result = self.get_result(rtype) # if DEBUG_MODE: # print("解密结果", result) self.json_list.clear() if result: new_body = new_body[:-1] else: continue if not args: args = {} if rtype == 'Ljava/lang/String;': result = list(result.values())[0][0] # 更新寄存器 args[rtn_name] = result # 更新代码 new_line = 'const-string {}, "{}"'.format(rtn_name, result) new_body.append(new_line) self.make_changes = True mtd.set_modified(True) elif rtype.startswith('['): args[rtn_name] = result # 把结果保存到当前分支 else: print("返回值并非字符串,也不是B/C数组") if args: block_args[this_block_key].update(args) # 把结果保存到当前分支 # if DEBUG_MODE: # print(block_args) # print('*' * 100) # print('last_block_key', last_block_key) # print('this_block_key', this_block_key) # # pre_args = block_args[last_block_key].copy() # if this_block_key in block_args: # pre_args.update(block_args[this_block_key]) # block_args[this_block_key] = mtd.set_body('\n'.join(new_body))
def _process_mtd(self, mtd): # 如果存在数组 array_data_content = [] arr_res = self.arr_data_ptn.search(mtd.get_body()) if arr_res: array_data_content = re.split(r'\n\s', arr_res.group()) lines = re.split(r'\n\s*', mtd.get_body()) old_body = lines.copy() # 存放原始方法体 new_body = [] # 存放解密后的方法体 snippet = [] # 存放smali代码,用于模拟执行 args = self.fields # 存放方法参数,用于smaliemu执行 index = -1 # 用于计数 for line in lines: snippet.append(line) index += 1 if 'sget' in line: print('->', line) from smafile import SmaliLine result = SmaliLine.parse(line) if not result: continue cname, fname, rtype, rname = result if rtype not in {'I', 'S', 'C', 'F', 'Ljava/lang/String;', '[B', '[C', '[I', '[Ljava/lang/String;'}: continue abs_fname = self.java2smali(cname) + '->' + fname + ':' + rtype if abs_fname in self.fields.keys(): continue if abs_fname in args.keys(): print(args[abs_fname]) continue print(cname, fname, rtype, rname) self.feild_datas = { 'type': 'field', 'data': [] } json_item = { 'className': cname, 'fieldName': [fname], 'fieldType': rtype } print(json_item) self.feild_datas['data'].append(json_item) value = self.get_field_value(json_item) if not value: continue self.fields[abs_fname] = value continue if 'invoke-static' not in line or not line.endswith(')Ljava/lang/String;'): new_body.append(line) continue # 排除Android自身的类 flag = False for clz in android_strs: if clz in line: flag = True break if flag: new_body.append(line) continue # result = self.invoke_static_ptn.match(line) # if not result: # new_body.append(line) # continue # print(result) from smafile import SmaliLine cname, mname, protos, rtype, rnames = SmaliLine.parse_invoke_static(line) # 参数名(寄存器的名),类名,方法名,proto(简称) # register_name, class_name, mtd_name, protos # ('v1, v2, v3', 'Lcom/game/pay/sdk/y', 'a', 'ISB') # 解密参数的寄存器名 # rnames = result.groups()[0].split(', ') # cname = result.groups()[1][1:].replace('/', '.') # mname = result.groups()[2] # protos = self.proto_ptn.findall(result.groups()[3]) print(rnames, cname, mname, protos) # 初始化所有寄存器 del snippet[-1] # snippet.extend(array_data_content) # try: # args.update(self.pre_process(snippet)) # except TIMEOUT_EXCEPTION: # pass try: # registers = self.get_vm_variables( # snippet, args, rnames) # args = registers if registers else args if DEBUG: print('smali代码:') print(snippet) self.emu.call(snippet, args=args, cv=True, thrown=False) registers = self.emu.vm.variables if registers: for k, v in registers.items(): if v is None: continue args[k] = v registers = args except TIMEOUT_EXCEPTION: snippet.clear() # new_body.append(line) continue snippet.clear() if not registers: continue # snippet.clear() # if not registers: # new_body.append(line) # continue # 从寄存器中获取对应的参数 # 参数获取 "arguments": ["I:198", "I:115", "I:26"]} arguments = [] # args = {} # the parameter of smali method ridx = -1 for item in protos: ridx += 1 rname = rnames[ridx] if rname not in registers: break value = registers[rnames[ridx]] argument = self.convert_args(item, value) if argument is None: break arguments.append(argument) if DEBUG: print('解密参数:') print(arguments) if len(arguments) != len(protos): new_body.append(line) continue json_item = self.get_json_item(cname, mname, arguments) # {id}_{rtn_name} 让这个唯一化,便于替换 old_content = '# %s' % json_item['id'] # 如果 move_result_obj 操作存在的话,解密后一起替换 find = self.move_result_obj_ptn.search(lines[index + 1]) if find: rtn_name = find.groups()[0] # 为了避免 '# abc_v10' 替换成 '# abc_v1' old_content = old_content + '_' + rtn_name + 'X' self.append_json_item(json_item, mtd, old_content, rtn_name) else: old_content = old_content + '_X' self.append_json_item( json_item, mtd, old_content, None) old_body[index] = old_content mtd.set_body('\n'.join(old_body)) if DEBUG: print("解密方法内容:") print(self.json_list)