mirror of
https://github.com/feicong/rom-course.git
synced 2025-11-05 10:23:32 +00:00
第12章内容优化
This commit is contained in:
parent
d029dcfa87
commit
3656255ad2
@ -1,18 +1,22 @@
|
||||
# 第十二章 逆向实战
|
||||
|
||||
通过本书前文,对`Android`系统循循渐进的讲解,从基础到编译,再到内置,分析源码,添加独有的功能。在最后一章中,将结合前文中学习的知识,进行一个案例的实战,在实战的过程中,首先要对需求进行分析,然后将需要实现的目标进行拆分,最后对其逐一实现,最后组合并测试效果。在实现的过程中,最重要的是思考实现的思路和方向,然后再追寻源码,去寻找入手点。
|
||||
本书内容的展开,从安卓源码编译再到系统功能内置,从源码分析到独有功能添加,无一不是遵循由简入繁,原理与实践相结合,因为笔者始终认为这才是系统化学习知识的最佳方案。
|
||||
|
||||
在本书最后一章中,将结合前文所学知识进行一个案例实战。在实战过程中,首先需要对需求进行分析,并将需要实现的目标进行拆分。然后逐一实现这些目标,并测试其效果。在实现的过程中,思考实现的思路和方向是至关重要的。随后可以深入追寻源码,在其中寻找切入点以便更好地完成任务。
|
||||
|
||||
|
||||
## 12.1 案例实战
|
||||
|
||||
`JniTrace`是一个逆向分析中常用的基于`frida`实现的工具,在`native`函数调用时,`c++`代码可以通过`JNI`来访问`Java`中的类,类成员以及函数,而`JniTrace`工具主要负责对所有`JNI`的函数调用进行监控,输出所有调用的`JNI`函数,传递的参数,以及其返回值。
|
||||
`JniTrace`是一个常用的逆向分析工具,基于`frida`实现。在调用本地函数时,C++代码可以通过JNI访问Java类、成员和函数。而`JniTrace`工具主要用于监控所有JNI函数调用,并输出这些函数的调用、传递参数和返回值。
|
||||
|
||||
但是由于`frida`过于知名,导致大多数情况下,开发者们都会对其进行检测,所以在使用时常常会面临各种反制手段导致无法进行下一步的分析。为了能够躲避检测并继续使用`JniTrace`,逆向人员将其迁移到了更隐蔽的类`Xposed`框架中(例如`LSPosed`)。
|
||||
然而,由于`frida`过于知名,为了防止软件被调试与分析,导致大多数情况下恶意软件开发者与互联网业务软件厂商会对其进行检测。因此,在使用时经常面临各种反制手段使得无法继续分析。为了躲避检测并继续使用`JniTrace`, 逆向人员将其迁移到更隐蔽的框架中(如LSPosed)。
|
||||
|
||||
而对比`Hook`的方案来说,从`AOSP`中修改,则完全不存在有`Hook`的痕迹,但是相对而言,开发也更沉重一些,因为需要对系统有一定的理解,并且需要重复的编译系统来进行测试。
|
||||
相比Hook方案,从AOSP中进行修改则完全没有Hook痕迹存在。但相应地需要更深入地理解系统,并重复编译系统以进行测试。
|
||||
|
||||
在这一章的实战中,将讲解如何从`AOSP`的角度完成`JniTrace`这样的功能,并且使用配置进行管理,让其仅对目标进程生效,仅对目标`native`函数生效。
|
||||
在本章的实战中,将讲解如何从AOSP角度完成类似JniTrace功能,并使用配置管理使其仅对目标进程和本地函数生效。
|
||||
|
||||
在前文中提到了处理`RegisterNative`输出时的问题:它会在运行时对所有进程监控,并生成大量的运行时日志输出。为优化这一点,在处理时可以通过配置获取当前进程是否为目标进程来选择性打印日志信息。同样的优化方法也适用于这个例子中的配置管理。
|
||||
|
||||
在前文讲解`RegisterNative`的输出时,注意到当时的处理将会对所有的进程生效,导致输出过于庞大,在,优化的处理也是从一个配置中,获取到当前进程是否为目标进程,才进行对应的打桩输出。在这个例子中的配置管理同样适用该优化。
|
||||
|
||||
## 12.2 需求
|
||||
|
||||
@ -26,19 +30,14 @@
|
||||
pip install jnitrace
|
||||
```
|
||||
|
||||
由于该工具是基于`frida`实现的,需要在手机中运行`frida-server`,在地址`https://github.com/frida/frida/releases`中下载`frida-server`,开发环境是`AOSP12`的情况直接下载`16`任意版本即可。然后将其推送到手机的`/data/local/tmp`目录中,并运行。具体命令如下。
|
||||
由于该工具是基于`frida`实现的,需要在手机中运行`frida-server`,在地址`https://github.com/frida/frida/releases`中下载`frida-server`,开发环境是`AOSP` 12的情况直接下载`16`任意版本即可。然后将其推送到手机的`/data/local/tmp`目录中,并运行。具体命令如下。
|
||||
|
||||
```
|
||||
adb push ./frida-server-16.1.1-android-arm64 /data/local/tmp
|
||||
|
||||
adb forward tcp:27042 tcp:27042
|
||||
|
||||
adb shell
|
||||
|
||||
su
|
||||
|
||||
cd /data/local/tmp
|
||||
|
||||
chmod +x ./frida-server-16.1.1-android-arm64
|
||||
|
||||
// 为防止出现错误,先将selinux关闭
|
||||
@ -513,7 +512,7 @@ JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in, std::string* error_msg)
|
||||
}
|
||||
```
|
||||
|
||||
继续分析`GetFunctionTable`实现。
|
||||
继续分析`GetFunctionTable`的实现。
|
||||
|
||||
```c++
|
||||
const JNINativeInterface* JNIEnvExt::GetFunctionTable(bool check_jni) {
|
||||
@ -525,7 +524,7 @@ const JNINativeInterface* JNIEnvExt::GetFunctionTable(bool check_jni) {
|
||||
}
|
||||
```
|
||||
|
||||
继续进入查看`GetJniNativeInterface`实现逻辑
|
||||
继续进入查看`GetJniNativeInterface`的实现逻辑。
|
||||
|
||||
```c++
|
||||
const JNINativeInterface* GetJniNativeInterface() {
|
||||
@ -562,7 +561,7 @@ struct JniNativeInterfaceFunctions {
|
||||
static jobject CallObjectMethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list args) {
|
||||
CHECK_NON_NULL_ARGUMENT(obj);
|
||||
CHECK_NON_NULL_ARGUMENT(mid);
|
||||
ALOGD("mikrom %s",__FUNCTION__);
|
||||
ALOGD("jnitrace %s",__FUNCTION__);
|
||||
ScopedObjectAccess soa(env);
|
||||
JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, args));
|
||||
return soa.AddLocalReference<jobject>(result.GetL());
|
||||
@ -582,7 +581,7 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& ,
|
||||
return;
|
||||
}
|
||||
// 打桩信息
|
||||
ALOGD("mikrom ShowVarArgs %s %s %s %s %p",runtime->GetProcessPackageName().c_str(),runtime->GetConfigItem().jniModuleName,
|
||||
ALOGD("jnitrace ShowVarArgs %s %s %s %s %p",runtime->GetProcessPackageName().c_str(),runtime->GetConfigItem().jniModuleName,
|
||||
runtime->GetConfigItem().jniFuncName,funcname,Thread::Current());
|
||||
|
||||
}
|
||||
@ -606,7 +605,7 @@ extern uint32_t JniMethodStart(Thread* self) {
|
||||
// 当前开始函数为要监控的目标函数时,则开启输出JNI
|
||||
if(strstr(methodname.c_str(),runtime->GetConfigItem().jniFuncName)){
|
||||
runtime->GetConfigItem().jniEnable=true;
|
||||
ALOGD("mikrom enter jni %s",methodname.c_str());
|
||||
ALOGD("jnitrace enter jni %s",methodname.c_str());
|
||||
}
|
||||
}
|
||||
//endadd
|
||||
@ -629,7 +628,7 @@ extern void JniMethodEnd(uint32_t saved_local_ref_cookie, Thread* self) {
|
||||
// 当前结束函数为要监控的目标函数时,则关闭输出JNI
|
||||
if(strstr(methodname.c_str(),runtime->GetConfigItem().jniFuncName)){
|
||||
runtime->GetConfigItem().jniEnable=false;
|
||||
ALOGD("mikrom leave jni %s",methodname.c_str());
|
||||
ALOGD("jnitrace leave jni %s",methodname.c_str());
|
||||
}
|
||||
}
|
||||
//endadd
|
||||
@ -650,7 +649,7 @@ static mirror::Object* JniMethodEndWithReferenceHandleResult(jobject result,
|
||||
std::string methodname=native_method->PrettyMethod();
|
||||
if(strstr(methodname.c_str(),runtime->GetConfigItem().jniFuncName)){
|
||||
runtime->GetConfigItem().jniEnable=false;
|
||||
ALOGD("mikrom leave jni %s",methodname.c_str());
|
||||
ALOGD("jnitrace leave jni %s",methodname.c_str());
|
||||
}
|
||||
}
|
||||
//endadd
|
||||
@ -658,13 +657,15 @@ static mirror::Object* JniMethodEndWithReferenceHandleResult(jobject result,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 12.5 打桩函数分类
|
||||
|
||||
前文中仅仅对其中类似`CallObjectMethodV`函数,进行简单的输出,而实际场景中,大量的`JNI`函数调用,并非有着这些参数,所以需要将`ShowVarArgs`进行封装,并有多种重载实现。具体重载参数需要根据实际`JNI`函数中有哪些参数来决定。在这里篇幅有限,所以不会将所有的`JNI`函数情况进行处理,主要将前文中测试中调用到的`JNI`函数进行对应处理。
|
||||
|
||||
根据`JniTrace`中的日志,需要对四个`JNI`函数进行打桩处理,分别是`GetMethodID、GetStringUTFChars、NewStringUTF、CallObjectMethodV`。根据这些函数对应的参数,对打桩的函数进行重载处理。
|
||||
|
||||
### 12.5.1 GetMethodID打桩
|
||||
|
||||
### 12.5.1 GetMethodID插桩
|
||||
|
||||
在开始修改代码前,先看看该函数的定义。
|
||||
|
||||
@ -714,14 +715,14 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,const char* funcna
|
||||
ArtMethod* method = jni::DecodeArtMethod(methodID);
|
||||
pid_t pid = getpid();
|
||||
// 前面加上标志是为了方便搜索日志
|
||||
ALOGD("%s /* TID %d */","mikrom",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","mikrom",funcname);
|
||||
ALOGD("%s |- jclass :%s","mikrom",className);
|
||||
ALOGD("%s |- char* :%p","mikrom",name);
|
||||
ALOGD("%s |: %s","mikrom",name);
|
||||
ALOGD("%s |- char* :%p","mikrom",sig);
|
||||
ALOGD("%s |: %s","mikrom",sig);
|
||||
ALOGD("%s |= jmethodID :0x%x {%s}","mikrom",method->GetMethodIndex(),method->PrettyMethod().c_str());
|
||||
ALOGD("%s /* TID %d */","jnitrace",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","jnitrace",funcname);
|
||||
ALOGD("%s |- jclass :%s","jnitrace",className);
|
||||
ALOGD("%s |- char* :%p","jnitrace",name);
|
||||
ALOGD("%s |: %s","jnitrace",name);
|
||||
ALOGD("%s |- char* :%p","jnitrace",sig);
|
||||
ALOGD("%s |: %s","jnitrace",sig);
|
||||
ALOGD("%s |= jmethodID :0x%x {%s}","jnitrace",method->GetMethodIndex(),method->PrettyMethod().c_str());
|
||||
}
|
||||
```
|
||||
|
||||
@ -739,7 +740,8 @@ static jmethodID GetMethodID(JNIEnv* env, jclass java_class, const char* name, c
|
||||
}
|
||||
```
|
||||
|
||||
### 12.5.2 GetStringUTFChars打桩
|
||||
|
||||
### 12.5.2 GetStringUTFChars插桩
|
||||
|
||||
参考上面的流程,首先了解该函数的定义结构。
|
||||
|
||||
@ -773,14 +775,14 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& ,
|
||||
return;
|
||||
}
|
||||
pid_t pid = getpid();
|
||||
ALOGD("%s /* TID %d */","mikrom",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","mikrom",funcname);
|
||||
ALOGD("%s /* TID %d */","jnitrace",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","jnitrace",funcname);
|
||||
if(is_copy== nullptr){
|
||||
ALOGD("%s |- jboolean* : %d","mikrom",false);
|
||||
ALOGD("%s |- jboolean* : %d","jnitrace",false);
|
||||
}else{
|
||||
ALOGD("%s |- jboolean* : %d","mikrom",*is_copy);
|
||||
ALOGD("%s |- jboolean* : %d","jnitrace",*is_copy);
|
||||
}
|
||||
ALOGD("%s |= char* : %s","mikrom",data);
|
||||
ALOGD("%s |= char* : %s","jnitrace",data);
|
||||
}
|
||||
```
|
||||
|
||||
@ -794,9 +796,10 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& ,
|
||||
ShowVarArgs(soa,__FUNCTION__,is_copy,bytes);
|
||||
return bytes;
|
||||
}
|
||||
```
|
||||
``
|
||||
`
|
||||
|
||||
### 12.5.3 NewStringUTF打桩
|
||||
### 12.5.3 NewStringUTF插桩
|
||||
|
||||
该函数同样非常简单,和上一个函数相反,只需要将参数打印即可,无需处理返回值,函数定义如下。
|
||||
|
||||
@ -825,9 +828,9 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& ,
|
||||
return;
|
||||
}
|
||||
pid_t pid = getpid();
|
||||
ALOGD("%s /* TID %d */","mikrom",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","mikrom",funcname);
|
||||
ALOGD("%s |- char* : %d","mikrom",data);
|
||||
ALOGD("%s /* TID %d */","jnitrace",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","jnitrace",funcname);
|
||||
ALOGD("%s |- char* : %d","jnitrace",data);
|
||||
}
|
||||
```
|
||||
|
||||
@ -845,7 +848,8 @@ static jstring NewStringUTF(JNIEnv* env, const char* utf) {
|
||||
}
|
||||
```
|
||||
|
||||
### 12.5.4 CallObjectMethodV打桩
|
||||
|
||||
### 12.5.4 CallObjectMethodV插桩
|
||||
|
||||
这个`JNI`函数不同于前面几种函数,在前几个函数中,参数是明确固定的,而`CallObjectMethodV`是通过`JNI`,调用一个`java`函数,而为此`java`函数提供的所有参数的类型,以及参数个数。都是未知的。而这些参数的信息同样是需要打桩展示出来的。
|
||||
|
||||
@ -912,10 +916,10 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
|
||||
|
||||
ArtMethod* method = jni::DecodeArtMethod(mid);
|
||||
pid_t pid = getpid();
|
||||
ALOGD("%s /* TID %d */","mikrom",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","mikrom",funcname);
|
||||
ALOGD("%s |- jmethodID :0x%x {%s}","mikrom",method->GetMethodIndex(),method->PrettyMethod().c_str());
|
||||
ALOGD("%s |- va_list :%p","mikrom",&vaList);
|
||||
ALOGD("%s /* TID %d */","jnitrace",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","jnitrace",funcname);
|
||||
ALOGD("%s |- jmethodID :0x%x {%s}","jnitrace",method->GetMethodIndex(),method->PrettyMethod().c_str());
|
||||
ALOGD("%s |- va_list :%p","jnitrace",&vaList);
|
||||
|
||||
uint32_t shorty_len = 0;
|
||||
const char* shorty =
|
||||
@ -943,11 +947,11 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
|
||||
ObjPtr<mirror::Class> cls=receiver->GetClass();
|
||||
if (cls->DescriptorEquals("Ljava/lang/String;")){
|
||||
ObjPtr<mirror::String> retStr =soa.Decode<mirror::String>(ret);
|
||||
ALOGD("%s |= jstring :%s","mikrom",(const char*)retStr->GetValue());
|
||||
ALOGD("%s |= jstring :%s","jnitrace",(const char*)retStr->GetValue());
|
||||
}else{
|
||||
std::string temp;
|
||||
const char* className= cls->GetDescriptor(&temp);
|
||||
ALOGD("%s |= jobject :%p {%s}","mikrom",&ret,className);
|
||||
ALOGD("%s |= jobject :%p {%s}","jnitrace",&ret,className);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -966,32 +970,32 @@ void VarArgsShowArg(const ScopedObjectAccessAlreadyRunnable& soa,
|
||||
case 'C':
|
||||
case 'S':
|
||||
case 'I':
|
||||
ss<<"mikrom"<<" |: jint : "<<va_arg(ap, jint)<<"\n";
|
||||
ss<<"jnitrace"<<" |: jint : "<<va_arg(ap, jint)<<"\n";
|
||||
break;
|
||||
case 'F':
|
||||
ss<<"mikrom"<<" |: jfloat : "<<va_arg(ap, jdouble)<<"\n";
|
||||
ss<<"jnitrace"<<" |: jfloat : "<<va_arg(ap, jdouble)<<"\n";
|
||||
break;
|
||||
case 'L':{
|
||||
jobject obj=va_arg(ap, jobject);
|
||||
ObjPtr<mirror::Object> receiver =soa.Decode<mirror::Object>(obj);
|
||||
if(receiver==nullptr){
|
||||
ss<<"mikrom"<<" |: jobject : null\n";
|
||||
ss<<"jnitrace"<<" |: jobject : null\n";
|
||||
break;
|
||||
}
|
||||
ObjPtr<mirror::Class> cls=receiver->GetClass();
|
||||
if (cls->DescriptorEquals("Ljava/lang/String;")){
|
||||
ObjPtr<mirror::String> argStr =soa.Decode<mirror::String>(obj);
|
||||
ss<<"mikrom"<<" |: jstring : "<<(const char*)argStr->GetValue()<<"\n";
|
||||
ss<<"jnitrace"<<" |: jstring : "<<(const char*)argStr->GetValue()<<"\n";
|
||||
}else{
|
||||
ss<<"mikrom"<<" |: jobject : "<<&obj<<"\n";
|
||||
ss<<"jnitrace"<<" |: jobject : "<<&obj<<"\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'D':
|
||||
ss<<"mikrom"<<" |: jdouble : "<<va_arg(ap, jdouble)<<"\n";
|
||||
ss<<"jnitrace"<<" |: jdouble : "<<va_arg(ap, jdouble)<<"\n";
|
||||
break;
|
||||
case 'J':
|
||||
ss<<"mikrom"<<" |: jlong : "<<va_arg(ap, jlong)<<"\n";
|
||||
ss<<"jnitrace"<<" |: jlong : "<<va_arg(ap, jlong)<<"\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1002,40 +1006,42 @@ void VarArgsShowArg(const ScopedObjectAccessAlreadyRunnable& soa,
|
||||
到这里案例中使用的相关`JNI`函数处理就添加完成了,编译后刷入测试机。当点击样例中的按钮时,最后输出效果如下所示。
|
||||
|
||||
```
|
||||
mikrom enter jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI() 0x74656a77b0
|
||||
mikrom /* TID 5465 */
|
||||
mikrom [+] JNIEnv->GetMethodID
|
||||
mikrom |- jclass :Lcn/mik/nativedemo/MainActivity;
|
||||
mikrom |- char* :0x7275d69f1b
|
||||
mikrom |: demo
|
||||
mikrom |- char* :0x7275d69eef
|
||||
mikrom |: (IFLjava/lang/String;)Ljava/lang/String;
|
||||
mikrom |= jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
mikrom /* TID 5465 */
|
||||
mikrom [+] JNIEnv->NewStringUTF
|
||||
mikrom |- char* : newdemo
|
||||
mikrom /* TID 5465 */
|
||||
mikrom [+] JNIEnv->CallObjectMethodV
|
||||
mikrom |- jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
mikrom |- va_list :0x7fe0461bb0
|
||||
mikrom |: jint : 1
|
||||
mikrom |: jfloat : 2
|
||||
mikrom |: jstring : newdemo
|
||||
mikrom |= jstring :3.0newdemo
|
||||
mikrom /* TID 5465 */
|
||||
mikrom [+] JNIEnv->GetStringUTFChars
|
||||
mikrom |- jboolean* : 0
|
||||
mikrom |= char* : 3.0newdemo
|
||||
mikrom /* TID 5465 */
|
||||
mikrom [+] JNIEnv->NewStringUTF
|
||||
mikrom |- char* : 3.0newdemo
|
||||
mikrom leave jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI()
|
||||
jnitrace enter jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI() 0x74656a77b0
|
||||
jnitrace /* TID 5465 */
|
||||
jnitrace [+] JNIEnv->GetMethodID
|
||||
jnitrace |- jclass :Lcn/mik/nativedemo/MainActivity;
|
||||
jnitrace |- char* :0x7275d69f1b
|
||||
jnitrace |: demo
|
||||
jnitrace |- char* :0x7275d69eef
|
||||
jnitrace |: (IFLjava/lang/String;)Ljava/lang/String;
|
||||
jnitrace |= jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
jnitrace /* TID 5465 */
|
||||
jnitrace [+] JNIEnv->NewStringUTF
|
||||
jnitrace |- char* : newdemo
|
||||
jnitrace /* TID 5465 */
|
||||
jnitrace [+] JNIEnv->CallObjectMethodV
|
||||
jnitrace |- jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
jnitrace |- va_list :0x7fe0461bb0
|
||||
jnitrace |: jint : 1
|
||||
jnitrace |: jfloat : 2
|
||||
jnitrace |: jstring : newdemo
|
||||
jnitrace |= jstring :3.0newdemo
|
||||
jnitrace /* TID 5465 */
|
||||
jnitrace [+] JNIEnv->GetStringUTFChars
|
||||
jnitrace |- jboolean* : 0
|
||||
jnitrace |= char* : 3.0newdemo
|
||||
jnitrace /* TID 5465 */
|
||||
jnitrace [+] JNIEnv->NewStringUTF
|
||||
jnitrace |- char* : 3.0newdemo
|
||||
jnitrace leave jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI()
|
||||
```
|
||||
|
||||
|
||||
## 12.6 调用栈展示
|
||||
|
||||
经过调整后,打桩函数已经非常接近`JniTrace`的输出效果了,但是还有最后的一点区别是在于`JNI`函数的调用堆栈,不仅仅需要看到函数的参数和返回值,还需要知道是在哪里触发了该函数。而获取调用堆栈地址,在`AOSP`源码是有相关支持的,当应用崩溃时,在`logcat`中能看到详细的堆栈信息。
|
||||
|
||||
|
||||
### 12.6.1 xUnwind获取调用栈
|
||||
|
||||
`xUnwind`是一个开源工具,该工具将堆栈获取进行了封装,并且有简单的`demo`演示如何获取堆栈。下载地址:`https://github.com/hexhacking/xUnwind.git`。接下来将分析该工具是如何实现的获取堆栈,然后再将其内置到`AOSP`中,在`JNI`函数调用结束时获取堆栈进行输出。
|
||||
@ -1464,7 +1470,7 @@ const char* kbacktrace(bool with_context,const char* moduleName) {
|
||||
#include "kbacktrace.h"
|
||||
|
||||
|
||||
#define TAG "mikrom"
|
||||
#define TAG "jnitrace"
|
||||
#define LOG_PRIORITY ANDROID_LOG_INFO
|
||||
#define ALOGI(fmt, ...) __android_log_print(LOG_PRIORITY, TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
@ -1643,7 +1649,7 @@ libkbacktrace.so
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define TAG "mikrom"
|
||||
#define TAG "jnitrace"
|
||||
#define LOG_PRIORITY ANDROID_LOG_INFO
|
||||
#define ALOGI(fmt, ...) __android_log_print(LOG_PRIORITY, TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
@ -1722,12 +1728,12 @@ DexFile_initConfig(JNIEnv* env, jobject ,jobject item) {
|
||||
if(handle_kbacktrace!=nullptr){
|
||||
citem.kbacktrace= dlsym(handle_kbacktrace, "_Z10kbacktracebPKc");
|
||||
if(citem.kbacktrace==nullptr){
|
||||
ALOGD("mikrom kbacktrace is null.err:%s",dlerror());
|
||||
ALOGD("jnitrace kbacktrace is null.err:%s",dlerror());
|
||||
}else{
|
||||
ALOGD("mikrom kbacktrace:%p.",citem.kbacktrace);
|
||||
ALOGD("jnitrace kbacktrace:%p.",citem.kbacktrace);
|
||||
}
|
||||
}else{
|
||||
ALOGD("mikrom handle_kbacktrace is null.err:%s",dlerror());
|
||||
ALOGD("jnitrace handle_kbacktrace is null.err:%s",dlerror());
|
||||
}
|
||||
}
|
||||
runtime->SetConfigItem(citem);
|
||||
@ -1743,7 +1749,7 @@ typedef const char* (*kbacktraceFunc)(bool,const char*);
|
||||
const char* getBacktrace(const char* moduleName){
|
||||
Runtime* runtime=Runtime::Current();
|
||||
if(runtime->GetConfigItem().kbacktrace== nullptr){
|
||||
ALOGD("mikrom kbacktrace is null");
|
||||
ALOGD("jnitrace kbacktrace is null");
|
||||
return nullptr;
|
||||
}
|
||||
kbacktraceFunc kbacktrace=(kbacktraceFunc)runtime->GetConfigItem().kbacktrace;
|
||||
@ -1759,67 +1765,45 @@ void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,const char* funcna
|
||||
const char* className= c->GetDescriptor(&temp);
|
||||
ArtMethod* method = jni::DecodeArtMethod(methodID);
|
||||
pid_t pid = getpid();
|
||||
ALOGD("%s /* TID %d */","mikrom",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","mikrom",funcname);
|
||||
ALOGD("%s |- jclass :%s","mikrom",className);
|
||||
ALOGD("%s |- char* :%p","mikrom",name);
|
||||
ALOGD("%s |: %s","mikrom",name);
|
||||
ALOGD("%s |- char* :%p","mikrom",sig);
|
||||
ALOGD("%s |: %s","mikrom",sig);
|
||||
ALOGD("%s |= jmethodID :0x%x {%s}","mikrom",method->GetMethodIndex(),method->PrettyMethod().c_str());
|
||||
ALOGD("%s /* TID %d */","jnitrace",pid);
|
||||
ALOGD("%s [+] JNIEnv->%s","jnitrace",funcname);
|
||||
ALOGD("%s |- jclass :%s","jnitrace",className);
|
||||
ALOGD("%s |- char* :%p","jnitrace",name);
|
||||
ALOGD("%s |: %s","jnitrace",name);
|
||||
ALOGD("%s |- char* :%p","jnitrace",sig);
|
||||
ALOGD("%s |: %s","jnitrace",sig);
|
||||
ALOGD("%s |= jmethodID :0x%x {%s}","jnitrace",method->GetMethodIndex(),method->PrettyMethod().c_str());
|
||||
Runtime* runtime=Runtime::Current();
|
||||
const char* backtrace= getBacktrace(runtime->GetConfigItem().jniModuleName);
|
||||
ALOGD("-------------------------mikrom Backtrace-------------------------\n%s\n",backtrace);
|
||||
ALOGD("-------------------------Backtrace-------------------------\n%s\n",backtrace);
|
||||
}
|
||||
```
|
||||
|
||||
到这里`JniTrace`的`AOSP`版本就完成了,其他的函数调用参考前面的做法即可,最后优化后的日志输入如下。
|
||||
|
||||
```
|
||||
mikrom enter jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI() 0x7a0bc06010
|
||||
mikrom /* TID 5641 */
|
||||
mikrom [+] JNIEnv->GetMethodID
|
||||
mikrom |- jclass :Lcn/mik/nativedemo/MainActivity;
|
||||
mikrom |- char* :0x781eb6cf1b
|
||||
mikrom |: demo
|
||||
mikrom |- char* :0x781eb6ceef
|
||||
mikrom |: (IFLjava/lang/String;)Ljava/lang/String;
|
||||
mikrom |= jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
-------------------------mikrom Backtrace-------------------------
|
||||
jnitrace enter jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI() 0x7a0bc06010
|
||||
jnitrace /* TID 5641 */
|
||||
jnitrace [+] JNIEnv->GetMethodID
|
||||
jnitrace |- jclass :Lcn/mik/nativedemo/MainActivity;
|
||||
jnitrace |- char* :0x781eb6cf1b
|
||||
jnitrace |: demo
|
||||
jnitrace |- char* :0x781eb6ceef
|
||||
jnitrace |: (IFLjava/lang/String;)Ljava/lang/String;
|
||||
jnitrace |= jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
-------------------------Backtrace-------------------------
|
||||
#05 pc 000000000000e9dc /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (_ZN7_JNIEnv11GetMethodIDEP7_jclassPKcS3_+60)
|
||||
#06 pc 000000000000e888 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (Java_cn_mik_nativedemo_MainActivity_stringFromJNI+80)
|
||||
mikrom /* TID 5641 */
|
||||
mikrom [+] JNIEnv->NewStringUTF
|
||||
mikrom |- char* : newdemo
|
||||
-------------------------mikrom Backtrace-------------------------
|
||||
#05 pc 000000000000ea14 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (_ZN7_JNIEnv12NewStringUTFEPKc+44)
|
||||
#06 pc 000000000000e89c /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (Java_cn_mik_nativedemo_MainActivity_stringFromJNI+100)
|
||||
mikrom /* TID 5641 */
|
||||
mikrom [+] JNIEnv->CallObjectMethodV
|
||||
mikrom |- jmethodID :0x277 {java.lang.String cn.mik.nativedemo.MainActivity.demo(int, float, java.lang.String)}
|
||||
mikrom |- va_list :0x7ffb41b790
|
||||
mikrom |: jint : 1
|
||||
mikrom |: jfloat : 2
|
||||
mikrom |: jstring : newdemo
|
||||
mikrom |= jstring :3.0newdemo
|
||||
-------------------------mikrom Backtrace-------------------------
|
||||
#06 pc 000000000000eae4 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (_ZN7_JNIEnv16CallObjectMethodEP8_jobjectP10_jmethodIDz+196)
|
||||
#07 pc 000000000000e8bc /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (Java_cn_mik_nativedemo_MainActivity_stringFromJNI+132)
|
||||
mikrom /* TID 5641 */
|
||||
mikrom [+] JNIEnv->GetStringUTFChars
|
||||
mikrom |- jboolean* : 0
|
||||
mikrom |= char* : 3.0newdemo
|
||||
-------------------------mikrom Backtrace-------------------------
|
||||
#05 pc 000000000000eb54 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (_ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh+52)
|
||||
#06 pc 000000000000e8d0 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (Java_cn_mik_nativedemo_MainActivity_stringFromJNI+152)
|
||||
mikrom /* TID 5641 */
|
||||
mikrom [+] JNIEnv->NewStringUTF
|
||||
mikrom |- char* : 3.0newdemo
|
||||
-------------------------mikrom Backtrace-------------------------
|
||||
#05 pc 000000000000ea14 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (_ZN7_JNIEnv12NewStringUTFEPKc+44)
|
||||
#06 pc 000000000000e910 /data/app/~~MjwExmAtQBa8X1Xp3ifz_g==/cn.mik.nativedemo-YbmCkQ7SdhNqbL7iXfOeug==/lib/arm64/libnativedemo.so (Java_cn_mik_nativedemo_MainActivity_stringFromJNI+216)
|
||||
mikrom leave jni java.lang.String cn.mik.nativedemo.MainActivity.stringFromJNI()
|
||||
jnitrace /* TID 5641 */
|
||||
jnitrace [+] JNIEnv->NewStringUTF
|
||||
jnitrace |- char* : newdemo
|
||||
-------------------------Backtrace-------------------------
|
||||
......
|
||||
```
|
||||
|
||||
|
||||
## 12.7 本章小结
|
||||
|
||||
本章完整介绍了系统代码插桩方式来实现`Jnitrace`功能。该功能用于动态跟踪分析程序执行时的JNI调用参数与栈详情信息。对比python版本的`Jnitrace`工具,使用Frida代码编写的跟踪工具拥有着便捷与扩展性强等特点,但缺点是这种方式需要处理大量调用,对程序的并发处理能力要求极高,在一些JNI调用频繁的程序上执行分析,可能会出现并发崩溃的情况,使用系统代码插桩的优点是,在高并发调用下,跟踪代码仍然能正常的输出分析内容,具有较高的稳定性。
|
||||
|
||||
系统定制涉及到技术原理与代码解读的部分着实枯燥,很感谢读者朋友们能坚持看完本书。本书所有的内容到这里就要讲完了,然后,值得被扩展与定制的功能非常之多,限于篇幅限制,作者水平十分有限,很多功能没有在本书中涉及与讨论。有兴趣的朋友,可以关注微信公众号[软件安全与逆向分析],微信号:feicong_sec。作者会在上面讨论一些系统定制修改与安全技术相关的话题。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user