写个代码扫描插件,再也不怕 log4j 等问题

news2024/11/17 9:55:35

引言

关于静态代码扫描,大家想必都非常熟悉了,比如 lintdetekt 等,这些也都是常用的扫描工具。但随着隐私合规在国内越来越趋于常态,我们经常需要考虑某些危险api的调用排查等等,此时上述的工具往往不容易实现现有的需求,以及后续扩展。而在这个背景下,ASM 就是解决方式的最佳手段之一。

故此,本篇我们将通过写一个代码扫描插件,从而简单玩转并入门 ASM :)

Github:Bee-AnalysisPlugin

背景

记得在前司(下厨房)的时候,我们 App 曾被报出存在漏洞问题,具体原因是:

项目中使用了log4j等api,导致存在安全漏洞。

其实当听到这个问题的时候,总感觉略有点离谱,客户端怎么会存在这个问题?

在我的印象中,log4j 似乎是21年时的一个广泛问题,当然主要影响是后端同学,团队内部也还排查过。但因为客户端和这系列库离的相对就比较远了,所以对于客户端的我们没有在意(为后面埋了伏笔)。

所以当真正收到相关部门邮件时,我们先是不相信,然后和另一个同学(化名z)开始着手排查:

结果还真是狠狠打脸了,项目历史代码中存在使用 HttpURLConnection 导致,而 HttpURLConnection 内部又引入了 Log4j 系列库,从而导致相关问题,于是就立即开始分工处理:

  • z负责写代码扫描插件,全量扫项目,从而确保已经完全移除相关api;
  • 我负责对代码层进行处理,对涉及到相关的 HttpURLConnection 逻辑进行移除与逻辑调整;

最终在收到问题的当天晚上就提了PR流程,总耗时大概3小时,也算是比较迅速。


事后来看, 虽然问题解决了,但同时也暴漏出了一些问题,比如 客户端代码 没有相关 危险代码扫描机制 ,导致这部分隐患一直处于黑盒状态。而从技术角度来思考,实现这个check也非常简单。

如下所示:

  • 定义一份线上的漏洞表(定期更新),每次 CI 时拉取最新的;
  • 定义一个代码扫描插件,每次 PR commit 时进行自动触发,并拉取最新的漏洞表,如果项目中存在相关漏洞,则中断本次打包并通知;

聊聊需求

通过上面的背景,我们大概也能知道本篇的缘由以及一些应用场景,所以如果要从练习角度入手,做一个代码扫描插件,其目的是静态扫描出相关方法的调用次数以及具体调用者,从而便于我们进行排查,应该怎么做?

此时可能会有同学抢答,我直接使用 Android Studio 全局搜索也行啊,为什么还需要专门写个插件扫描呢? 🤔

直接使用AS也能实现类似的需求,但是如果我们需要找出所有相关的调用处,这并不是一件易事,特别是对于复杂的项目而言(当然你要是没事愿意一个一个🔍,那另说了😑)。

而如果使用 ASM ,上述的需求实现起来就比较简单,而且后续的扩展也会相对成本较低,甚至我们还可以做一个调用替换等等,当然这些都是后话。

基础入门

为避免部分同学不太理解 ASM ,故这里选择先简单聊聊 ASM 基础背景,也算科普了(逃跑~)。

什么是ASM?

Java ASM(Java Bytecode Assembler)是一个用于 生成 和 修改 Java字节码的库。ASM 提供了一种灵活而强大的方式来分析、转换和生成Java类文件。使用 ASM ,我们可以在 不改变源代码 的情况下,通过操纵字节码来实现对代码的定制化需求。这种能力在许多领域中都有应用,包括 编译器 、代码优化字节码工具AOP(面向切面编程)框架等。

ASM与AGP关系

回到 Android 中,我们知道 Android虚拟机 是基于 Dalvik(5.0是ART),而 Dalvik 也是属于 JVM虚拟机 的一种。所以Android的开发语言是 Java (Kotlin会由编译器转为Java),而我们 Java 代码编译后的 class 文件为了便于 Dalvik 识别,故最终还需要转为dex 文件。

整个过程如下所示:

java -> class -> dex

常用的 AGP(Android Gradle Plugin) 插件,就是在 class -> dex 前,为开发者提供了一个时机,允许我们进行二次修改 Class ,从而实现自定义的需求,这也即是 ASM 在 AGP 中的作用由来。

ASM常见API
  • ClassReader

    负责对 Class 进行读写,最终调用 accpet 加载 class,由 ClassVisitor 开始进行处理;

  • ClassVisitor

    负责对读取到的 Class 进行操作,比如对 class 中某一部分信息(方法、属性等)进行修改;

ASM基础操作

总结起来通常就是三步:

  • 读取class,创建 ClassReader
  • 进行修改,创建 ClassVisitor(通常是ClassWriter+其他);
  • 保存结果,ClassWriter.toByteArray() ;

伪代码如下:

val cr = ClassReader(classStream)
val cw = ClassWriter(cr, 0)
val cv = xxxClassVisitor(cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
FileOutputStream(outClassPath).use {
     it.write(cw.toByteArray())
}

ClassVisitor 提供了很多方法,比如当方法被调用时(visitMethod),开发者可以根据需求重写相应的方法,从而在 class 访问过程中,实现 class 修改。当然这些都只是最基础的操作,实际使用时我们还会使用其他更多的一些 Api ,由于本文并不是全面介绍相关 Api 的文章,故这部分就留给读者自行探索了:)

具体思路

要扫描代码,肯定是要先写一个 Plugin ,然后注册一个 Transform ,并在其中其中读取所有 class 与 jar ,从而对其进行处理。具体过程中,如果存在我们指定的方法调用,我们就将当前调用类的位置或者方法保存,最后当 ASM 处理结束后,我们再对结果进行处理。

不过需要注意的是 Transform 在 AGP7.0 已经被标注了 废弃AGP8.0 也已经正式 移除 ,所以我们要实现上述的逻辑,还是需要做一些改动。

故我们选用的是 AndroidComponentsExtension 来进行实现,这个 API 是Android团队专门针对 ASM 做的一个 hook 时机。不过需要注意的是,其并不像 Transform,我们可以 拿到所有class以及jar直接进行处理,而是当某个 class 被处理时,我们可以有时机进行拦截并处理。故如果我们想确保收集完所有信息,就必须在相应的 Task 之后再进行汇总处理,比如在 transformxxClassesWithAsm 之后。

实现效果

我们以检测业务中 PrintStream 类的调用为例,最终实现效果如下所示:

image.png

如上图所示,业务中一共有三处使用 PrintStream 类,分别调用的都是其 print() 以及 println() 方法。

当然对于结果的处理,无论是以文件形式保存还是其他方式,都是由我们自行处理,这里只是将其打印出来。

具体流程

示例Github: Bee-AnalysisPlugin

插件配置

作为开始,我们需要定义一个自己的插件类,需要继承自 Plugin 类,具体代码如下所示:

image.png

上述的流程我们分为3步:

  1. 创建我们的扩展实例(用于传递配置参数);
  2. 注册 AsmClassVisitor ,用于访问字节码;
  3. 当字节码处理完成后,统计处理结果;

具体的配置扩展类: RuleExtension

open class RuleExtension {
 var classPackages: Array<String> = emptyArray()
 var enableLog: Boolean = false
}

注意:这里需要增加open,否则编译失败;

ASM配置

在 AGP 7.0 之后,我们自定义的 ASM 访问器,需要继承自 AsmClassVisitorFactory ,并需要传入一个 InstrumentationParameters 泛型,用于确定是否需要实例化参数,因为我们需要对每个变体进行处理,所以这里传入 buildType 作为分类。当然如果并不需要传参的话,这里的工厂泛型可以直接传入 InstrumentationParameters.None ;

image.png

 

image.png

上述的流程如下:

我们定义了一个 字节码工厂访问器,并规定只处理非 Androidx 以及 R. 相关的 class,这样当字节码在处理时,如果当前class满足条件,就会触发 createClassVisitor() 方法,从而我们就可以创建自己的 字节码访问类,并使用这个处理类对当前字节码进行修改。

当我们在读取 class 时,内部会对相关的方法、构造函数、属性等等都进行一次遍历或者调用,同时也会触发相关的回调方法,在这些回调方法里,也有对应的访问器进行处理,整体类似一个树形结构。

比如当访问 class 中的方法时,此时会调用 visitMethod() 方法,而我们本篇是希望遍历所有方法,所以需要重写该方法,并返回我们自己的方法访问器(MethodVisitor);

相应的,在具体的 MethodVisitor 里,当这个方法内部去访问其他方法时,或者访问其他对象时等,也都会再次回调相关方法。故此,我们只需要在其访问其他方法时,将其保存到我们自己池子中,从而就可以得到如下信息:

当前类、当前方法、被访问的类、被访问的方法等

而根据这些信息,我们就可以清晰的得知我们自己需要拦截的方法被谁调用了,调用了多少次,调用位置等等。

检测逻辑

具体的检测逻辑就比较简单了,我们只需要定义一个静态处理类,其内部持有一个 Map 结构的结果集(key 为变体名、value为结果集),而具体的判断规则可以存在一个Set或者List中。比如我们示例中只需要判断是否存在指定包或者类的调用,那么只需要传入 packages 即可,如果有更多的规则,比如方法等等,则可以根据逻辑进行更改。

image.png

具体逻辑如上,其中 filterAndAddMethod() 是每次当访问到相关方法时调用,如果满足条件,则将其信息缓存起来;当ASM处理完成后,也就是 transformXXXClassesWithAsm 之后,我们再调用 end() 去统计,从而按照当前 buildType 输出结果。

当然,当拿到结果后,怎么处理那都是题外话题了,比如可以直接打印,或者存储到文件里,也可以抛出异常等等,这些就留给大家自行决断吧。

使用方式

具体的使用方式,比较简单,我们直接在 application 所在的 build.gradle 添加下面的配置语句即可。

//示例
analysis {
    classPackages = ["java.io.PrintStream"]
}

示例Github: Bee-AnalysisPlugin

总结

本篇到这里就结束了,严格而言,本篇其实算不上什么ASM高深技巧,只能算的上是基础操作。更多是希望,通过本篇,能使得新手同学对于 ASM 基础使用有一个了解,特别是在 AGP7.0 之后的打开方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1817984.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP开发的爱情盲盒交友系统网站源码

源码介绍 PHP开发的爱情盲盒交友系统网站源码 独立后台 源码截图 源码下载 PHP开发的爱情盲盒交友系统网站源码

TCPListen客户端和TCPListen服务器

创建项目 TCPListen服务器 public Form1() {InitializeComponent();//TcpListener 搭建tcp服务器的类&#xff0c;基于socket套接字通信的//1创建服务器对象TcpListener server new TcpListener(IPAddress.Parse("192.168.107.83"), 3000);//2 开启服务器 设置最大…

Kali Linux 2022.2 发布,包含 10 个新工具和WSL 改进

Offensive Security发布了Kali Linux 2022.2&#xff0c;这是2022年的第二个版本&#xff0c;具有桌面增强功能&#xff0c;有趣的愚人节屏幕保护程序&#xff0c;WSL GUI改进&#xff0c;终端调整&#xff0c;最重要的是&#xff0c;新的工具&#xff01; Kali Linux是一个Li…

Python | Leetcode Python题解之第148题排序链表

题目&#xff1a; 题解&#xff1a; class Solution:def sortList(self, head: ListNode) -> ListNode:def merge(head1: ListNode, head2: ListNode) -> ListNode:dummyHead ListNode(0)temp, temp1, temp2 dummyHead, head1, head2while temp1 and temp2:if temp1.v…

Django中使用下拉列表过滤HTML表格数据

在Django中&#xff0c;你可以使用下拉列表&#xff08;即选择框&#xff09;来过滤HTML表格中的数据。这通常涉及两个主要步骤&#xff1a;创建过滤表单和处理过滤逻辑。 创建过滤表单 首先&#xff0c;你需要创建一个表单&#xff0c;用于接收用户选择的过滤条件。这个表单可…

集合java

1.集合 ArrayList 集合和数组的优势对比&#xff1a; 长度可变 添加数据的时候不需要考虑索引&#xff0c;默认将数据添加到末尾 package com.itheima;import java.util.ArrayList;/*public boolean add(要添加的元素) | 将指定的元素追加到此集合的末尾 | | p…

E-R数据模型是什么?

概念模型是从现实世界到计算机世界转换的一个中间层次,在数据库设计的过程中它是比较关键的一步。因此,概念模型必须能够真实地反映现实世界中被管理事物的特征及其复杂的联系,即应该具有丰富的语义表达能力和直接模拟现实世界的能力,且具有直观、自然、语义丰富、易于用户…

「51媒体」媒体邀约-全国邀请媒体现场报道宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 「51媒体」媒体邀约是一家专注于提供媒体传播方案和执行的服务公司&#xff0c;旨在通过一站式服务帮助企业或个人进行有效的媒体邀约和活动宣传。 「51媒体」提供的不仅仅是简单的媒体邀…

U盘文件删除如何恢复?4个实用技巧(含图文)

“我的u盘里保存了很多重要的文件&#xff0c;但是不知道为什么部分文件丢失&#xff0c;有什么方法可以帮我快速恢复u盘文件的吗&#xff1f;希望大家帮帮我&#xff01;” U盘作为我们日常存储和传输数据的重要工具&#xff0c;其数据的安全性和可恢复性尤为重要。当U盘中的文…

Vue22-v-model收集表单数据

一、效果图 二、代码 2-1、HTML代码 2-2、vue代码 1、v-model单选框的收集信息 v-model&#xff1a;默认收集的就是元素中的value值。 单选框添加默认值&#xff1a; 2、v-model多选框的收集信息 ①、多个选择的多选 注意&#xff1a; 此处的hobby要是数组&#xff01;&…

白酒:茅台镇白酒的品鉴会与文化交流活动

茅台镇&#xff0c;这个位于中国贵州省的小镇&#xff0c;因其与众不同的自然环境和杰出的酿酒工艺而成为世界著名的白酒产区。云仓酒庄豪迈白酒作为茅台镇的品牌&#xff0c;积极参与各种品鉴会和文化交流活动&#xff0c;向世界展示了中国白酒的魅力和文化底蕴。 近年来&…

华为云CodeArts API:API管理一体化平台 5月新特性上线啦!

CodeArts API是华为云API全生命周期管理一体化解决方案平台&#xff0c;支持开发者高效实现API设计、API开发、API测试、API托管、API运维、API变现的一站式体验。 通过以API契约为锚点&#xff0c;CodeArts API保证API各阶段数据高度一致&#xff0c;为开发者提供友好易用的A…

低功耗,大算力!最适合大模型的AI芯片是它?

在如今AI技术飞速发展的时代&#xff0c;AI加速芯片已经成为了大模型时代必不可少的核心组件。从CPU到GPU&#xff0c;再到TPU和NPU&#xff0c;各种芯片不断涌现&#xff0c;但都面临着能耗和算力的平衡问题。那么&#xff0c;有没有一种AI芯片能够同时满足低功耗和高算力的需…

(el-Transfer)操作(不使用 ts):Element-plus 中 Select 组件动态设置 options 值需求的解决过程

Ⅰ、Element-plus 提供的Select选择器组件与想要目标情况的对比&#xff1a; 1、Element-plus 提供Select组件情况&#xff1a; 其一、Element-ui 自提供的Select代码情况为(示例的代码)&#xff1a; // Element-plus 提供的组件代码: <template><div class"f…

前端JS必用工具【js-tool-big-box】学习,下载大文件(纯下载功能版)

这一小节呢&#xff0c;我们说一下 js-tool-big-box 工具库&#xff0c;下载文件的用法。这一小节说的是纯下载版本。 意思就是我们在前端项目开发中&#xff0c;下载功能嘛&#xff0c;无论你发送fetch请求&#xff0c;还是axios请求&#xff0c;你总得发送一下请求&#xff0…

车载网络安全指南 概述(一)

返回总目录->返回总目录<- 目录 前言 参考文档 术语 前言 汽车电子系统网络安全指南给出汽车电子系统网络安全活动框架,以及在此框架下的汽车电子系统网络安全活动、组织管理和支持保障等方面的建议。 汽车电子系统网络安全指南适用于指导整车厂、零部件供应商、软…

自定义函数命名规范

自定义函数命名规范 正文自定义函数名称不能与内置函数名称一致自定义函数名称不能与文件名称一致 正文 在 Lumerical 中&#xff0c;对于自定义函数名称&#xff0c;也有必须要遵守的规则。这里简单记录一下。 自定义函数名称不能与内置函数名称一致 比如&#xff0c;内置函…

深度神经网络——语音识别技术的探索与应用

概述 论文地址&#xff1a;https://arxiv.org/pdf/2402.19443.pdf 使用深度学习的语音识别技术已取得重大进展。这使得语音识别系统更加准确。然而&#xff0c;这项技术非常复杂&#xff0c;很难理解哪些信息用于何处。因此&#xff0c;本文提出了一种识别语音识别系统中哪些信…

Vue 项目开启 gzip

1. 压缩方式&#xff1a; 在 Nginx 开启压缩&#xff1a;当浏览器发起请求时&#xff0c;服务端对传输资源进行实时压缩&#xff0c;然后返回给浏览器&#xff1b;前端配置打包压缩并在服务端加上支持 gizp 的配置&#xff1a;当浏览器请求时&#xff0c;服务端直接将压缩资源…

虚拟化 之一 详解 jailhouse 架构及原理、软硬件要求、源码文件、基本组件

Jailhouse 是一个基于 Linux 实现的针对创建工业级应用程序的小型 Hypervisor&#xff0c;是由西门子公司的 Jan Kiszka 于 2013 年开发的&#xff0c;并得到了官方 Linux 内核的支持&#xff0c;在开源社区中获得了知名度和吸引力。 Jailhouse Jailhouse 是一种轻量级的虚拟化…