Calling JNI Functions with Java Object Arguments from the Command Line
来源:互联网 发布:linux读取jar包文件 编辑:程序博客网 时间:2024/06/04 19:38
When analyzing malware or penetration testing an app which uses a native library, it’s helpful to isolate and execute the library’s functions. This opens the door for debugging and using the malware’s own code against it. For example, if the malware has encrypted strings and the decryption is done by a native function, you could either spend a bunch of time reversing the algorithm to write your own decryption routine or you could just harness the function such that you can execute it with arbitrary inputs. If the malware author completely changes their decryption, you might not have to change anything. In this post, I’ll explain how to harness a native library and execute its functions even if they require arguments from a live JVM instance.
In a previous post, I explained how to create a Java VM from Android native code but I didn’t give any real examples of how to use it. In this post, I’ll give a concrete example.
There are at least two approaches to harness a native function. The first is to modify the app to accept some input from you and pass that to the native function. For example, you can write an intent filter, convert it to Smali, add the code to the target app, modify the manifest, run the app, and send it intents via adb
with your arguments. Even better, you could add a small socket or web server instead of an intent filter and send curl
requests, which doesn’t require modifying the manifest.
The second approach is to create a small native executable which loads the library, calls the target function, can be executed from the command line, and passes whatever arguments you give it. This makes it easier to debug since you’re just running an executable rather than an entire app.
Target App
I created an example app so you can follow along at home. It’s called native-harness-target. To clone and build (of course replace $ANDROID_*
vars for yourself):
12345
git clone https://github.com/CalebFenton/native-harness-target.gitcd native-harness-targetecho 'ndk.dir=$ANDROID_NDK' > local.propertiesecho 'sdk.dir=$ANDROID_SDK' >> local.properties./gradlew build
The APKs will be in app/build/outputs/apk/. For this post, I’ll be using an x86 emulator image and app-universal-debug.apk.
The app has an encrypted string and uses a native library to decrypt the string at run time. Here’s how the string decryption looks in Smali:
123456789101112131415161718
const/16 v3, 0x57new-array v1, v3, [Bfill-array-data v1, :array_2a.local v1, "encryptedStringBytes":[Binvoke-static {}, Lorg/cf/nativeharness/Cryptor;->getInstance()Lorg/cf/nativeharness/Cryptor;move-result-object v0.line 21.local v0, "c":Lorg/cf/nativeharness/Cryptor;# v3 contains a String made from encrypted bytesnew-instance v3, Ljava/lang/String;invoke-direct {v3, v1}, Ljava/lang/String;-><init>([B)V# Call the decryption method, move result back to v3invoke-virtual {v0, v3}, Lorg/cf/nativeharness/Cryptor;->decryptString(Ljava/lang/String;)Ljava/lang/String;move-result-object v3
Building the Harness
I started with a tool called native-shim by Tim “diff” Strazzere (a fellow RedNaga member!) as a foundation for the harness. What shim does is load a library and call its JNI_OnLoad
. This makes debugging easy because you can just tell your debugger to start shim
and pass the path to the target library as an argument. Set your debugger to break on library load and you can step through the JNI_OnLoad
. Also, native-shim is great because it shows how to do almost everything you need to make harness work: load libraries (.so files), get references to functions, and call them.
First, I added code to initialize a Java VM instance and passed that instance to JNI_OnLoad
. This makes for a more realistic JNI initialization. Without a real VM instance, the internal state of the JNI library may be a little weird. It really depends on how JNI_OnLoad
is implemented by the particular library. It may not matter at all, but it’s common to check the JNI version from the little code I’ve seen, and to do that you need an instance of the VM.
12345678910111213
printf(" [+] Initializing JavaVM Instance\n");JavaVM *vm = NULL;JNIEnv *env = NULL;int status = init_jvm(&vm, &env);if (status == 0) {printf(" [+] Initialization success (vm=%p, env=%p)\n", vm, env);} else {printf(" [!] Initialization failure (%i)\n", status);return -1;}printf(" [+] Calling JNI_OnLoad\n");onLoadFunc(vm, NULL);
Tim, just let me know if you want this in native-shim and I’ll send a pull request.
Eventually, the goal was to make the harness open a socket server, read arguments over the socket, and call the function with those arguments. This way, the decryption function just becomes a service, and a Python script could easily interface with it.
Understand the Target Function
To call a function you need the function signature and the return type. To get this, let’s look at a decompilation oforg.cf.nativeharness.Cryptor
which declares the decryptString
native method.
1234567891011121314
public class Cryptor {private static Cryptor instance = null;public static Cryptor getInstance() {if (instance == null) {instance = new Cryptor();}return instance;}public native String decryptString(String encryptedString);}
From this code, you can see the method takes a String
and returns a String
. Seems simple. Let’s convert that to a native function method signature.
1
Java_org_cf_nativeharness_Cryptor_decryptString(JNIEnv *env, jstring encryptedString)
Every JNI native method needs JNIEnv
as the first argument. This means the typedef for our function should be:
1
typedef jstring(*decryptString_t)(JNIEnv *, jstring);
Unfortunately, if you try executing this function using the above typedef, you’ll get a cryptic error message:
123
E/dalvikvm: JNI ERROR (app bug): attempt to use stale local reference 0x1E/dalvikvm: VM abortingA/libc: Fatal signal 6 (SIGABRT) at 0x00000a9a (code=-6), thread 2714 (harness)
This confused me for a while. I thought maybe I was getting a null reference somewhere so I added lots of printf
s to show the memory locations of all the relevant pointers. The error really sounds like there’s something wrong with one of the arguments, but all of the pointers looked good, none were null.
I had the idea to be extra super double sure I got the method signature right. Maybe there’s some JNI boiler plate I was forgetting? To do this, I used javah
which generates C header and source files that are needed to implement native methods.
To do this, you’ll need dex2jar installed and on your class path and you need to change platforms/android-19
to point to whichever platform you have installed.
123
$ d2j-dex2jar.sh app-universal-debug.apkdex2jar app-universal-debug.apk -> ./app-universal-debug-dex2jar.jar$ javah -cp app-universal-debug-dex2jar.jar:$ANDROID_SDK/platforms/android-19/android.jar org.cf.nativeharness.Cryptor
This creates _org_cf_nativeharness_Cryptor.h_ which contains:
12
JNIEXPORT jstring JNICALL Java_org_cf_nativeharness_Cryptor_decryptString(JNIEnv *, jobject, jstring);
This has jobject
as the second argument. WHY? What gives? If already know the answer to this, I bet you’ve spent a lot of time looking at Smali, specifically invoke-virtual
. Whenever you call a virtual method (i.e. usually anything non-static), the first argument is an instance of the object which implements the method. In this case, the first argument should be an instance of org.cf.nativeharness.Cryptor
.
Of course, you could cheat and just look at str-crypt.c to find the signature but if you’re really reverse engineering or pen-testing, you won’t have the source.
The real function typedef should have a jobject
for the Cryptor
instance as the first argument:
1
typedef jstring(*decryptString_t)(JNIEnv *, jobject, jstring);
You may be wondering why the method is not static to begin with. There isn’t a good reason for it to be static, true. But in the original app which made me write this blog, the target method wasn’t static and I ran into this problem.
The lesson here is if you’re not sure what the signature is, try javah
and keep in mind virtual methods take an instance for the first argument, similar to Java’s Method#invoke()
).
Building the Socket Server
This is the least interesting part of harness. If you don’t mind, I’m just going to skip this. You can see the code for yourself. Also, I’m just a C tourist. If you think the code is shit, I believe you. But if you want to tell me it’s shit, it must come with a pull request.
Using the Harness
Here’s an overview of the steps required to test the harness:
- start an emulator
- push the harness to the device
- push target native library and any dependencies to the device (in this case, there are no dependencies)
- push the native harness target app to the device
- start the harness
- forward ports from the emulator to the host
- run _decrypt_string.py_ and cross your fingers
To push the app and native library to the device:
123456
$ adb push app/build/output/apk/app-universal-debug.apk /data/local/tmp/target-app.apk$ unzip app/build/outputs/apk/app-universal-debug.apk lib/x86/libstr-crypt.soArchive: app/build/outputs/apk/app-universal-debug.apkinflating: lib/x86/libstr-crypt.so$ adb push lib/x86/libstr-crypt.so /data/local/tmplib/x86/libstr-crypt.so: 1 file pushed. 1.5 MB/s (5476 bytes in 0.004s)
To push harness to the device,
12
cd harnessmake && make install
Note: this pushes the x86 library to the device. If you really want to use another emulator image, replace make install
with adb push libs/<your emulator flavor>/harness /data/local/tmp
.
Now, run harness
with the path to the target library as the first argument:
123456789101112131415
$ adb shell /data/local/tmp/harness /data/local/tmp/libstr-crypt.so[*] Native Harness[+] Loading target: [ /data/local/tmp/libstr-crypt.so ][+] Library Loaded![+] Found JNI_OnLoad, good[+] Initializing JavaVM InstanceWARNING: linker: libdvm.so has text relocations. This is wasting memory and is a security risk. Please fix.[+] Initialization success (vm=0xb8e420a0, env=0xb8e420e0)[+] Calling JNI_OnLoad[+] Found decryptString function, good (0xb761f4f0)[+] Finding Cryptor class[+] Found Cryptor class: 0x1d2001d9[+] Found Cryptor.getInstance(): 0xb27bc270[+] Instantiated Cryptor class: 0x1d2001dd[+] Starting socket server on port 5001
To test that it’s all working, in another terminal run:
123
$ ./decrypt_string.pySending encrypted stringDecrypted string: "Seek freedom and become captive of your desires. Seek discipline and find your liberty."
Finally, bask in your own greatness if you catch the reference, nerd.
Conclusion
You should be able to take the harness code and modify the target function to run whatever function you want. This won’t always work 100% reliably because programs are arbitrarily complicated and can do all kinds of weird shit.
原文地址: http://calebfenton.github.io/2017/04/14/calling_jni_functions_with_java_object_arguments_from_the_command_line/
- Calling JNI Functions with Java Object Arguments from the Command Line
- Bind JNI functions with a Java object
- Calling Java Class Methods from C with JNI
- Running scripts from the command line with idascript
- 【翻译】【技术】Java入门:Command-Line Arguments
- Running the Compiler from the Command Line
- GDB Command Line Arguments
- 关于Command-line Arguments
- Ant command line arguments
- exec Command-line Arguments
- B. Command Line Arguments
- Command-Line Arguments
- IDL command line arguments
- UE4 Command-Line Arguments
- Command-Line Arguments
- go command-line-arguments
- 1: Command Line Python(Challenge: Working with the Command Line)
- Creating a website from the command line
- Java WEB 中常用的弹出窗口
- 水经注万能地图下载器功能简介(最新版)
- json得到数据在jsp页面实现分页
- 1759:最长上升子序列
- service mysql start 报错总结【全】
- Calling JNI Functions with Java Object Arguments from the Command Line
- mysql监控工具
- redis-bgrewriteaof问题--持久化恢复问题
- LimeSDR USB实现实时 GPS信号欺骗
- 面试中常见的SQL优化问题
- 针对工程中使用了各平台的sdk及引入第三方平台的整体sdk包冲突的解决方法
- Linux 下部署常用命令
- 嵌入式面试C语言函数相关(待续)
- js 多线程