Android 应用程序:java / JNI 调用挂钩策略
Android app : java / JNI call hooking strategies
我的目标是检测 AOSP,以便动态记录来自目标应用程序的所有 java 或 JNI 调用,有或没有参数和 return 值。我不想修改应用程序,这就是我要修改 Android 源代码的原因。我对 AOSP 及其大量的库和框架不是很有经验,所以我正在寻找建议,因为我不知道从哪里开始。此外,由于可能记录的行数,该过程必须高效(即我不相信必须为每个挂钩方法实现挂钩 class 的类似调试的方法可以工作)
到目前为止我的理解:
使用相对较新的 ART 系统,它将 DEX 应用程序源代码编译成一种机器可执行代码(OAT?),与 Dalvik 相比,它的仪器更复杂。
执行流程:已编译的 java 应用字节码(取决于已编译的 Android API)+ libs.so -> DVM -> forked Zygote VM -> 应用程序的执行。
如果我尝试挂钩根 (Android API + libs.so) 它将需要大量的工作来挂钩每个调用。理想情况是所有 java 呼叫都通过的地方。 ART 有这样的地方吗?
AOSP源码比较难懂,因为好像没有文档说明每个源文件在全局架构中的作用。那么在哪里挂接电话比较好?
编辑(s)
这个主题没有很好地涵盖,所以我会为感兴趣的人展示信息。
我的研究发现了这个博客:http://blog.csdn.net/l173864930/article/details/45035521。 (+Google 翻译)
谁链接到这个有趣的 Java 和 ELF (arm) 调用挂钩项目:https://github.com/boyliang/AllHookInOne
这不是我想要的,但我会尝试实现一个适合我需要的动态分析的 AOSP 补丁。
我已经成功回答了我的问题。
对于我从源代码中可以理解的内容,java 调用有 3 个可能的入口点:
- ArtMethod::调用 (art/runtime/mirror/art_method.cc)
- 执行 (art/runtime/interpreter/interpreter.cc)
- DoCall (art/runtime/interpreter/interpreter_common.cc)
ArtMethod::Invoke 似乎用于反射和直接调用带有指向 OAT 代码部分的指针的方法。 (同样,没有文档,它可能不准确)。
执行结束一般调用DoCall。
ART 的一些优化使得 Java 调用的研究变得困难,例如方法内联和直接偏移地址调用。
第一步是禁用这些优化:
在 device/brand-name/model/device.mk 中(在我的例子中 device/lge/hammerhead/device.mk 用于 nexus 5):
将选项 "interpret-only" 添加到 dex2oat。使用此选项,ART 仅编译引导类路径,因此不会在 OAT 中编译应用程序。
PRODUCT_PROPERTY_OVERRIDES := \
dalvik.vm.dex2oat-filter=interpret-only
第二步是在art/compiler/dex/frontend.cc中禁用内联:
取消注释 "kSuppressMethodInlining"。
/* Default optimizer/debug setting for the compiler. */
static uint32_t kCompilerOptimizerDisableFlags = 0 | // Disable specific optimizations
(1 << kLoadStoreElimination) |
// (1 << kLoadHoisting) |
// (1 << kSuppressLoads) |
// (1 << kNullCheckElimination) |
// (1 << kClassInitCheckElimination) |
(1 << kGlobalValueNumbering) |
// (1 << kPromoteRegs) |
// (1 << kTrackLiveTemps) |
// (1 << kSafeOptimizations) |
// (1 << kBBOpt) |
// (1 << kMatch) |
// (1 << kPromoteCompilerTemps) |
// (1 << kSuppressExceptionEdges) |
(1 << kSuppressMethodInlining) |
0;
最后一步是在 art/compiler/driver/compiler_driver.cc 中禁用直接代码偏移调用:
-bool use_dex_cache = GetCompilerOptions().GetCompilePic();
+bool use_dex_cache = true;
通过这些更改,所有不同的调用都将落入 DoCall 函数中,我们最终可以在其中添加目标日志记录例程。
在art/runtime/interpreter/interpreter_common.h中,在include开头添加:
#ifdef HAVE_ANDROID_OS
#include "cutils/properties.h"
#endif
在art/runtime/interpreter/interpreter_common.cc中,在DoCall函数的开头添加:
#ifdef HAVE_ANDROID_OS
char targetAppVar[92];
property_get("target.app.pid", targetAppVar, "0");
int targetAppPID = atoi(targetAppVar);
if(targetAppPID != 0 && targetAppPID == getpid())
LOG(INFO) << "DoCall - " << PrettyMethod(method, true);
#endif
为了定位应用程序,我使用 属性 来设置目标 pid。
为此,我们需要 lib system/core/libcutils 并且此 lib 仅在 AOSP 编译为真正的 phone 时可用(不会弄乱当前的 makefile)。
因此该解决方案不适用于模拟器。 (只是猜测,从未尝试过 编辑:确认,"cutils/properties.h" 无法添加到模拟器的构建中。
编译并刷入补丁的AOSP后,启动一个应用程序,ps | grep 用于查找 PID 并在 root 中设置 属性 :
shell@android:/ # ps | grep contacts
u0_a2 4278 129 1234668 47356 ffffffff 401e8318 S com.android.contacts
shell@android:/ # setprop target.app.pid 4278
shell@android:/ # logcat
[...]
I/art ( 4278): DoCall - int android.view.View.getId()
I/art ( 4278): DoCall - void com.android.contacts.activities.PeopleActivity$ContactsUnavailableFragmentListener.onCreateNewContactAction()
I/art ( 4278): DoCall - void android.content.Intent.<init>(java.lang.String, android.net.Uri)
I/art ( 4278): DoCall - void android.app.Activity.startActivity(android.content.Intent)
I/ActivityManager( 498): START u0 {act=android.intent.action.INSERT dat=content://com.android.contacts/contacts cmp=com.android.contacts/.activities.ContactEditorActivity} from uid 10002 on display 0
V/WindowManager( 498): addAppToken: AppWindowToken{3a82282b token=Token{dc3f87a ActivityRecord{c0aaca5 u0 com.android.contacts/.activities.ContactEditorActivity t4}}} to stack=1 task=4 at 1
I/art ( 4278): DoCall - void android.app.Fragment.onPause()
I/art ( 4278): DoCall - void com.android.contacts.common.list.ContactEntryListFragment.removePendingDirectorySearchRequests()
I/art ( 4278): DoCall - void android.os.Handler.removeMessages(int)
I/art ( 4278): DoCall - void com.android.contacts.list.ProviderStatusWatcher.stop()
I/art ( 4278): DoCall - boolean com.android.contacts.list.ProviderStatusWatcher.isStarted()
I/art ( 4278): DoCall - void android.os.Handler.removeCallbacks(java.lang.Runnable)
I/art ( 4278): DoCall - android.content.ContentResolver com.android.contacts.ContactsActivity.getContentResolver()
I/art ( 4278): DoCall - void android.content.ContentResolver.unregisterContentObserver(android.database.ContentObserver)
I/art ( 4278): DoCall - void android.app.Activity.onPause()
I/art ( 4278): DoCall - void android.view.ViewGroup.drawableStateChanged()
I/art ( 4278): DoCall - void com.android.contacts.ContactsActivity.<init>()
I/art ( 4278): DoCall - void com.android.contacts.common.activity.TransactionSafeActivity.<init>()
I/art ( 4278): DoCall - void android.app.Activity.<init>()
I/art ( 4278): DoCall - void com.android.contacts.util.DialogManager.<init>(android.app.Activity)
I/art ( 4278): DoCall - void java.lang.Object.<init>()
[...]
结束时:
shell@android:/ # setprop target.app.pid 0
瞧!
从用户的角度来看,过载并不明显,但 logcat 会很快被填满。
PS : 文件路径和名称匹配 Android 5 版本 (Lollipop),它们可能与高级版本不同。
PS' :如果有人想打印方法的参数,我建议它查看 art/runtime/utils.cc 的 PrettyArguments 方法,并在某处找到一些实际的实现代码。
也许你可以从 Xposed 项目中得到更多的想法,它通过禁用方法内联和直接分支优化来遵循相同的方法:
https://github.com/rovo89/android_art/commit/0f807a6561201230962f77a46120a53d3caa12c2
https://github.com/rovo89/android_art/commit/92e8c8e0309c4a584f4279c478d54d8ce036ee59
我的目标是检测 AOSP,以便动态记录来自目标应用程序的所有 java 或 JNI 调用,有或没有参数和 return 值。我不想修改应用程序,这就是我要修改 Android 源代码的原因。我对 AOSP 及其大量的库和框架不是很有经验,所以我正在寻找建议,因为我不知道从哪里开始。此外,由于可能记录的行数,该过程必须高效(即我不相信必须为每个挂钩方法实现挂钩 class 的类似调试的方法可以工作)
到目前为止我的理解:
使用相对较新的 ART 系统,它将 DEX 应用程序源代码编译成一种机器可执行代码(OAT?),与 Dalvik 相比,它的仪器更复杂。
执行流程:已编译的 java 应用字节码(取决于已编译的 Android API)+ libs.so -> DVM -> forked Zygote VM -> 应用程序的执行。
如果我尝试挂钩根 (Android API + libs.so) 它将需要大量的工作来挂钩每个调用。理想情况是所有 java 呼叫都通过的地方。 ART 有这样的地方吗?
AOSP源码比较难懂,因为好像没有文档说明每个源文件在全局架构中的作用。那么在哪里挂接电话比较好?
编辑(s)
这个主题没有很好地涵盖,所以我会为感兴趣的人展示信息。
我的研究发现了这个博客:http://blog.csdn.net/l173864930/article/details/45035521。 (+Google 翻译) 谁链接到这个有趣的 Java 和 ELF (arm) 调用挂钩项目:https://github.com/boyliang/AllHookInOne
这不是我想要的,但我会尝试实现一个适合我需要的动态分析的 AOSP 补丁。
我已经成功回答了我的问题。 对于我从源代码中可以理解的内容,java 调用有 3 个可能的入口点:
- ArtMethod::调用 (art/runtime/mirror/art_method.cc)
- 执行 (art/runtime/interpreter/interpreter.cc)
- DoCall (art/runtime/interpreter/interpreter_common.cc)
ArtMethod::Invoke 似乎用于反射和直接调用带有指向 OAT 代码部分的指针的方法。 (同样,没有文档,它可能不准确)。
执行结束一般调用DoCall。
ART 的一些优化使得 Java 调用的研究变得困难,例如方法内联和直接偏移地址调用。
第一步是禁用这些优化:
在 device/brand-name/model/device.mk 中(在我的例子中 device/lge/hammerhead/device.mk 用于 nexus 5):
将选项 "interpret-only" 添加到 dex2oat。使用此选项,ART 仅编译引导类路径,因此不会在 OAT 中编译应用程序。
PRODUCT_PROPERTY_OVERRIDES := \
dalvik.vm.dex2oat-filter=interpret-only
第二步是在art/compiler/dex/frontend.cc中禁用内联:
取消注释 "kSuppressMethodInlining"。
/* Default optimizer/debug setting for the compiler. */
static uint32_t kCompilerOptimizerDisableFlags = 0 | // Disable specific optimizations
(1 << kLoadStoreElimination) |
// (1 << kLoadHoisting) |
// (1 << kSuppressLoads) |
// (1 << kNullCheckElimination) |
// (1 << kClassInitCheckElimination) |
(1 << kGlobalValueNumbering) |
// (1 << kPromoteRegs) |
// (1 << kTrackLiveTemps) |
// (1 << kSafeOptimizations) |
// (1 << kBBOpt) |
// (1 << kMatch) |
// (1 << kPromoteCompilerTemps) |
// (1 << kSuppressExceptionEdges) |
(1 << kSuppressMethodInlining) |
0;
最后一步是在 art/compiler/driver/compiler_driver.cc 中禁用直接代码偏移调用:
-bool use_dex_cache = GetCompilerOptions().GetCompilePic();
+bool use_dex_cache = true;
通过这些更改,所有不同的调用都将落入 DoCall 函数中,我们最终可以在其中添加目标日志记录例程。
在art/runtime/interpreter/interpreter_common.h中,在include开头添加:
#ifdef HAVE_ANDROID_OS
#include "cutils/properties.h"
#endif
在art/runtime/interpreter/interpreter_common.cc中,在DoCall函数的开头添加:
#ifdef HAVE_ANDROID_OS
char targetAppVar[92];
property_get("target.app.pid", targetAppVar, "0");
int targetAppPID = atoi(targetAppVar);
if(targetAppPID != 0 && targetAppPID == getpid())
LOG(INFO) << "DoCall - " << PrettyMethod(method, true);
#endif
为了定位应用程序,我使用 属性 来设置目标 pid。
为此,我们需要 lib system/core/libcutils 并且此 lib 仅在 AOSP 编译为真正的 phone 时可用(不会弄乱当前的 makefile)。
因此该解决方案不适用于模拟器。 (只是猜测,从未尝试过 编辑:确认,"cutils/properties.h" 无法添加到模拟器的构建中。
编译并刷入补丁的AOSP后,启动一个应用程序,ps | grep 用于查找 PID 并在 root 中设置 属性 :
shell@android:/ # ps | grep contacts
u0_a2 4278 129 1234668 47356 ffffffff 401e8318 S com.android.contacts
shell@android:/ # setprop target.app.pid 4278
shell@android:/ # logcat
[...]
I/art ( 4278): DoCall - int android.view.View.getId()
I/art ( 4278): DoCall - void com.android.contacts.activities.PeopleActivity$ContactsUnavailableFragmentListener.onCreateNewContactAction()
I/art ( 4278): DoCall - void android.content.Intent.<init>(java.lang.String, android.net.Uri)
I/art ( 4278): DoCall - void android.app.Activity.startActivity(android.content.Intent)
I/ActivityManager( 498): START u0 {act=android.intent.action.INSERT dat=content://com.android.contacts/contacts cmp=com.android.contacts/.activities.ContactEditorActivity} from uid 10002 on display 0
V/WindowManager( 498): addAppToken: AppWindowToken{3a82282b token=Token{dc3f87a ActivityRecord{c0aaca5 u0 com.android.contacts/.activities.ContactEditorActivity t4}}} to stack=1 task=4 at 1
I/art ( 4278): DoCall - void android.app.Fragment.onPause()
I/art ( 4278): DoCall - void com.android.contacts.common.list.ContactEntryListFragment.removePendingDirectorySearchRequests()
I/art ( 4278): DoCall - void android.os.Handler.removeMessages(int)
I/art ( 4278): DoCall - void com.android.contacts.list.ProviderStatusWatcher.stop()
I/art ( 4278): DoCall - boolean com.android.contacts.list.ProviderStatusWatcher.isStarted()
I/art ( 4278): DoCall - void android.os.Handler.removeCallbacks(java.lang.Runnable)
I/art ( 4278): DoCall - android.content.ContentResolver com.android.contacts.ContactsActivity.getContentResolver()
I/art ( 4278): DoCall - void android.content.ContentResolver.unregisterContentObserver(android.database.ContentObserver)
I/art ( 4278): DoCall - void android.app.Activity.onPause()
I/art ( 4278): DoCall - void android.view.ViewGroup.drawableStateChanged()
I/art ( 4278): DoCall - void com.android.contacts.ContactsActivity.<init>()
I/art ( 4278): DoCall - void com.android.contacts.common.activity.TransactionSafeActivity.<init>()
I/art ( 4278): DoCall - void android.app.Activity.<init>()
I/art ( 4278): DoCall - void com.android.contacts.util.DialogManager.<init>(android.app.Activity)
I/art ( 4278): DoCall - void java.lang.Object.<init>()
[...]
结束时:
shell@android:/ # setprop target.app.pid 0
瞧!
从用户的角度来看,过载并不明显,但 logcat 会很快被填满。
PS : 文件路径和名称匹配 Android 5 版本 (Lollipop),它们可能与高级版本不同。
PS' :如果有人想打印方法的参数,我建议它查看 art/runtime/utils.cc 的 PrettyArguments 方法,并在某处找到一些实际的实现代码。
也许你可以从 Xposed 项目中得到更多的想法,它通过禁用方法内联和直接分支优化来遵循相同的方法:
https://github.com/rovo89/android_art/commit/0f807a6561201230962f77a46120a53d3caa12c2
https://github.com/rovo89/android_art/commit/92e8c8e0309c4a584f4279c478d54d8ce036ee59