背景

看完本章,你将会学习到用ASM的tree api进行对匿名线程的hook操作,同时也能够了解到asm相关的操作和背景知识介绍!对于ASM插桩来说,可能很多人都不陌生了,但是大多数可能都停留在core api上,对于现在市面上的一些插桩库,其实很多都用tree api进行编写了,因为tree api的简单与明了的特性,也越来越成为许多开源库的选择。(ASM有两套api类型,分别是core 和 tree)

ASM介绍

ASM其实就是一个可以编译字节码的工具,比如说我们日常开发会引入很多的类库对不对,又或者说我们的项目太大了,想修改某个点的时候,统一修改容易出错(比如隐私合规问题等),这个时候如果能有一个工具对生成后的class文件进行编辑的话,就非常方便我们进行后续的工作了。

本章主要介绍tree api,下文所说的ASM都是指tree api的操作哦,对于core api的介绍可以查看笔者曾经写过的文章Spider。

class文件

我们常说的class文件,其实从二进制的角度出发,无非是分成以下几个部分:

可以看到,一个class文件其实就是由上图中的多个部分组成,而ASM,就是把这些结构进行了更进一步的抽象,对于class文件,其实就是抽象成asm中的class node类

对于一个class文件来说,通过以下就可以进行唯一性识别,分别是:version(版本),access(作用域,比如private等修饰符),name(名称),signature(泛型签名),superName(父类),interfaces(实现的接口),fields(当前的属性),methoss(当前的方法)。 所以如果想要修改一个class,我们修改对应的classNode即可

fields

属性,也是类非常重要的一部分,在字节码中,是如此定义的

对于一个属性,ASM将其抽象为FieldNode

对于一个属性field来说,通过以下就可以进行唯一性识别:access(作用域,跟class结构一样,比如private修饰),name(属性名称),desc(签名),signature(泛型签名),value(当前对应的数值)

methods

相比于属性,我们的方法结构更为复杂

相比于属性的单一,一个方法可能由多条指令组成而,一个方法的成功执行,也涉及到局部变量表跟操作数栈的配合。ASM中把方法抽象成这样一个定义 方法 = 方法头 方法体

方法头:即标识一个方法的基本属性,包括:access(作用域),name(方法名),desc(方法签名),signature(泛型签名),exceptions(方法可以抛出的异常)

方法体:相比于方法头,方法体的概念其实就比较简单了,其实方法体就是方法的各条指令的集合,主要包括instrutions(方法的指令集),tryCatchBlocks(异常的节点集),maxStack(操作数栈的最大深度),maxLocals(本地变量表的最大长度)

可以看到,方法其中的InsnList对象,是特指方法的指令集的抽象,这里继续讲解

InsnList

public class InsnList implements Iterable<AbstractInsnNode> {
    private int size;
    private AbstractInsnNode firstInsn;
    private AbstractInsnNode lastInsn;
    AbstractInsnNode[] cache;
    ...

可以看到,主要的对象就是firstInsn,与lastInsn,代表着方法指令集的头指令与尾指令,每一个指令其实都被抽象成了AbstractInsnNode的子类,AbstractInsnNode定义了一条指令最基础的信息,我们可以看看这个类的子类

这里我们再看看我们最常用的methodInsnNode

public class MethodInsnNode extends AbstractInsnNode {
  /**
   * The internal name of the method's owner class (see {@link
   * org.objectweb.asm.Type#getInternalName()}).
   *
   * <p>For methods of arrays, e.g., {@code clone()}, the array type descriptor.
   */
  public String owner;
  /** The method's name. */
  public String name;
  /** The method's descriptor (see {@link org.objectweb.asm.Type}). */
  public String desc;
  /** Whether the method's owner class if an interface. */
  public boolean itf;

这个就是一个普通方法指令最根本的定义了,owner(方法调用者),name(方法名称),desc(方法签名)等等,他们都有着相似的结构,这个也是我们接下来会实战的重点。

Signature

嗯!我们最后介绍一下这个神奇的东西!不知道大家在看介绍的时候,有没有一脸疑惑,这个我解释为泛型签名,这个跟desc(函数签名)参数有什么区别呢?当然,这个不仅仅在函数上有出现,在属性,类的结构上都有出现!是不是非常神奇!

其实Signature属性是在JDK 1.5发布后增加到了Class文件规范之中,它是一个可选的定长属性, 可以出现于类、属性表和方法表结构的属性表中。我们想想看,jdk1.5究竟是发生什么了!其实就是对泛型的支持,那么1.5版本之前的sdk怎么办,是不是也要进行兼容了!所以java标准组就想到了一个折中的方法,就是泛型擦除,泛型信息编译(类型变量、参数化类型)之后 都通通被擦除掉,以此来进行对前者的兼容。那么这又导致了一个问题,擦除的泛型信息有时候正是我们所需要的,所以Signature就出现了,把这些泛型信息存储在这里,以提供运行时反射等类型信息的获取!实际上可以看到,我们大部分的方法或者属性这个值都为null,只有存在泛型定义的时候,泛型的信息才会被存储在Signature里面

实战部分

好啦!有了理论基础,我们也该去实战一下,才不是口水文!以我们线程优化为例子,在工作项目中,或者在老项目中,可能存在大多数不规范的线程创建操作,比如直接new Thread等等,这样生成的线程名就会被赋予默认的名字,我们这里先把这类线程叫做“匿名线程”!当然!并不是说这个线程没有名字,而是线程名一般是“Thread -1 ”这种没有额外信息含量的名字,这样对我们后期的线程维护会带来很大的干扰,时间长了,可能就存在大多数这种匿名线程,有可能带来线程创建的oom crash!所以我们的目标是,给这些线程赋予“名字”,即调用者的名字

解决“匿名”Thread

为了达到这个目的,我们需要对thread的构造有一个了解,当然Thread的构造函数有很多,我们举几个例子

public Thread(String name) {
    init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

可以看到,我们Thread的多个构造函数,最后一个参数都是name,即Thread的名称,所以我们的hook点是,能不能在Thread的构造过程,调用到有name的构造函数是不是就可以实现我们的目的了!我们再看一下普通的new Thread()字节码

那么我们怎么才能把new Thread()的方式变成 new Thread(name)的方式呢?很简单!只需要我们把init的这条指令变成有参的方式就可以了,怎么改变呢?其实就是改变desc!方法签名即可,因为一个方法的调用,就是依据方法签名进行匹配的。我们在函数后面添加一个string的参数即可

node是methidInsnNode
def desc =
        "${node.desc.substring(0, r)}Ljava/lang/String;${node.desc.substring(r)}"
node.desc = desc

那么这样我们就可以完成了吗,非也非也,我们只是给方法签名对加了一个参数,但是这并不代表我们函数就是这么运行的!因为方法参数的参数列表中的string参数我们还没放入操作数栈呢!那么我们就可以构造一个string参数放入操作数栈中,这个指令就是ldc指令啦!asm为我们提供了一个类是LdcInsnNode,我们可以创建一个该类对象即可,构造参数需要传入一个字符串,那么这个就可以把当前方法的owner(解释如上,调用者名称)放进去了,是不是就达到我们想要的目的了!好啦!东西我们又了,我们要在哪里插入呢?

所以我们的目标很明确,就是在init指令调用前插入即可,asm也提供了insertBefore方法,提供在某个指令前插入的便捷操作。

method.instructions.insertBefore(
        node,
        new LdcInsnNode(klass.name)
)

我们看看最后插入后的字节码

当然,我们插入asm代码一般是在android提供给我们的Transform阶段进行的(agp新版有改变,但是大体工作流程一致),所以我们在transfrom中为了避免对类的过度干扰,我们还需要把不必要的阶段提早剔除!比如我们只在new Thread操作,那么就把非Opcodes.INVOKESPECIAL的操作过滤即可。还有就是非init阶段(即非构造函数阶段)或者owner不为Thread类就可以提前过滤,不参与更改即可。

那我们看到完整的代码(需要在Transform中执行的代码)

static void transform(ClassNode klass) {
    println("ThreadTransformUtils")
    // 这里只处理Thread
    klass.methods?.forEach { methodNode ->
        methodNode.instructions.each {
            // 如果是构造函数才继续进行
            if (it.opcode == Opcodes.INVOKESPECIAL) {
                transformInvokeSpecial((MethodInsnNode) it, klass, methodNode)
            }
        }
    }
}
private static void transformInvokeSpecial(MethodInsnNode node, ClassNode klass, MethodNode method) {
    // 如果不是构造函数,就直接退出
    if (node.name != "<init>" || node.owner != THREAD) {
        return
    }
    println("transformInvokeSpecial")
    transformThreadInvokeSpecial(node, klass, method)
}
private static void transformThreadInvokeSpecial(
        MethodInsnNode node,
        ClassNode klass,
        MethodNode method
) {
    switch (node.desc) {
    // Thread()
        case "()V":
            // Thread(Runnable)
        case "(Ljava/lang/Runnable;)V":
            method.instructions.insertBefore(
                    node,
                    new LdcInsnNode(klass.name)
            )
            def r = node.desc.lastIndexOf(')')
            def desc =
                    "${node.desc.substring(0, r)}Ljava/lang/String;${node.desc.substring(r)}"
            // println("   $SHADOW_THREAD.makeThreadName(Ljava/lang/String;Ljava/lang/String;) => ${this.owner}.${this.name}${this.desc}: ${klass.name}.${method.name}${method.desc}")
            println(" * ${node.owner}.${node.name}${node.desc} => ${node.owner}.${node.name}$desc: ${klass.name}.${method.name}${method.desc}")
            node.desc = desc
            break
    }
}

最后

看到这里,应该可以了解到asm tree api相关用法与实战了,希望能有所帮助!

以上就是ASM的tree api对匿名线程的hook操作详解的详细内容,更多关于ASM tree api匿名线程hook的资料请关注Devmax其它相关文章!

ASM的tree api对匿名线程的hook操作详解的更多相关文章

  1. HTML5之消息通知的使用(Web Notification)

    通知可以说是web中比较常见且重要的功能,私信、在线提问、或者一些在线即时通讯工具我们总是希望第一时间知道对方有了新的反馈。本篇文章主要介绍了HTML5之消息通知的使用(Web Notification),感兴趣的小伙伴们可以参考一下

  2. ios – 我可以使用哪些iPhone OS API来实现类似于iBook页面翻转过渡的过渡动画?

    >UIKitAPI中的某个地方是否可以使用该动画,还是我必须自己实现?它肯定有3D感觉,他们可以使用OpenGLESAPI吗?解决方法Apple当然使用OpenGLES来实现它.Apple使用的实际API是私有的,但thisblogger具有示例代码的实现的开始.

  3. iOS 7,用于断开调用的私有API CTCallDisconnect不起作用

    谢谢!

  4. 我应该使用哪个高级API来管理iOS上的UDP套接字?

    在“NetworkProgrammingTopicsConceptualGuide”的“UsingSocketsandStreams”一章中,Apple说:Note:POSIXnetworkingdoesnotactivatethecellularradiooniOS.Forthisreason,thePOSIXnetworkingAPIisgenerallydiscouragediniOS.同样

  5. 保护MY REST API仅用于MY IOS APP

    我在Laravel中设计一个RESTAPI,用于我的ios应用程序.目前我被困在以下几点:如何保护我的RESTAPI只允许访问我的ios应用程序?听起来我需要通过向我的IOSAPP授予一个私钥来将类似于HMAC方法的内容合并到我的IOSAPP代码中.当从iosapp中运行请求时,我传递带有私钥和其他数据的哈希,然后当在服务器上收到请求时,我通过重新计算哈希来检测请求是否来自应用程序内的用户.我不知道这是否安全&我会认为不是吗?

  6. ios – 尝试向我们分配IP而不是localhost或home时,NSURLSession失败

    我有一台本地运行的服务器(我的IP是192.168.0.98),并且已经尝试使用一些网络代码来访问它.最初这是通过AFNetworking完成的,但我现在用这样的NSURLSession完成了它:然后我用这3个URL运行它:>http://localhost:8080/api–>作品.>http://127.0.0.1:8080/api–>作品.>http://192.168.0.98:8080/

  7. 适用于iOS的Google云端硬盘实时API

    我想使用GoogleDrive和新的实时API在我的iOS应用中实现实时协作.我知道我可以在Objective-C中设置一个Web视图,并在Web视图和我的本机应用程序之间建立双向通信,因此使用javascript库,但我担心这对于高容量来说效率低下数据流量.我希望可能会有一个原生的解决方案即将出现.有关Objective-C的GoogleApi客户端库是否会更新以包含Google云端硬盘实时API的任何消息?

  8. ios – 如何通过iPhone中的Graph API在Facebook上“喜欢”和“评论”?

    我正在使用图形api显示新闻源.我对以下问题有疑问.>我想为每个新闻提要帖子提供“赞”功能.>我想为每个新闻提要帖子提供“评论”功能.有人可以帮助我如何使用iPhone中的图形API来解决这个问题.解决方法请参考我的答案:HowtocommentorlikeaphotoinfacebookthroughFBconnectorGraphAPIiniPhoneSDK?只需将您的访问令牌发送到https

  9. ios – 使用带有OAuth 2.0的Google API在iPhone中登录Gmail

    我找到了Google提供的服务,可以访问各种Google服务的GoogleApi.我可以在iPhone中设置一个项目,并为iOS应用程序和本机应用程序创建API访问.我想为我的iPhone应用程序使用本机API.它API为我提供了电子邮件,全名,名字,姓氏,google_id,性别,dob,profile_image.如何在我的iPhone应用程序,任何示例应用程序,可用的代码段中使用这些?

  10. ios – 如何使用YouTube API V3?

    我想知道如何在iOS应用中使用新的YouTubeAPI(第3版),但我不知道如何做.我做了很多关于它的研究,但是我发现所有的例子和老API的代码,所以它们是无效的.现在,我明白了,使用新的API你必须在Google开发者控制台中创建一个项目…使用API2很简单它…

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部