其他
利用AndroidNativeEmu完成多层jni调用的模拟
本文为看雪论坛优秀文章
看雪论坛作者ID:飞翔的猫咪
题目出自看雪高研班2021年12月份作业第二题:已知该apk中有jni函数jnicheck,当接收参数为“XUe”时,返回true,请使用AndroidNativeEmu实现对该jni函数的完全模拟调用(不需要对crypt2函数进行逆向分析)
一
分析
这道题主要考察的是对多层jni调用的模拟,所谓多层jni调用指native java函数的实现c/c++函数中又反过来调用java函数,而这个被调用的java函数仍然是一个native函数。而对于AndroidNativeEmu和UniDbg目前(做题的时候)都不支持这种多层调用,UniDbg更新应该算是比较频繁的,有可能后边会支持。
不能直接支持,只能用取巧的方式来实现。我这里用了两个程序来实现,一个程序通过subprocess来调用另外一个程序,获取这个程序的结果进一步利用。这种实现方式是有缺陷的,如果模拟的程序函数对全局的变量产生了副作用,或者有共同的依赖,那么模拟可能会失败,在这种情况下需要做特殊的处理,单独处理同步共享的全局变量。
通过这个题目的练习可以学得AndroidNativeEmu和UniDbg不支持的地方,或者说有待改进的点。工具不支持,必要时必须自己写代码扩展功能。
二
解答
单独为crypt2函数写一个源文件,crypt2_impl.py文件内容为:
import logging
import sys
from androidemu.emulator import Emulator
from androidemu.java.helpers.native_method import native_method
from androidemu.utils import memory_helpers
@native_method
def sprintf(uc, str, format, args):
str_utf8 = memory_helpers.read_utf8(uc, str)
format_utf8 = memory_helpers.read_utf8(uc, format)
args_utf8 = memory_helpers.read_utf8(uc, args)
logger.info("str_utf8:{}, format_utf8:{}, args_utf8:{}".format(str_utf8, format_utf8, args_utf8))
result_string = format_utf8 % args_utf8
memory_helpers.write_utf8(uc, str, result_string)
return len(result_string)
@native_method
def malloc(uc, size):
return emulator.memory_manager.allocate(size)
@native_method
def strcmp(uc, s1, s2):
s1_utf8 = memory_helpers.read_utf8(uc, s1)
s2_utf8 = memory_helpers.read_utf8(uc, s2)
if s1_utf8 == s2_utf8:
ret = 0
elif s1_utf8 < s2_utf8:
ret = -1
else:
ret = 1
logger.info("s1:{},s2:{},ret:{}".format(s1_utf8, s2_utf8, ret))
return ret
# Configure logging
logging.basicConfig(
stream=sys.stderr,
level=logging.DEBUG,
format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)
logger = logging.getLogger(__name__)
so_file = "my_binaries/libnative-lib.so"
libc_file = "example_binaries/32/libc.so"
# Initialize emulator
emulator = Emulator(vfp_inst_set=True)
emulator.modules.add_symbol_hook('sprintf', emulator.hooker.write_function(sprintf) + 1)
emulator.modules.add_symbol_hook('malloc', emulator.hooker.write_function(malloc) + 1)
emulator.modules.add_symbol_hook('strcmp', emulator.hooker.write_function(strcmp) + 1)
libc_module = emulator.load_library(libc_file, do_init=False)
lib_module = emulator.load_library(so_file, do_init=False)
# call .init.proc
emulator.call_native(lib_module.base + 0x320B8 + 1)
for funcptr in lib_module.init_array:
logger.info("init_array -->" + str(hex(funcptr)))
emulator.call_native(funcptr)
result = emulator.call_symbol(lib_module, "Java_com_kanxue_crackme_MainActivity_crypt2",
emulator.java_vm.jni_env.address_ptr, 0, sys.argv[1], is_return_jobject=False)
print(result)
main.py文件内容:
import logging
import sys
import unicorn
import capstone
from androidemu.emulator import Emulator
from androidemu.utils import memory_helpers
from androidemu.java.java_class_def import JavaClassDef
from androidemu.java.java_method_def import java_method_def
from androidemu.java.helpers.native_method import native_method
from subprocess import check_output
cs = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
cs.detail = True
class MainActivity(metaclass=JavaClassDef, jvm_name='com/kanxue/crackme/MainActivity'):
def __init__(self):
pass
@java_method_def(name='crypt2', args_list=['jstring'], signature='(Ljava/lang/String;)Z', native=False)
def crypt2(self, *args, **kwargs):
arg = args[0].value
print("crypt2 args : {}".format(arg))
ret = check_output(["python","crypt2_impl.py",arg])
ret = int(ret.decode('utf-8').strip())
print("crypt2 result : {}".format(ret))
return ret
@native_method
def sprintf(uc, str, format, args):
str_utf8 = memory_helpers.read_utf8(uc, str)
format_utf8 = memory_helpers.read_utf8(uc, format)
args_utf8 = memory_helpers.read_utf8(uc, args)
print("str_utf8:{}, format_utf8:{}, args_utf8:{}".format(str_utf8, format_utf8, args_utf8))
result_string = format_utf8 % args_utf8
memory_helpers.write_utf8(uc, str, result_string)
return len(result_string)
@native_method
def malloc(uc, size):
return emulator.memory_manager.allocate(size)
@native_method
def strcmp(uc, s1, s2):
s1_utf8 = memory_helpers.read_utf8(uc, s1)
s2_utf8 = memory_helpers.read_utf8(uc, s2)
if s1_utf8 == s2_utf8:
return 0
print("s1:{},s2:{}".format(s1_utf8, s2_utf8))
return len(s1_utf8) - len(s2_utf8)
# Configure logging
logging.basicConfig(
stream=sys.stdout,
level=logging.DEBUG,
format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)
logger = logging.getLogger(__name__)
def mem_read_unmapped(uc, type, address, size, value, user_data):
print("mem_read_unmapped type:{},address:{},size:{},value:{},pc:{}".format(type, address, size, value,
hex(uc.reg_read(
unicorn.arm_const.UC_ARM_REG_PC) - libc_module.base)))
def hook_code(uc, address, size, user_data):
if lib_module.base < address < (lib_module.base + lib_module.size):
lib_name = "libnative-lib.so"
pc = uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC) - lib_module.base
elif libc_module.base < address < (libc_module.base + libc_module.size):
lib_name = "libc.so"
pc = uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC) - libc_module.base
else:
lib_name = "lib_Unknown"
pc = uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC)
CODE = uc.mem_read(uc.reg_read(unicorn.arm_const.UC_ARM_REG_PC), size)
dis_code = None
for i in cs.disasm(CODE, 0, len(CODE)):
dis_code = i.mnemonic + " " + i.op_str
print(lib_name + " >>> Tracing instruction at {}, instruction size = {} , pc:{}, bytes:{}".format(pc, size,
hex(pc),
dis_code))
def UC_HOOK_MEM_WRITE(uc, type, address, size, value, userdata):
print("address:" + str(hex(address)) + ",value:" + str(value))
so_file = "my_binaries/libnative-lib.so"
libc_file = "example_binaries/32/libc.so"
# Initialize emulator
emulator = Emulator(vfp_inst_set=True)
emulator.modules.add_symbol_hook('sprintf', emulator.hooker.write_function(sprintf) + 1)
emulator.modules.add_symbol_hook('malloc', emulator.hooker.write_function(malloc) + 1)
emulator.modules.add_symbol_hook('strcmp', emulator.hooker.write_function(strcmp) + 1)
libc_module = emulator.load_library(libc_file, do_init=False)
lib_module = emulator.load_library(so_file, do_init=False)
# emulator.mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)
# call .init.proc
emulator.call_native(lib_module.base + 0x320B8 + 1)
for funcptr in lib_module.init_array:
logger.info("init_array -->" + str(hex(funcptr)))
emulator.call_native(funcptr)
emulator.java_classloader.add_class(MainActivity)
result = emulator.call_symbol(lib_module, "Java_com_kanxue_crackme_MainActivity_jnicheck",
emulator.java_vm.jni_env.address_ptr, 0, "XUe", is_return_jobject=False)
print("jnicheck result : {}".format(result))
当输入值为'XUe'时打印出来的结果为:
2022-01-14 14:48:41,303 DEBUG androidemu.internal.modules | Loading module 'example_binaries/32/libc.so'.
2022-01-14 14:48:41,311 DEBUG androidemu.internal.modules | => Base address: 0xa0000000
2022-01-14 14:48:41,330 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:48:41,330 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:48:42,125 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:48:42,125 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:48:42,529 DEBUG androidemu.internal.modules | Loading module 'my_binaries/libnative-lib.so'.
2022-01-14 14:48:42,532 DEBUG androidemu.internal.modules | => Base address: 0xa009b000
2022-01-14 14:48:42,566 INFO __main__ | init_array -->0xa00cd1e1
2022-01-14 14:48:42,568 DEBUG androidemu.java.jni_env | JNIEnv->GetStringUtfChars(1, 0) was called
2022-01-14 14:48:42,568 DEBUG androidemu.java.jni_env | => XUe
str_utf8:, format_utf8:%s666, args_utf8:XUe
2022-01-14 14:48:42,572 DEBUG androidemu.java.jni_env | JNIEnv->NewStringUtf(XUe666) was called
2022-01-14 14:48:42,573 DEBUG androidemu.java.jni_env | JNIEnv->GetStringUtfChars(2, 0) was called
2022-01-14 14:48:42,573 DEBUG androidemu.java.jni_env | => XUe666
2022-01-14 14:48:42,575 DEBUG androidemu.java.jni_env | JNIEnv->FindClass(com/kanxue/crackme/MainActivity) was called
2022-01-14 14:48:42,575 DEBUG androidemu.java.jni_env | JNIEnv->GetStaticMethodId(3, crypt2, (Ljava/lang/String;)Z) was called
2022-01-14 14:48:42,576 DEBUG androidemu.java.jni_env | JNIEnv->CallStaticBooleanMethodV(com/kanxue/crackme/MainActivity, crypt2 <(Ljava/lang/String;)Z>, 0x100ffea4) was called
crypt2 args : XUe666
2022-01-14 14:48:42,856 DEBUG androidemu.internal.modules | Loading module 'example_binaries/32/libc.so'.
2022-01-14 14:48:42,864 DEBUG androidemu.internal.modules | => Base address: 0xa0000000
2022-01-14 14:48:42,883 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:48:42,883 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:48:43,678 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:48:43,679 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:48:44,093 DEBUG androidemu.internal.modules | Loading module 'my_binaries/libnative-lib.so'.
2022-01-14 14:48:44,096 DEBUG androidemu.internal.modules | => Base address: 0xa009b000
2022-01-14 14:48:44,131 INFO __main__ | init_array -->0xa00cd1e1
2022-01-14 14:48:44,133 DEBUG androidemu.java.jni_env | JNIEnv->GetStringUtfChars(1, 0) was called
2022-01-14 14:48:44,133 DEBUG androidemu.java.jni_env | => XUe666
2022-01-14 14:48:44,135 INFO __main__ | str_utf8:, format_utf8:%s, args_utf8:XUe666
2022-01-14 14:48:44,143 INFO __main__ | s1:WFVlNjY2,s2:WFVlNjY2,ret:0
crypt2 result : 1
jnicheck result : 1
Process finished with exit code 0
当输入值不为'XUe'时打印出来的结果为:
2022-01-14 14:49:34,282 DEBUG androidemu.internal.modules | Loading module 'example_binaries/32/libc.so'.
2022-01-14 14:49:34,289 DEBUG androidemu.internal.modules | => Base address: 0xa0000000
2022-01-14 14:49:34,308 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:49:34,308 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:49:35,089 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:49:35,090 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:49:35,493 DEBUG androidemu.internal.modules | Loading module 'my_binaries/libnative-lib.so'.
2022-01-14 14:49:35,496 DEBUG androidemu.internal.modules | => Base address: 0xa009b000
2022-01-14 14:49:35,530 INFO __main__ | init_array -->0xa00cd1e1
2022-01-14 14:49:35,531 DEBUG androidemu.java.jni_env | JNIEnv->GetStringUtfChars(1, 0) was called
2022-01-14 14:49:35,532 DEBUG androidemu.java.jni_env | => AAA
str_utf8:, format_utf8:%s666, args_utf8:AAA
2022-01-14 14:49:35,536 DEBUG androidemu.java.jni_env | JNIEnv->NewStringUtf(AAA666) was called
2022-01-14 14:49:35,537 DEBUG androidemu.java.jni_env | JNIEnv->GetStringUtfChars(2, 0) was called
2022-01-14 14:49:35,537 DEBUG androidemu.java.jni_env | => AAA666
2022-01-14 14:49:35,538 DEBUG androidemu.java.jni_env | JNIEnv->FindClass(com/kanxue/crackme/MainActivity) was called
2022-01-14 14:49:35,539 DEBUG androidemu.java.jni_env | JNIEnv->GetStaticMethodId(3, crypt2, (Ljava/lang/String;)Z) was called
2022-01-14 14:49:35,540 DEBUG androidemu.java.jni_env | JNIEnv->CallStaticBooleanMethodV(com/kanxue/crackme/MainActivity, crypt2 <(Ljava/lang/String;)Z>, 0x100ffea4) was called
crypt2 args : AAA666
2022-01-14 14:49:35,822 DEBUG androidemu.internal.modules | Loading module 'example_binaries/32/libc.so'.
2022-01-14 14:49:35,829 DEBUG androidemu.internal.modules | => Base address: 0xa0000000
2022-01-14 14:49:35,849 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:49:35,849 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:49:36,647 ERROR androidemu.internal.modules | => Undefined external symbol: android_get_application_target_sdk_version
2022-01-14 14:49:36,648 ERROR androidemu.internal.modules | => Undefined external symbol: dl_unwind_find_exidx
2022-01-14 14:49:37,059 DEBUG androidemu.internal.modules | Loading module 'my_binaries/libnative-lib.so'.
2022-01-14 14:49:37,062 DEBUG androidemu.internal.modules | => Base address: 0xa009b000
2022-01-14 14:49:37,096 INFO __main__ | init_array -->0xa00cd1e1
2022-01-14 14:49:37,098 DEBUG androidemu.java.jni_env | JNIEnv->GetStringUtfChars(1, 0) was called
2022-01-14 14:49:37,098 DEBUG androidemu.java.jni_env | => AAA666
2022-01-14 14:49:37,100 INFO __main__ | str_utf8:, format_utf8:%s, args_utf8:AAA666
2022-01-14 14:49:37,108 INFO __main__ | s1:WFVlNjY2,s2:QUFBNjY2,ret:1
crypt2 result : 0
jnicheck result : 0
Process finished with exit code 0
模拟成功。
看雪ID:飞翔的猫咪
https://bbs.pediy.com/user-home-607812.htm
# 往期推荐
6.V8 Array.prototype.concat函数出现过的issues和他们的POC们
球分享
球点赞
球在看
点击“阅读原文”,了解更多!