Shizuku 源码解读 (自动解锁手机抢购飞天茅台)解决遗留部分问题

4 篇文章 1 订阅
3 篇文章 0 订阅

每天定时抢购飞天茅台

原视频链接

背景

Shizuku 源码解读上文

  • 上次预留两个问题

      - 1. 新进程中的context 如何构建创造
    
      - 2. hidden api的实现原理
    

问题一 新进程中的context 如何构建创造

  • 反射构造客户端Service,构造使用context 原代码

    • public class UserService {
      
        
          @Nullable
          public static Pair<IBinder, String> create(String[] args) {
            
              Log.i(TAG, String.format("starting service %s/%s...", pkg, cls));
      
              IBinder service;
      
              try {
                  Context systemContext = ActivityThread.systemMain().getSystemContext();
      
                  DdmHandleAppName.setAppName(name != null ? name : pkg + ":user_service", 0);
      
                  //noinspection InstantiationOfUtilityClass
                  UserHandle userHandle = Refine.unsafeCast(
                          Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                                  ? UserHandleHidden.of(userId)
                                  : new UserHandleHidden(userId));
                  Context context = Refine.<ContextHidden>unsafeCast(systemContext).createPackageContextAsUser(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY, userHandle);
                  ClassLoader classLoader = context.getClassLoader();
                  Class<?> serviceClass = classLoader.loadClass(cls);
                  Constructor<?> constructorWithContext = null;
                  try {
                      constructorWithContext = serviceClass.getConstructor(Context.class);
                  } catch (NoSuchMethodException | SecurityException ignored) {
                  }
                  if (constructorWithContext != null) {
                      service = (IBinder) constructorWithContext.newInstance(context);
                  } else {
                      service = (IBinder) serviceClass.newInstance();
                  }
              } catch (Throwable tr) {
                  Log.w(TAG, String.format("unable to start service %s/%s...", pkg, cls), tr);
                  return null;
              }
      
              return new Pair<>(service, token);
          }
      }
      
  • Context systemContext = ActivityThread.systemMain().getSystemContext(); ActivityThread 作为隐藏API,为什么项目中可以使用到?

    • 自定义ActivityThread,包名类名和系统相同

      •   package android.app;
          
          public class ActivityThread {
          
              public static ActivityThread systemMain() {
                  throw new RuntimeException();
              }
          
              public static ActivityThread currentActivityThread() {
                  throw new RuntimeException();
              }
          
              public Application getApplication() {
                  throw new RuntimeException();
              }
          
              public ContextImpl getSystemContext() {
                  throw new RuntimeException();
              }
          }
        
    • 调用自定义的ActivityThread,这样运行不就抛出异常吗?为什么会顺利执行

    • 双亲委派机制

      •  private final ClassLoader parent;
         protected Class<?> loadClass(String name, boolean resolve)
        	        throws ClassNotFoundException {
        	            // First, check if the class has already been loaded
        	            Class<?> c = findLoadedClass(name);
        	            if (c == null) {
        	                try {
        	                    if (parent != null) {
        	                        c = parent.loadClass(name, false);
        	                    } else {
        	                        c = findBootstrapClassOrNull(name);
        	                    }
        	                } catch (ClassNotFoundException e) {
        	                    // ClassNotFoundException thrown if class not found
        	                    // from the non-null parent class loader
        	                }
        	                if (c == null) {
        	                    // If still not found, then invoke findClass in order
        	                    // to find the class.
        	                    c = findClass(name);
        	                }
        	            }
        	            return c;
        }
        
        1. findLoadedClass(name) 从缓存中获取Class对象,如果获取不到,就让parent.loadClass(name)找。
        • android 调用native方法从 class_table(哈希map)的缓存中查找

        • java中可能java层和native层都有缓存,Android只有native有缓存

        1. 递归此过程。直到顶层为BootClassLoader类型时,如果在缓存中无法获取到需要的class对象,就调用BootClassLoader.findClass(name)。
        1. 最后,如果parent获取不到class对象,就调用自己的findClass(name)寻找。
        • DexPathList -> dexElements这个数组就是一个Dex文件集合,而findClass就是遍历这个集合,然后从Dex文件中获取需要的字节码文件,然后使用字节码文件创建对应的Class对象,并将其缓存到class_table中。

        1. 如果findClass()方法仍然无法加载该类,那么ClassLoader会抛出ClassNotFoundException异常。
    • 根据双亲委派机制,loadClass时返回BootClassCloder中已经加载的系统ActivityThread类,那我们自己实验一下,

      • apk类加载器的父类是否是BootClassCloder?

        • ClassLoader classLoader = MainActivity.class.getClassLoader();
          while (classLoader != null) {
              Log.d("gzp", classLoader.getClass().getCanonicalName());
              classLoader = classLoader.getParent();
          }
          
          在这里插入图片描述
      • 自己替换一下系统类,demo验证一下,

        • package android.os;
          
          import android.util.Log;
          
          public class ServiceManager {
          
              public static IBinder getService(String name) {
                  Log.d("gzp", "custom getService");
                  throw new RuntimeException("STUB");
              }
          
              public static void addService(String name, IBinder service) {
                  Log.d("gzp", "custom addService");
                  throw new RuntimeException("STUB");
              }
          
          }
          
          public class MainActivity extends HomeActivity {
              @Override
              protected void onCreate(@Nullable Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
               
                  IBinder iBinder = ServiceManager.getService("activity");
                  Log.d("gzp", "custom" + iBinder);
              }
          }
          
          
          在这里插入图片描述
          果然没有输出custom getService日志,并且返回的是一个对象
  • 是不是所有的类都可以自定义替换呢

    • 当然不是,如果替换View类,你会发现编译报错;android jar 包含的类替换有可能编译失败;导入类时,优先导入android jar的类

问题二 hidden api的实现原理

  • hidden api的实现原理 Context context = Refine.<ContextHidden>unsafeCast(systemContext).createPackageContextAsUser();

  • HiddenApiRefinePlugin 查看源码

    • gradle 插件,在编译(compileDebugJavaWithJavac/compileRelaseJavaWithJavac)之后 将android.content.ContextHidden重新命名为 android.content.Context

      •  class RefineRemapper extends Remapper {
            
         
             @Override
             public String map(final String typeName) {
                 final ClassData data = context.loadClassData(typeName.replace('/', '.') + "$" + RefineProcessor.REFINE_METADATA_CLASS_NAME);
                 if (data == null) {
                     return typeName;
                 }
         
                 if (data.getClassAnnotations().contains(RefineProcessor.Descriptor.class.getName())) {
                     final String to = data.getClassAnnotations().stream()
                             .filter(a -> a.startsWith(RefineProcessor.REFINE_NS_PACKAGE))
                             .findFirst()
                             .orElseThrow(() -> new UnsupportedOperationException("Use deprecated refine class " + data.getClassName()));
         
                     return to.substring(RefineProcessor.REFINE_NS_PACKAGE.length() + 1).replace('.', '/');
                 }
         
                 return typeName;
             }
         
         }
        
    • 过程:

        1. 编译时调用自定义ContextHidden.createPackageContextAsUser 保证编译通过,不会因为类找不到或者方法找不到而抛出编译异常;
        1. 编译之后,重新命名(android.content.ContextHidden重新命名为 android.content.Context)
        1. 加载类时,根据双亲委派机制,findClass时,返回BootClassCloder中的系统android.content.Context
        1. 运行调用系统的android.content.Context
  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值