前言
Android P 对非 SDK API 的使用做了限制, 导致在 Android P 上会出现一些状况. 在很早前预览版本刚出来的时候, 360 团队就出了两篇文章. Android P 调用隐藏 API 限制原理 https://mp.weixin.qq.com/s/sktB0x5yBexkn4ORQ1YofA 以及 突破 Android P(Preview 1) 对调用隐藏 API 限制的方法 https://mp.weixin.qq.com/s/4k3DBlxlSO2xNNKqjqUdaQ
限制方式三种:
反射
直接调用
jni 调用
这一篇文章就是根据上面的文章来的.
方法一 (不建议)
使用 Provided(CompileOnly) 的方式去解决调用限制, 只能解决反射调用的问题, 而无法解决直接调用或者 jni 调用的方式. 不建议使用
方法二 (不建议)
这个方法二对应的是 360 文章中的方法三. 主要代码如下.
- class ObjPtr {
- public:
- uintptr_t reference_;
- };
- ObjPtr
- (*sys_GetDeclaredMethodInternal)(void *self, jobject kclass, jstring name, jobjectArray args);
- void *(*executableGetArtMethod)(void *ex);
- ObjPtr myGetDeclaredMethodInternal(void *self, jobject kclass, jstring name, jobjectArray args) {
- ObjPtr res = sys_GetDeclaredMethodInternal(self, kclass, name, args);
- if (res.reference_ != 0) {
- void *pMethod = executableGetArtMethod((void *) (res.reference_));
- reinterpret_cast<uint32_t *>(pMethod)[1] &= 0xcfffffff;
- }
- return res;
- }
- extern "C" int hookForPMethod() {
- void *libc = fake_dlopen("/system/lib/libart.so", RTLD_NOW);
- if (libc != NULL) {
- void *p = fake_dlsym(libc, "_ZN3art6mirror5Class25GetDeclaredMethodInternalILNS_11Poin"
- "terSizeE4ELb0EEENS_6ObjPtrINS0_6MethodEEEPNS_6ThreadENS4_IS1_EENS4_INS0_6StringEEEN"
- "S4_INS0_11ObjectArrayIS1_EEEE");
- if (p != NULL) {
- MSHookFunction(p,
- reinterpret_cast<void *>(myGetDeclaredMethodInternal),
- reinterpret_cast<void **>(&sys_GetDeclaredMethodInternal));
- }
- *(void **) (&executableGetArtMethod) =
- fake_dlsym(libc, "_ZN3art6mirror10Executable12GetArtMethodEv");
- fake_dlclose(libc);
- } //if
- return 1;
- }
其中, fake_dlopen,fake_dlsym 使用的是 https://github.com/Guolei1130/Nougat_dlfunctions , 主要是 Android 7.0 以上对 dlopen,dlsym 等函数做了限制. 因此用这个库. 而 MSHookFunction, 则是大名鼎鼎的 http://www.cydiasubstrate.com/ .
上面的代码只解决了反射方法的问题. 我按照这种思路去解决字段问题的时候发现.
GetDeclaredField 是 inline 的, 无法切入. 而 CreateFromArtField 又是 hidden 的, 也不好切入.
因此, 放弃了这种方法.
方法三 (可用, 但是有更好的)
这里对应的方法三, 对应的是 360 文章中的方法二, 也就是修改 classloader 的方式. 代码如下.
- void (*setClassLoader)(void *pClass, void *new_cl);
- ObjPtr (*toClass)(jclass global_jclss);
- extern "C" void makeHiddenApiAccessable(JNIEnv *env) {
- void *libart = fake_dlopen("/system/lib/libart.so", RTLD_NOW);
- if (libart != NULL) {
- *(void **) (&toClass) = fake_dlsym(libart, "_ZN3art16WellKnownClasses7ToClassEP7_jclass");
- *(void **) (&setClassLoader) =
- fake_dlsym(libart, "_ZN3art6mirror5Class14SetClassLoaderENS_6ObjPtrINS0_11ClassLoaderEEE");
- jclass cls = env->FindClass("com/example/support_p/ReflectionHelper");
- ObjPtr op = toClass(cls);
- setClassLoader((void *) op.reference_, NULL);
- }
- }
没错, 代码就是这么点. 这样, 我们就可以在 ReflectionHelper 中调用非公开 API 了. 但是这里会依赖 Nougat_dlfunctions 这个库.
方法四 (超级好)
既然是修改 classloader, 那么我们为什么不在 java 层修改呢. 代码如下.
- private void testJavaPojie() {
- try {
- Class reflectionHelperClz = Class.forName("com.example.support_p.ReflectionHelper");
- Class classClz = Class.class;
- Field classLoaderField = classClz.getDeclaredField("classLoader");
- classLoaderField.setAccessible(true);
- classLoaderField.set(reflectionHelperClz, null);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
而这里用的相关反射只是 light 级别的, 没有什么影响. 反而代码量超小, 也不依赖其他.
方法五 (超级好 + 1)
这个方案来自 @区长 大神
方法四还是存在一点问题. 如果以后把 classloader 加入到深灰或者黑名单, 那就僵硬了. 所以, 我们不用反射, 直接用 unsafe 去修改. 代码这就不贴了. 为了得到 classloader 的偏移量, 我们写一个和 Class 结构一样的类, 用这个类得到的 classLoader 的偏移量和 Class 是一样的.
注意:
如果我们用修改 ClassLoader 的方式的话, 那么 ReflectionHelper 类中只能反射调用非公开 API, 注意了.
代码在这里, 觉得好的给个 star 吧
来源: https://juejin.im/post/5ba0f3f7e51d450e6f2e39e0