篇幅有限 完整内容及源码关注公众号:ReverseCode,发送 冲
抓包 安装某东9.2.2,启动postern,开启socks抓包
分析 jadx反编译搜索sign=
或者getSign
,出现地方太多随机挑一个
跟进HMACSHA256
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static String HMACSHA256(byte[] bArr, byte[] bArr2) { try { SecretKeySpec secretKeySpec = new SecretKeySpec(bArr2, "HmacSHA256"); Mac instance = Mac.getInstance("HmacSHA256"); instance.init(secretKeySpec); return byte2hex(instance.doFinal(bArr)); } catch (NoSuchAlgorithmException e2) { e2.printStackTrace(); return null; } catch (InvalidKeyException e3) { e3.printStackTrace(); return null; } }
adb shell dumpsys activity activities|more
查看当前运行app的进程名为com.jingdong.app.mall
,尝试使用frida对SecretKeySpec
进行hook时SecretKeySpec
报错secretKeySpec.$init.overload().implementation
启动了frida-server,app就进程卡死,被检测到了frida,具体方案查看多种特征检测 Frida
尝试修改frida名字为fs1280arm64
,不以默认端口27047启动./fs1280arm64 -l 127.0.0.1:8080
,转发端口到主机adb forward tcp:8080 tcp:8080
Frida frida -H 127.0.0.1:8080 com.jingdong.app.mall -l jd.js
根据反编译源码Mac instance = Mac.getInstance("HmacSHA256");
进行hook,结果并没有hook上,检测frida可以通过hluda反检测hluda-server-15.1.12-android-arm64.xz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 setImmediate(function(){ Java.perform(function () { var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec'); secretKeySpec.$init.overload('[B','java.lang.String').implementation = function (a,b) { var result = this.$init(a, b); console.log(">>> 算法名" + b); return result; } var mac = Java.use('javax.crypto.Mac'); mac.getInstance.overload('java.lang.String').implementation = function (a) { // showStacks(); var result = this.getInstance(a); console.log("mac ======================================"); console.log("算法名:" + a); return result; } mac.update.overload('[B').implementation = function (a) { this.update(a); console.log("mac ======================================"); console.log("update:" + bytesToString(a)) } mac.update.overload('[B','int','int').implementation = function (a,b,c) { this.update(a,b,c) console.log("mac ======================================"); console.log("update:" + bytesToString(a) + "|" + b + "|" + c); } mac.doFinal.overload().implementation = function () { var result = this.doFinal(); console.log("mac ======================================"); console.log("doFinal结果(hex):" + bytesToHex(result)); console.log("doFinal结果(base64):" + bytesToBase64(result)); return result; } mac.doFinal.overload('[B').implementation = function (a) { var result = this.doFinal(a); console.log("mac ======================================"); console.log("doFinal参数:" + bytesToString(a)); console.log("doFinal结果(hex):" + bytesToHex(result)); console.log("doFinal结果(base):" + bytesToBase64(result)); return result; } }) })
我们尝试通过hook http,打印调用栈获取堆栈信息,或者hook系统时间函数(因为参数中拼装了st的时间戳),找到sign可能存在的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function Where(stack){ var at = "" for(var i = 0; i < stack.length; ++i){ at += stack[i].toString() + "\n" } return at } var OkHttpClient = Java.use("okhttp3.OkHttpClient"); OkHttpClient.newCall.implementation = function (request) { var result = this.newCall(request); console.log(request.toString()); var stack = threadinstance.currentThread().getStackTrace(); console.log("http >>> Full call stack:" + Where(stack)); return result; }; var SystemClass = Java.use('java.lang.System'); SystemClass.currentTimeMillis.implementation = function(){ var result = this.currentTimeMillis(); console.log("==== " + result + " ===="); return result; }
在jadx中搜索com.jingdong.sdk.jdupgrade.inner
也一无所获,大概率在so里完成的加密。通过在so中搜索grep "sign" *.so
或者strings -f *.so | grep "Sign"
批量搜索so中的字符串
使用ida打开libjdbitmapkit.so
,搜索sign
静态绑定函数,尝试hook该方法com.jingdong.common.utils.BitmapkitUtils
1 2 3 4 5 6 7 8 9 10 var checkHookG = Java.use('com.jingdong.common.utils.BitmapkitUtils'); checkHookG.getSignFromJni.implementation = function(a,b,c,d,e,f){ var result = this.getSignFromJni(a,b,c,d,e,f); console.log(">>> checkHookG = " + b + ' / ' + c + ' / ' + d + ' / ' + d + ' / ' + f + ' \n rc= ' + result); // var stack = threadinstance.currentThread().getStackTrace(); // console.log("Full call stack:" + Where(stack)); return result; }
查看BitmapkitUtils
类,一共5个参数都是String
主动调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function hookBitmapkitUtils() { Java.perform(function () { var currentApplication = Java.use('android.app.ActivityThread').currentApplication(); var context = currentApplication.getApplicationContext(); Java.choose("com.jingdong.common.utils.BitmapkitUtils",{ onMatch:function(instance){ console.log("found instance =>",instance); var a = "search"; var b = '{"addrFilter":"1","addressId":"0","articleEssay":"1","deviceidTail":"35","exposedCount":"0","gcLat":"0.0","gcLng":"0.0","imagesize":{"gridImg":"709x709","listImg":"455x455","longImg":"709x908"},"insertArticle":"1","insertScene":"1","insertedCount":"0","isCorrect":"1","keyword":"gg","locLat":"","locLng":"","newMiddleTag":"1","newVersion":"3","oneBoxMod":"1","orignalSearch":"1","orignalSelect":"1","page":"1","pageEntrance":"1","pagesize":"10","pvid":"","searchVersionCode":"9180","secondInsedCount":"0","showShopTab":"yes","showStoreTab":"1","stock":"1"}' var c = '-accf8528c046' var d = '-accf8528c046' var e = '9.2.2' var signature = instance.getSignFromJni(context, a, b, c, d, e) console.log(signature) return signature },onComplete:function(){ console.log('Search complete') } }) }) }
Xposed 尝试主动调用com.jingdong.common.utils.BitmapkitUtils的getSignFromJni,传入通过frida hook得到的参数信息,暴露http请求
HookLoader public static native String getSignFromJni(Context context, String str, String str2, String str3, String str4, String str5);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 public class HookLoader implements IXposedHookLoadPackage { private final static String TAG = "onejane"; public static void log(String s) { Log.i(TAG, s); } public static Context applicationContext; public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (loadPackageParam.packageName.equals("com.jingdong.app.mall")) { log("Im comming jd 1"); try { Class<?> ContextClass = XposedHelpers.findClass("android.content.ContextWrapper", loadPackageParam.classLoader); XposedHelpers.findAndHookMethod(ContextClass, "getApplicationContext", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); if (applicationContext != null) return; applicationContext = (Context) param.getResult(); log("-->得到上下文"); } }); } catch (Throwable t) { log("-->获取上下文出错"); // XposedBridge.log(t); } // http server class myHttpServer extends NanoHTTPD { private static final String REQUEST_ROOT = "/"; public myHttpServer() throws IOException { // 端口是8088,也就是说要通过http://127.0.0.1:8088来访当问 super(8888); start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); log("---onejane Server---"); } @Override public Response serve(IHTTPSession session) { // log("serve"); //这个就是之前分析,重写父类的一个参数的方法, //这里边已经把所有的解析操作已经在这里执行了 return super.serve(session); } @Override public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) { // log("serve xxx"); //这就是上边的serve方法最后一行调用的那个过时的方法,这里简单的做个判断就好了 // if (!method.equals(Method.POST)) {//判断请求方式是否争取 // return newFixedLengthResponse("the request method is incoorect"); // } log(uri); String strA = ""; String strB = ""; for (Map.Entry<String, String> entry : parms.entrySet()) { strA += " parms Key = " + entry.getKey() + ", Value = " + entry.getValue(); log("parms Key = " + entry.getKey() + ", Value = " + entry.getValue()); } for (Map.Entry<String, String> entry : files.entrySet()) { strB += " files Key = " + entry.getKey() + ", Value = " + entry.getValue(); log("files Key = " + entry.getKey() + ", Value = " + entry.getValue()); } Class<?> clazzJDUtils = null; try { clazzJDUtils = loadPackageParam.classLoader.loadClass("com.jingdong.common.utils.BitmapkitUtils"); log("load class:" + clazzJDUtils); } catch (Exception e) { log("load class err:" + Log.getStackTraceString(e)); return newFixedLengthResponse("BitmapkitUtils load class is null"); } if (StringUtils.containsIgnoreCase(uri, "getSignFromJni")) {//判断uri是否正确 String str = parms.get("str"); String str2 = parms.get("str2"); String str3 = parms.get("str3"); String str4 = parms.get("str4"); String str5 = parms.get("str5"); log("getSignFromJni:" + str + " / " + str2 + " / " + str3 + " / " + str4 + " / " + str5); if (!StringUtils.isEmpty(str)) {//判断post过来的数据是否正确 return getSignFromJni(clazzJDUtils, str, str2, str3, str4, str5); } else { return newFixedLengthResponse("getSignFromJni postData is null, " + strA + strB); } } //判断完了开始解析数据,如果是你想要的数据,那么你就给返回一个正确的格式就好了 //举个栗子:return newFixedLengthResponse("{\"result\":0,\"success\":true}"); return super.serve(uri, method, headers, parms, files); } public Response getSignFromJni(Class<?> clazzUse, String str, String str2, String str3, String str4, String str5) { if (applicationContext != null) { String rc = (String) XposedHelpers.callStaticMethod(clazzUse, "getSignFromJni", applicationContext, str, str2, str3, str4, str5); log("getSignFromJni = " + rc); return newFixedLengthResponse(rc); } return newFixedLengthResponse("getSignFromJni Context is null"); } } new myHttpServer(); } } }
python调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requests def getKey(k1, k2, k3, k4, k5): data = { "str": k1, "str2": k2, "str3": k3, "str4": k4, "str5": k5, } sign = requests.get("http://172.20.103.239:8888/getSignFromJni", data).text return sign if __name__ == '__main__': body = '{"addrFilter":"1","addressId":"0","articleEssay":"1","deviceidTail":"35","exposedCount":"0","gcLat":"0.0","gcLng":"0.0","imagesize":{"gridImg":"709x709","listImg":"455x455","longImg":"709x908"},"insertArticle":"1","insertScene":"1","insertedCount":"0","isCorrect":"1","keyword":"冰箱","localNum":"0","newMiddleTag":"1","newVersion":"3","oneBoxMod":"1","orignalSearch":"1","orignalSelect":"1","page":"1","pageEntrance":"1","pagesize":"10","pvid":"","searchVersionCode":"9180","secondInsedCount":"0","showShopTab":"yes","showStoreTab":"1","stock":"1"}' sign = getKey("search", body, "-accf8528c046", "-accf8528c046", "9.2.2") print(sign)
ExAndroidNativeEmu OnejaneNdk Android Studio新建C++项目,src/main/cpp/native-lib.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 // ---- 静态注册 ---- extern "C" JNIEXPORT jstring JNICALL Java_com_example_onejanendk_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } // ---- 动态注册 ---- // 获取数组的大小 # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) // 指定要注册的类,对应完整的java类名 #define JNIREG_CLASS "com/example/onejanendk/MainActivity" // 返回字符串"hello load jni" JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz) { return env->NewStringUTF("onejane hello load jni."); } // Java和JNI函数的绑定表 static JNINativeMethod method_table[] = { {"HelloLoad", "()Ljava/lang/String;", (void *) native_hello},//绑定 }; // 注册native方法到java中 static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods, int numMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { return JNI_FALSE; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } int register_ndk_load(JNIEnv *env) { // 调用注册方法 return registerNativeMethods(env, JNIREG_CLASS, method_table, NELEM(method_table)); } JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { return result; } register_ndk_load(env); // 返回jni的版本 return JNI_VERSION_1_4; }
com/example/onejanendk/MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = findViewById(R.id.sample_text); // tv.setText(stringFromJNI()); tv.setText(HelloLoad()); } public native String stringFromJNI(); public native String HelloLoad(); }
生成的包中包括'x86', 'armeabi-v7a', 'arm64-v8a', 'x86_64'
这些cpu平台的libnative-lib.so
1 git clone https://github.com/maiyao1988/ExAndroidNativeEmu
新建runemu.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 def hook_code(mu, address, size, user_data): try: emu = user_data if (not emu.memory.check_addr(address, UC_PROT_EXEC)): logger.error("addr 0x%08X out of range"%(address,)) sys.exit(-1) # # androidemu.utils.debug_utils.dump_registers(mu, sys.stdout) # androidemu.utils.debug_utils.dump_code(emu, address, size, g_cfd) except Exception as e: logger.exception("exception in hook_code") sys.exit(-1) # # def hook_mem_read(uc, access, address, size, value, user_data): pc = uc.reg_read(UC_ARM_REG_PC) pass def hook_mem_write(uc, access, address, size, value, user_data): pc = uc.reg_read(UC_ARM_REG_PC) pass class MainActivity(metaclass=JavaClassDef, jvm_name='com/example/onejanendk/MainActivity'): def __init__(self): pass @java_method_def(name='HelloLoad', signature='()Ljava/lang/String;', native=True) def hello_load(self, mu): pass def main(): filename = "./libnative-lib.so" # Initialize emulator emulator = Emulator( vfp_inst_set = True, vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") ) # Register Java class. emulator.java_classloader.add_class(MainActivity) emulator.mu.hook_add(UC_HOOK_CODE, hook_code, emulator) emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write) emulator.mu.hook_add(UC_HOOK_MEM_READ, hook_mem_read) # Load all libraries. lib_module = emulator.load_library(filename) # androidemu.utils.debug_utils.dump_symbols(emulator, sys.stdout) # Show loaded modules. logger.info("Loaded modules:") for module in emulator.modules: logger.info("=> 0x%08x - %s" % (module.base, module.filename)) logger.info(">>> libnative-lib.so load_base = 0x%x" % (lib_module.base) ) try: # Run JNI_OnLoad. # JNI_OnLoad will call 'RegisterNatives'. emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) # 第一种方法,直接调用 strRc = emulator.call_symbol(lib_module, 'Java_com_example_onejanendk_MainActivity_stringFromJNI',emulator.java_vm.jni_env.address_ptr,0x00) print("stringFromJNI result call: %s" % strRc) # 第二种方法,通过类成员函数来调用 # Do native stuff. main_activity = MainActivity() logger.info("Response from JNI call: %s" % main_activity.hello_load(emulator)) # Dump natives found. logger.info("Exited EMU.") logger.info("Native methods registered to MainActivity:") for method in MainActivity.jvm_methods.values(): if method.native: logger.info("- [0x%08x] %s - %s" % (method.native_addr, method.name, method.signature)) except UcError as e: print("Exit at 0x%x" % emulator.mu.reg_read(UC_ARM_REG_PC)) raise
python模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 def main(): filename = "../jd/libjdbitmapkit.so" # Initialize emulator emulator = Emulator( vfp_inst_set = True, vfs_root=posixpath.join(posixpath.dirname(__file__), "vfs") ) # Register Java class. emulator.mu.hook_add(UC_HOOK_CODE, hook_code, emulator) emulator.mu.hook_add(UC_HOOK_MEM_WRITE, hook_mem_write) emulator.mu.hook_add(UC_HOOK_MEM_READ, hook_mem_read) # Load all libraries. lib_module = emulator.load_library(filename) # androidemu.utils.debug_utils.dump_symbols(emulator, sys.stdout) # Show loaded modules. logger.info("Loaded modules:") for module in emulator.modules: logger.info("=> 0x%08x - %s" % (module.base, module.filename)) logger.info(">>> libnative-lib.so load_base = 0x%x" % (lib_module.base) ) try: # Run JNI_OnLoad. # JNI_OnLoad will call 'RegisterNatives'. emulator.call_symbol(lib_module, 'JNI_OnLoad', emulator.java_vm.address_ptr, 0x00) except UcError as e: print("Exit at 0x%x" % emulator.mu.reg_read(UC_ARM_REG_PC)) raise
python runemu.py
报错如下:RuntimeError: Could not find class 'com/jingdong/common/utils/BitmapkitUtils' for JNIEnv.
上文已经知道sign
静态绑定函数是libjdbitmapkit.so
的Java_com_jingdong_common_utils_BitmapkitUtils_getSignFromJni
,在JNI中注册 BitmapkitUtils类 ,JavaClassDef指定类型
1 2 3 4 5 6 7 from androidemu.java.java_field_def import JavaFieldDef class BitmapkitUtils(metaclass=JavaClassDef, jvm_name='com/jingdong/common/utils/BitmapkitUtils'): def __init__(self): pass emulator.java_classloader.add_class(BitmapkitUtils)
报错如下:RuntimeError: Could not find static field ('a', 'Landroid/app/Application;') in class com/jingdong/common/utils/BitmapkitUtils.
1 2 3 4 5 6 7 8 9 class Application(metaclass=JavaClassDef, jvm_name='android/app/Application'): def __init__(self): pass class BitmapkitUtils(metaclass=JavaClassDef, jvm_name='com/jingdong/common/utils/BitmapkitUtils',jvm_fields=[JavaFieldDef("a", "Landroid/app/Application;", True, Application())]): def __init__(self): pass emulator.java_classloader.add_class(Application) emulator.java_classloader.add_class(BitmapkitUtils)
报错如下:RuntimeError: Could not find class 'android/app/Activity' for JNIEnv.
1 2 3 4 class Activity(metaclass=JavaClassDef, jvm_name='android/app/Activity'): def __init__(self): pass emulator.java_classloader.add_class(Activity)
报错如下:NameError: name 'Application' is not defined
添加Application
1 2 3 4 class Application(metaclass=JavaClassDef, jvm_name='android/app/Application'): def __init__(self): pass emulator.java_classloader.add_class(Application)
报错如下:RuntimeError: Could not find class 'android/content/pm/Signature' for JNIEnv.
添加Signature
1 2 3 4 class Signature(metaclass=JavaClassDef, jvm_name='android/content/pm/Signature'): def __init__(self): pass emulator.java_classloader.add_class(Signature)
报错如下:RuntimeError: Could not find method ('getPackageManager', '()Landroid/content/pm/PackageManager;') in class android/app/Activity.
1 2 3 4 5 6 7 8 class Activity(metaclass=JavaClassDef, jvm_name='android/app/Activity'): def __init__(self): pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False) def getPackageManager(self, mu): logger.info("Im in Activity.getPackageManager") pass
报错如下:没有call_object_method
的实现
getPackageManager方法大多在JNI_OnLoad中实现,IDA打开后进入check_status(v21)
,CallObjectMethod
方法有三个参数,a1是JNIEnv,dword_175DC
是十六进制地址,v4是方法id
call_object_method函数报错,从jni_env.py里面找到这个函数,发现没有实现这个函数,手动实现如下
1 2 3 4 @native_method def call_object_method(self, mu,env,obj_idx, method_id): logger.debug("JNIEnv->call_object_method(%d,%s) was called" % (obj_idx,method_id) ) return self.__call_xxx_method(mu, env, obj_idx, method_id, None, 1)
继续运行runemu.py 报错如下,3523215368 方法调用失败
报错的obj_idx和method_id打印出来为JNIEnv->call_object_method(4096,3523215368) was called
在jni_env.py中的find_class
添加log
1 2 3 rc = self.add_local_reference(jclass(clazz)) logger.debug("JNIEnv->FindClass(%s) was called, rc = %d" % (name,rc)) return rc
说明3523215368
是android/app/Activity类的getPackageManager方法,该方法之前已经补过了,在__call_xxx_method
中添加logger.debug(type(pyobj))
,发现该对象是<class '__main__.Application'>
,因为 Application 和 Activity 都是 ContextWrapper 的子类, getPackageManager 是 ContextWrapper 里的方法,获取的两个 jmethodid 自然是一样的。
我们在java_method_def.py中的方法java_method_def加一个参数jvm_id,并在初始化的时候做兼容
1 2 3 4 5 6 7 def __init__(self, func_name, func, name, signature, native, args_list=None, modifier=None, ignore=None,jvm_id = None): if jvm_id == None: self.jvm_id = next_method_id() else: self.jvm_id = jvm_id logger.debug("JavaMethodDef name =%s,jvm_id = %s" % (name,self.jvm_id))
在runemu.py中为Application和Activity都加上getPackageManager 方法,设置jvm_id相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Application(metaclass=JavaClassDef, jvm_name='android/app/Application'): def __init__(self): pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False,jvm_id=0xd2000000+0x1000) def getPackageManager(self, mu): logger.info("Im in Application.getPackageManager") pass class Activity(metaclass=JavaClassDef, jvm_name='android/app/Activity'): def __init__(self): pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False,jvm_id=0xd2000000+0x1000) def getPackageManager(self, mu): logger.info("Im in Activity.getPackageManager") pass
对抗AndroidNativeEmu思路,拿到Application的对象,然后也去调用Activity类的getPackageManager,跑到这个call_object_method,AndroidNativeEmu就会崩溃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Java_com_example_onejanendk_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { jclass AppClass = env->FindClass("android/app/Application"); jmethodID midA = env->GetMethodID(AppClass, "getPackageManager","()Landroid/content/pm/PackageManager;"); Method *pMid_a = (Method *) midA; LOGD("midA = 0x%08X", midA); LOGD("pMid_a = 0x%08X", pMid_a->nativeFunc); jclass AppClassB = env->FindClass("android/app/Activity"); jmethodID midB = env->GetMethodID(AppClassB, "getPackageManager","()Landroid/content/pm/PackageManager;"); Method *pMid_b = (Method *) midB; LOGD("midB = 0x%08X", midB); LOGD("pMid_b = 0x%08X", pMid_b->nativeFunc); //获取Activity Thread的实例对象 jclass activityThread = env->FindClass("android/app/ActivityThread"); jmethodID currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread); //获取Application,也就是全局的Context jmethodID getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;"); jobject ApplicationObj = env->CallObjectMethod(at, getApplication); jobject packageManager = env->CallObjectMethod(ApplicationObj, midB); std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
Could not find method ('getPackageName', '()Ljava/lang/String;') in class android/app/Activity.
回到上面的报错,添加getPackageName
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Application(metaclass=JavaClassDef, jvm_name='android/app/Application'): def __init__(self): pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False,jvm_id=0xd2000000+0x1000) def getPackageManager(self, mu): logger.info("Im in Application.getPackageManager") pass @java_method_def(name='getPackageName', signature='()Ljava/lang/String;' , native=False,jvm_id=0xd2000000+0x1004) def getPackageName(self, mu): logger.info("Im in Application.getPackageName") return "com.jingdong.app.mall" class Activity(metaclass=JavaClassDef, jvm_name='android/app/Activity'): def __init__(self): pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False,jvm_id=0xd2000000+0x1000) def getPackageManager(self, mu): logger.info("Im in Activity.getPackageManager") pass @java_method_def(name='getPackageName', signature='()Ljava/lang/String;' , native=False,jvm_id=0xd2000000+0x1004) def getPackageName(self, mu): logger.info("Im in Application.getPackageName") return "com.jingdong.app.mall"
3523215368 从前面的日志能找到是getPackageInfo方法的jvm_id,打开androidemu.utils.debug_utils.dump_code(emu, address, size, g_cfd) 开关,会输出汇编代码,可以方便定位出错的代码位置。
IDA打开so,跳到0x00002BF4查看
之前调用 Application.getPackageManager() 来返回的PackageManager对象,在之前的代码里我们只打印了一下日志,并没有实现这个函数,引入原作者定义的PackageManager类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from androidemu.java.classes.package_manager import * class Application(metaclass=JavaClassDef, jvm_name='android/app/Application'): def __init__(self): self.__pkg_Manager = PackageManager() pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False,jvm_id=0xd2000000+0x1000) def getPackageManager(self, mu): logger.info("Im in Application.getPackageManager") return self.__pkg_Manager @java_method_def(name='getPackageName', signature='()Ljava/lang/String;' , native=False,jvm_id=0xd2000000+0x1004) def getPackageName(self, mu): logger.info("Im in Application.getPackageName") return "com.jingdong.app.mall" class Activity(metaclass=JavaClassDef, jvm_name='android/app/Activity'): def __init__(self): self.__pkg_Manager = PackageManager() pass @java_method_def(name='getPackageManager', signature='()Landroid/content/pm/PackageManager;' , native=False,jvm_id=0xd2000000+0x1000) def getPackageManager(self, mu): logger.info("Im in Activity.getPackageManager") return self.__pkg_Manager @java_method_def(name='getPackageName', signature='()Ljava/lang/String;' , native=False,jvm_id=0xd2000000+0x1004) def getPackageName(self, mu): logger.info("Im in Application.getPackageName") return "com.jingdong.app.mall"
继续报错Could not find field ('signatures', '[Landroid/content/pm/Signature;') in class android/content/pm/PackageInfo.
,于是我们将实现的Signature类挪到package_manager.py的PackageInfo中
1 2 3 4 5 6 7 8 9 10 11 12 class Signature(metaclass=JavaClassDef, jvm_name='android/content/pm/Signature'): def __init__(self): pass class PackageInfo(metaclass=JavaClassDef, jvm_name='android/content/pm/PackageInfo', jvm_fields=[ JavaFieldDef('applicationInfo', 'Landroid/content/pm/ApplicationInfo;', False), JavaFieldDef("signatures", "[Landroid/content/pm/Signature;", False), ]): def __init__(self): self.applicationInfo = ApplicationInfo() self.signatures = [Signature(),]
报错Could not find method ('toByteArray', '()[B') in class android/content/pm/Signature.
,增加toByteArray, 它的返回值是个字节数组,暂时返回一个1234567890
1 2 3 4 5 6 7 8 class Signature(metaclass=JavaClassDef, jvm_name='android/content/pm/Signature'): def __init__(self): pass @java_method_def(name='toByteArray', signature='()[B') def toByteArray(self,mu): logger.info("Im in Signature.toByteArray 3082") return bytearray.fromhex('1234567890')
报错NotImplementedError
get_byte_array_elements
方法没有实现,将jbyteArray转成jbyte指针,参数有2个,jbyteArray对象和一个bool型的变量,GetByteArrayElements和ReleaseByteArrayElements是成对出现的,我们在jni_eny.py中添加函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @native_method def get_byte_array_elements(self, mu, env,array_idx,isCopy): logger.debug("JNIEnv->get_byte_array_elements (%d,%d) was called ", array_idx,isCopy) obj = self.get_local_reference(array_idx) if not isinstance(obj,jbyteArray): raise ValueError("Expected a jbyteArray") data_ptr = self._emu.native_memory.allocate(len(obj.value)) mu.mem_write(data_ptr,bytes(obj.value)) return data_ptr @native_method def release_byte_array_elements(self, mu, env,jbArr,jb_ptr): logger.debug("JNIEnv->release_byte_array_elements was called") self._emu.native_memory.free(jb_ptr) # raise NotImplementedError()
在memory.py中添加free函数
1 2 def free(self,data_ptr): return self._memory.unmap(data_ptr)
修改memory_map.py中的unmap函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def unmap(self, addr, size = None): if not self.__is_multiple(addr): raise RuntimeError('addr was not multiple of page size (%d, %d).' % (addr, PAGE_SIZE)) if size == None: size = page_end(addr) - addr else: size = page_end(addr+size) - addr try: print("unmap 0x%08X sz=0x0x%08X end=0x0x%08X"%(addr,size, addr+size)) if (addr in self.__file_map_addr): file_map_attr = self.__file_map_addr[addr] if (addr+size != file_map_attr[0]): raise RuntimeError("unmap error, range 0x%08X-0x%08X does not match file map range 0x%08X-0x%08X from file %s" %(addr, addr+size, addr, file_map_attr[0])) # self.__file_map_addr.pop(addr) # self.__mu.mem_unmap(addr, size) # except unicorn.UcError as e: #TODO:just for debug for r in self.__mu.mem_regions(): print("region begin :0x%08X end:0x%08X, prot:%d"%(r[0], r[1], r[2])) # raise return -1 # return 0
报错:Could not find class 'com/jingdong/jdsdk/widget/ToastUtils' for JNIEnv.
runemu.py添加类ToastUtils
1 2 3 4 5 6 7 8 9 10 class ToastUtils(metaclass=JavaClassDef, jvm_name='com/jingdong/jdsdk/widget/ToastUtils'): def __init__(self): pass @java_method_def(name='longToast',args_list=["jstring"], signature='(Ljava/lang/String;)V') def longToast(self, mu, *args, **kwargs): logger.info("longToast %r" % args) pass emulator.java_classloader.add_class(ToastUtils)
终于将JNI_OnLoad 跑通了
getSignFromJni函数我们之前分析过,它一共有6个参数,第一个参数是Context,后面5个都是String
1 2 3 4 5 from androidemu.java.classes.activity_thread import * activity_Th = ActivityThread() result = emulator.call_symbol(lib_module, 'Java_com_jingdong_common_utils_BitmapkitUtils_getSignFromJni',emulator.java_vm.jni_env.address_ptr,0x00, activity_Th.getSystemContext(emulator),"asynInteface", '{"intefaceType":"asynIntefaceType","skuId":"100008667315"}', "99001184062989-f460e22c02fa", "android", "9.2.2") logger.info("Response from JNI call: %s" % result.toString(emulator))
IDA反编译出的伪代码中比java多了两个参数,JNIEnv *env 和 jobject obj
JNIEnv类型实际上代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。例如,创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等等
jobject obj参数表示了 如果native方法不是static的话,这个obj就代表这个native方法的类实例,如果native方法是static的话,这个obj就代表这个native方法的类的class对象实例(static方法不需要类实例的,所以就代表这个类的class对象)
报错:RuntimeError: Could not find class 'java/lang/StringBuffer' for JNIEnv.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 from androidemu.java.jni_env import JNIEnv class StringBuffer(metaclass=JavaClassDef, jvm_name='java/lang/StringBuffer'): def __init__(self): self._str = '' pass @java_method_def(name='<init>', signature='()V', native=False) def init(self, emu): pass @java_method_def(name='append', args_list=["jstring"], signature='(Ljava/lang/String;)Ljava/lang/StringBuffer;', native=False) def append(self, emu,*args, **kwargs): logger.info("append %r" % args) pyobj = JNIEnv.jobject_to_pyobject(args[0]) self._str += pyobj return self @java_method_def(name='toString', signature='()Ljava/lang/String;', native=False) def toString(self, emu): logger.info("toString %r" % self._str) return String(self._str) class Integer(metaclass=JavaClassDef, jvm_name='java/lang/Integer'): def __init__(self): self._int = -1 pass @java_method_def(name='<init>', args_list=["jint"], signature='(I)V', native=False) def init(self, emu,*args, **kwargs): # pyobj = JNIEnv.jobject_to_pyobject(args[0]) logger.info("Integer init %d" % args[0]) self._int = args[0] pass @java_method_def(name='toString', signature='()Ljava/lang/String;', native=False) def toString(self, emu): logger.info("Integer toString %d" % self._int) return str(self._int) class String(metaclass=JavaClassDef, jvm_name='java/lang/String'): def __init__(self,inStr=''): self._str = inStr pass @java_method_def(name='getBytes', args_list=["jstring"], signature='(Ljava/lang/String;)[B', native=False) def getBytes(self, emu,*args, **kwargs): logger.info("%r String getBytes %r" % (self._str, args) ) return bytearray(self._str.encode('utf8')) @java_method_def(name='toString', signature='()Ljava/lang/String;', native=False) def toString(self, emu): return self._str emulator.java_classloader.add_class(StringBuffer) emulator.java_classloader.add_class(String) emulator.java_classloader.add_class(Integer)
Unidbg 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class JingDong extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private Module module; private DvmClass bitMapKitUtils; // jd apk 文件路径 public String apkPath = ""; // jd apk so 文件路径 public String soPath = ""; private static LibraryResolver createLibraryResolver() { return new AndroidResolver(23); } private static AndroidEmulator createARMEmulator() { return AndroidEmulatorBuilder.for32Bit().setProcessName("").build(); } public JingDong() { emulator = createARMEmulator(); final Memory memory = emulator.getMemory(); memory.setLibraryResolver(createLibraryResolver()); vm = emulator.createDalvikVM(new File(apkPath)); vm.setVerbose(true); DalvikModule dm = vm.loadLibrary(new File(soPath), false); vm.setJni(this); dm.callJNI_OnLoad(emulator); module = dm.getModule(); } public static void main(String[] args) throws IOException { JingDong jd = new JingDong(); jd.destroy(); } private void destroy() throws IOException { emulator.close(); } }
1 2 3 4 5 6 7 8 9 10 @Override public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) { switch (signature) { case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": // resolveClass 创建目标类,newObject创建实例 return vm.resolveClass("android/content/Application").newObject(null); } return super.getStaticObjectField(vm, dvmClass, signature); }
这里dvmMethod为空,打个log看看
1 2 3 4 5 6 7 public static void main(String[] args) throws IOException { Logger.getLogger(DalvikVM.class).setLevel(Level.DEBUG); Logger.getLogger(BaseVM.class).setLevel(Level.DEBUG); JingDong jd = new JingDong(); jd.destroy(); }
从常规的jni开发来说,一般就是findClass,找到类对象,再通过类对象,找到方法的methodID,再通过callxxmethod来进行调用,第一个参数是jobject(打个比方,如果是static的话,就是jclass),第二个参数就是methodID,第三个参数就是输入参数,根据https://github.com/zhkl0228/unidbg/issues/315
MethodID不匹配,需要明确继承关系。so里面 Activity 类根本就没有实例化,可以在DalvikVM.java中加上硬编码转换或者把Application换成Activity也可以,毕竟Activity也是可以获取Application对象的
或者在getStaticObjectField中表明继承关系
unidbg中getMethodId方法中表明将每个类生成一个hashmap,以signature作为hashcode的key,类方法作为DvmMethod作为value,getMethod时先从hashmap中查找,找不到就从超类中查找,实现继承关关系就是为了DvmMethod的查找,如果application不继承自身hashmap没有,自然超类也没有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) { switch (signature) { case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": // resolveClass 创建目标类,newObject创建实例 /** * DvmClass activity=vm.resolveClass("android/app/Activity") * DvmClass application=vm.resolveClass("android/app/Application",activity); * return application.newObject(signature); */ return vm.resolveClass("android/app/Application",vm.resolveClass("android/app/Activity")).newObject(null); // return vm.resolveClass("android/app/Activity", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null); // return vm.resolveClass("android/app/Application").newObject(null); } return super.getStaticObjectField(vm, dvmClass, signature); }
1 2 3 4 5 6 7 8 @Override public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { switch (signature) { case "android/app/Application->getApplicationInfo()Landroid/content/pm/ApplicationInfo;": return new ApplicationInfo(vm); } return super.callObjectMethod(vm, dvmObject, signature, varArg); }
1 2 3 4 5 6 7 @Override public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) { if ("android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;".equals(signature)) { return new StringObject(vm, apkPath); } return super.getObjectField(vm, dvmObject, signature); }
objection -N -h 127.0.0.1 -p 8080 -g com.jingdong.app.mall explore -P ~/.objection/plugins –startup-command “android hooking watch class_method com.jingdong.common.utils.BitmapkitZip.unZip –dump-args –dump-return” 远程objection的spawn启动,查看入参并未检测到
frida -U -f com.jingdong.app.mall -l hook_RegisterNatives.js 修改针对性输出并没有想要的信息
IDA打开libjdbitmapkit.so,shift+F12搜索unZip
F5拿到伪C代码
尝试hook so的导出函数和偏移地址,findBaseAddress并没有拿到地址,hook失败。
mikrom以listen_wait启动,frida -U 京东 -l hook_libart.js > jd.log
,unzip的三个参数分别是app安装目录 第二个参数为META-INF 目录 第三个是RSA,反编译后拿到META-INF下的JINGDONG.RSA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature) { case "com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B": { StringObject udApkPath = varArg.getObjectArg(0); StringObject udDirectory = varArg.getObjectArg(1); StringObject udFilename = varArg.getObjectArg(2); if (apkPath.equals(udApkPath.getValue()) && "META-INF/".equals(udDirectory.getValue()) && ".RSA".equals(udFilename.getValue())) { byte[] data = vm.unzip("META-INF/JINGDONG.RSA"); return new ByteArray(vm, data); } } } return super.callStaticObjectMethod(vm ,dvmClass, signature, varArg); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature) { case "sun/security/pkcs/PKCS7-><init>([B)V": { ByteArray array = varArg.getObjectArg(0); try { return vm.resolveClass("sun/security/pkcs/PKCS7").newObject(new PKCS7(array.getValue())); } catch (ParsingException e) { throw new IllegalStateException(e); } } } return super.newObject(vm, dvmClass, signature, varArg); }
1 2 3 4 5 6 7 8 9 10 11 @Override public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) { switch (signature) { case "sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;": { PKCS7 pkcs7 = (PKCS7) dvmObject.getValue(); X509Certificate[] certificates = pkcs7.getCertificates(); return ProxyDvmObject.createObject(vm, certificates); } } return super.callObjectMethod(vm, dvmObject, signature, varArg); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) { switch (signature) { case "com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B": { DvmObject<?> obj = varArg.getObjectArg(0); byte[] bytes = objectToBytes(obj.getValue()); return new ByteArray(vm, bytes); } } } private static byte[] objectToBytes(Object obj) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); oos.flush(); byte[] array = baos.toByteArray(); oos.close(); baos.close(); return array; } catch (IOException e) { throw new IllegalStateException(e); } }
环境修补完成,开始调用函数
1 2 3 4 5 6 7 8 9 10 11 public void callSign() { DvmClass cBitmapkitUtils = vm.resolveClass("com/jingdong/common/utils/BitmapkitUtils"); StringObject ret = cBitmapkitUtils.callStaticJniMethodObject(emulator, "getSignFromJni()(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", vm.resolveClass("android/content/Context").newObject(null), "clientImage", "{\"moduleParams\":{\"18\":\"1565611060638\",\"19\":\"1565229712150\",\"25\":\"1567478504636\",\"27\":\"1602488415048\",\"28\":\"1631069159956\",\"30\":\"1567404005627\",\"32\":\"1567997588476\",\"34\":\"1593508185597\",\"35\":\"1568708316462\",\"37\":\"1630293538664\",\"42\":\"1623741761542\",\"44\":\"1569247647090\",\"46\":\"1588839806224\",\"47\":\"1571295610042\",\"61\":\"1582091758495\",\"70\":\"1585279774645\",\"74\":\"1586781606615\"}}", "d5a585639f505b18", "android", "10.2.0"); System.out.println(ret.getValue()); }
1 2 3 4 5 6 7 8 9 @Override public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "java/lang/StringBuffer-><init>()V" : { return vm.resolveClass("java/lang/StringBuffer" ).newObject(new StringBuffer()); } } return super .newObjectV(vm, dvmClass, signature, vaList); }
1 2 3 4 5 6 7 8 9 10 11 12 @Override public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { switch (signature) { case "java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;" : { StringBuffer buffer = (StringBuffer) dvmObject.getValue(); StringObject str = vaList.getObjectArg(0 ); buffer.append(str.getValue()); return dvmObject; } } return super .callObjectMethodV(vm, dvmObject, signature, vaList); }
1 2 3 4 5 6 7 8 9 10 11 @Override public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) { switch (signature) { case "java/lang/Integer-><init>(I)V": { int intArg = vaList.getIntArg(0); Integer integer = Integer.valueOf(intArg); return vm.resolveClass("java/lang/Integer").newObject(integer); } } return super.newObjectV(vm, dvmClass, signature, vaList); }
1 2 3 4 5 6 7 8 9 10 @Override public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { switch (signature) { case "java/lang/Integer->toString()Ljava/lang/String;": { Integer integer = (Integer) dvmObject.getValue(); return new StringObject(vm, integer.toString()); } } return super.callObjectMethodV(vm, dvmObject, signature, vaList); }
1 2 3 4 5 6 7 8 9 10 @Override public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { switch (signature) { case "java/lang/StringBuffer->toString()Ljava/lang/String;": { StringBuffer stringBuffer = (StringBuffer) dvmObject.getValue(); return new StringObject(vm, stringBuffer.toString()); } } return super.callObjectMethodV(vm, dvmObject, signature, vaList); }