当前位置: 首页 > news >正文

企业品牌网站建设我们的优势青海城乡建设部网站

企业品牌网站建设我们的优势,青海城乡建设部网站,红豆视频在线观看高清免费下载,wordpress 斜杠结尾1.前言近两年来工信部对于应用的隐私合规安全问题愈加重视#xff0c;对 Android 平台的管控程度也要比 IOS 平台严格很多#xff0c;很多不合规的应用也先后被下架要求整改。笔者就曾遇到过加班整改隐私合规的问题#xff0c;隐私合规问题主要针对两个方面。在用户同意隐私…1.前言近两年来工信部对于应用的隐私合规安全问题愈加重视对 Android 平台的管控程度也要比 IOS 平台严格很多很多不合规的应用也先后被下架要求整改。笔者就曾遇到过加班整改隐私合规的问题隐私合规问题主要针对两个方面。在用户同意隐私协议之前不能收集用户隐私数据例如 IMEI、AndroidId、MAC 等在用户同意隐私协议之后收集用户数据行为在对应场景不能超频。比如一分钟不能超过 3 次获取 IMEI针对上述两个方面有以下措施来针对通过静态扫描收集项目中自有代码 三方 sdk使用隐私合规相关 api 的相关代码通过 ASM 插桩在调用隐私合规 api 之前插入代码记录运行时的方法调用链和当前时间hook 隐私合规 api替换字节码指令将调用链指向工具类在未同意隐私协议之前不调用相关的 api2.实现2.1 注解和工具类通过定义注解和工具类用来定义要处理哪些隐私合规相关的方法目前笔者已经处理了大部分请放心食用2.1.1 注解/*** 收集和注解匹配的方法* visitMethodInsn(int opcode, String owner, String name,String desc)** 如果 originName 和 originDesc 传逻辑会在插件中处理*/ Retention(RetentionPolicy.CLASS) Target({ElementType.METHOD}) public interface AsmMethodReplace {/*** 指令操作码*/int targetMethodOpcode();/*** 方法所有者类*/String targetClass();/*** 方法名称*/String targetName() default ;/*** 方法描述符*/String targetDesc() default ;/*** 是否进行 hook*/boolean hook() default false;}该注解用来匹配调用隐私合规 api 的字节码指令例如通过 ASM 调用 getImei 的字节码指令为methodVisitor.visitMethodInsn(INVOKEVIRTUAL, android/telephony/TelephonyManager, getImei, ()Ljava/lang/String;, false);。targetName 属性和 targetDesc 可以不赋值这里做了取巧的处理会根据工具类的 method name 和method descriptor 推断出调用隐私合规方法的字节码指令这块后面插件会处理。最后的 hook 属性表示是否 hook 掉原始的调用链将调用指向工具类中的方法。2.1.2 工具类上述注解可以用在任何地方笔者将常用的隐私合规的方法聚合起来方法工具类中统一处理例如处理 IMEI 的逻辑如下RequiresApi(api Build.VERSION_CODES.O) AsmMethodReplace(targetMethodOpcode OPCODE_INVOKEVIRTUAL, targetClass CLASS_NAME_TELEPHONYMANAGER,hook true) public static String getImei(TelephonyManager telephonyManager) {if (!checkAgreePrivacy(getImei)) {Log.e(TAG, TIP);return ;}if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) {Log.i(TAG, getImei-SDK_INT above android Q);return ;}return telephonyManager.getImei(); }如果还没有同意隐私协议直接 return “”否者走正常的调用方法。同时通过工具类   Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);ConfigGlobal.getInstance().setStoreDirectory(base.getExternalCacheDir().getAbsolutePath());}设置存储调用堆栈和时间的文件路径。2.2 插件处理gradle 插件的基本使用在这里就不赘述了这里主要是两方面的处理编译时扫描代码处理有定义特定注解的方法分析字节码指令收集所有需要处理的隐私合规相关 api 相关的信息再次扫描根据第一次扫描收集到的信息判断当前类是否含有调用隐私合规 api 的字节码指令如果有在该类中注入一个写文件方法及在隐私合规 api 调用指令之前插入写文件的字节码指令用来记录调用堆栈和频次。2.2.1 模版代码下面这块代码是我们在自定义 gradle 插件时常用的模版代码供大家使用abstract class BaseTransform extends Transform {AbstractExecutorService executorService ForkJoinPool.commonPool()private ListCallableVoid taskList new ArrayList()protected Project projectBaseTransform(Project project) {this.project project}OverrideString getName() {return getClass().simpleName}OverrideSetQualifiedContent.ContentType getInputTypes() {return TransformManager.CONTENT_CLASS}OverrideSet? super QualifiedContent.Scope getScopes() {return TransformManager.SCOPE_FULL_PROJECT}Overrideboolean isIncremental() {return true}Overridevoid transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {super.transform(transformInvocation)println(transform start---------------)if (firstTransform()) {printCopyRight()}onTransformStart(transformInvocation)def startTime System.currentTimeMillis()def inputs transformInvocation.inputsdef outputProvider transformInvocation.outputProviderdef context transformInvocation.contextdef isIncremental transformInvocation.isIncremental()if (!isIncremental) {outputProvider.deleteAll()}//1 // inputs.each { input - // input.jarInputs.each { JarInput jarInput - // forEachJar(jarInput, outputProvider, context, isIncremental) // } // // input.directoryInputs.each { DirectoryInput dirInput - // forEachDir(dirInput, outputProvider, context, isIncremental) // } // }//3inputs.each { input -input.jarInputs.each { jarInput -submitTask(new Runnable() {Overridevoid run() {forEachJar(jarInput, outputProvider, context, isIncremental)}})}input.directoryInputs.each { DirectoryInput dirInput -submitTask(new Runnable() {Overridevoid run() {forEachDir(dirInput, outputProvider, context, isIncremental)}})}}def futures executorService.invokeAll(taskList)futures.each { it -it.get()}onTransformEnd(transformInvocation)println(getName() transform end--------------- duration : (System.currentTimeMillis() - startTime) ms)}void submitTask(Runnable runnable) {taskList.add(new CallableVoid() {OverrideVoid call() throws Exception {runnable.run()return null}})}void forEachDir(DirectoryInput directoryInput, TransformOutputProvider outputProvider, Context context, boolean isIncremental) {def inputDir directoryInput.fileFile dest outputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes,directoryInput.scopes,Format.DIRECTORY)println directoryInputPath: directoryInput.file.absolutePathprintln destPath: dest.absolutePathdef srcDirPath inputDir.absolutePathdef destDirPath dest.absolutePathdef temporaryDir context.temporaryDirFileUtils.forceMkdir(dest)Logger.info(srcDirPath:${srcDirPath}, destDirPath:${destDirPath})if (isIncremental) {directoryInput.getChangedFiles().each { entry -def classFile entry.keyswitch (entry.value) {case Status.NOTCHANGED:Logger.info(处理 class classFile.absoluteFile NOTCHANGED)breakcase Status.REMOVED:Logger.info(处理 class classFile.absoluteFile REMOVED)//最终文件应该存放的路径def destFilePath classFile.absolutePath.replace(srcDirPath, destDirPath)def destFile File(destFilePath)if (destFile.exists()) {destFile.delete()}breakcase Status.ADDED:case Status.CHANGED:Logger.info(处理 class classFile.absoluteFile ADDED or CHANGED)modifyClassFile(classFile, srcDirPath, destDirPath, temporaryDir)breakdefault:break}}} else {com.android.utils.FileUtils.getAllFiles(inputDir).each { File file -modifyClassFile(file, srcDirPath, destDirPath, temporaryDir)}}}void modifyClassFile(classFile, srcDirPath, destDirPath, temporaryDir) {Logger.info(处理 class classFile.absoluteFile)//目标路径def destFilePath classFile.absolutePath.replace(srcDirPath, destDirPath)def destFile new File(destFilePath)if (destFile.exists()) {destFile.delete()}Logger.info(处理 classdestFile destFile.absoluteFile)String className CommonUtil.path2ClassName(classFile.absolutePath.replace(srcDirPath File.separator, ))Logger.info(处理 className className)File modifyFile nullif (CommonUtil.isLegalClass(classFile) shouldHookClass(className)) {modifyFile getModifyFile(classFile, temporaryDir, className)}if (modifyFile null) {modifyFile classFile}FileUtils.copyFile(modifyFile, destFile)}File getModifyFile(File classFile, File temporaryDir, String className) {byte[] sourceBytes IOUtils.toByteArray(new FileInputStream(classFile))def tempFile new File(temporaryDir, CommonUtil.generateClassFileName(classFile))if (tempFile.exists()) {FileUtils.forceDelete(tempFile)}def modifyBytes modifyClass(className, sourceBytes)if (modifyBytes null) {modifyBytes sourceBytes}tempFile.createNewFile()def fos new FileOutputStream(tempFile)fos.write(modifyBytes)fos.flush()IOUtils.closeQuietly(fos)return tempFile}void forEachJar(JarInput jarInput, TransformOutputProvider outputProvider, Context context, boolean isIncremental) {Logger.info(jarInput: jarInput.file)File destFile outputProvider.getContentLocation(//防止同名被覆盖CommonUtil.generateJarFileName(jarInput.file), jarInput.contentTypes, jarInput.scopes, Format.JAR)//增量编译处理if (isIncremental) {Status status jarInput.statusswitch (status) {case Status.NOTCHANGED:Logger.info(处理 jar jarInput.file.absoluteFile NotChanged)//Do nothingreturncase Status.REMOVED:Logger.info(处理 jar jarInput.file.absoluteFile REMOVED)if (destFile.exists()) {FileUtils.forceDelete(destFile)}returncase Status.ADDED:case Status.CHANGED:Logger.info(处理 jar jarInput.file.absoluteFile ADDED or CHANGED)break}}if (destFile.exists()) {FileUtils.forceDelete(destFile)}CommonUtil.isLegalJar(jarInput.file) ? transformJar(jarInput.file, context.getTemporaryDir(), destFile): FileUtils.copyFile(jarInput.file, destFile)}def transformJar(File jarFile, File temporaryDir, File destFile) {Logger.info(处理 jar jarFile.absoluteFile)File tempOutputJarFile new File(temporaryDir, CommonUtil.generateJarFileName(jarFile))if (tempOutputJarFile.exists()) {FileUtils.forceDelete(tempOutputJarFile)}JarOutputStream jarOutputStream new JarOutputStream(new FileOutputStream(tempOutputJarFile))JarFile inputJarFile new JarFile(jarFile, false)try {def entries inputJarFile.entries()while (entries.hasMoreElements()) {def jarEntry entries.nextElement()def entryName jarEntry.getName()def inputStream inputJarFile.getInputStream(jarEntry)try {byte[] sourceByteArray IOUtils.toByteArray(inputStream)def modifiedByteArray nullif (!jarEntry.isDirectory() CommonUtil.isLegalClass(entryName)) {String className CommonUtil.path2ClassName(entryName)if (shouldHookClass(className)) {modifiedByteArray modifyClass(className, sourceByteArray)}}if (modifiedByteArray null) {modifiedByteArray sourceByteArray}jarOutputStream.putNextEntry(new JarEntry(entryName))jarOutputStream.write(modifiedByteArray)jarOutputStream.closeEntry()} finally {IOUtils.closeQuietly(inputStream)}}} finally {jarOutputStream.flush()IOUtils.closeQuietly(jarOutputStream)IOUtils.closeQuietly(inputJarFile)}FileUtils.copyFile(tempOutputJarFile, destFile)}private byte[] modifyClass(String className, byte[] sourceBytes) {byte[] classBytesCodetry {classBytesCode hookClassInner(className, sourceBytes)} catch (Throwable e) {e.printStackTrace()classBytesCode nullprintln throw exception when modify class ${className}}return classBytesCode}/*** 打印日志信息*/static void printCopyRight() {println()println #######################################################################println ########## println ########## 欢迎使用隐私合规处理插件println ########## println #######################################################################println ########## println ########## 插件配置参数 println ########## println ########## -isDebug: PrivacyGlobalConfig.isDebugprintln ########## -handleAnnotationName: PrivacyGlobalConfig.handleAnnotationNameprintln ########## -exclude: PrivacyGlobalConfig.excludeprintln ########## println ########## println ########## println #######################################################################println()}protected boolean firstTransform() {return false}boolean shouldHookClass(String className) {def excludes PrivacyGlobalConfig.excludeif (excludes ! null) {for (String string : excludes) {if (className.startsWith(string)) {return false}}}return shouldHookClassInner(className)}protected abstract boolean shouldHookClassInner(String className)protected abstract byte[] hookClassInner(String className, byte[] bytes)protected abstract void onTransformStart(TransformInvocation transformInvocation)protected abstract void onTransformEnd(TransformInvocation transformInvocation) }2.2.2 注解处理 transform收集和处理具有特定注解的字节码指令,给下一个 transform 使用Override byte[] hookClassInner(String className, byte[] bytes) {ClassReader cr new ClassReader(bytes)ClassNode classNode new ClassNode()cr.accept(classNode, 0)classNode.methods.each { methodNode -//编译期注解methodNode.invisibleAnnotations.each { annotationNode -if (PrivacyGlobalConfig.getHandleAnnotationName() annotationNode.desc) {collectPrivacyMethod(annotationNode, methodNode, cr.className)}}}return bytes }/*** 收集注解和注解关联的方法* param annotationNode 注解信息* param methodNode 方法信息*/ static collectPrivacyMethod(AnnotationNode annotationNode, MethodNode methodNode, String className) {ListObject values annotationNode.valuesLogger.info(annotation values : ${values})MethodReplaceItem item new MethodReplaceItem(values, methodNode, CommonUtil.getClassInternalName(className))PrivacyGlobalConfig.methodReplaceItemList.offer(item)Logger.info(collectPrivacyMethod success: ${item})println(collectPrivacyMethod success: ${item}) }MethodReplaceItem中封装了收集到的字节码属性同时会根据注解关联方法的字节码指令推断出想要处理的隐私合规 api 的字节码指令。MethodReplaceItem(ListObject annotationPair, MethodNode methodNode, String owner) {replaceOpcode Opcodes.INVOKESTATICreplaceClass ownerreplaceMethod methodNode.namereplaceDesc methodNode.descfor (int i 0; i annotationPair.size(); i i 2) {def key annotationPair[i]def value annotationPair[i 1]if (key targetMethodOpcode) {targetOpcode value} else if (key targetClass) {targetOwner value} else if (key targetName) {targetMethod value} else if (key targetDesc) {targetDesc value}else if(key hook){willHook value}}Logger.info(targetOpcode:${targetOpcode},targetOwner:${targetOwner} , replaceDesc${replaceDesc})if (isEmpty(targetMethod)) {targetMethod replaceMethod}if (isEmpty(targetDesc)) {//静态方法oriDesc 跟 targetDesc 一样if (targetOpcode Opcodes.INVOKESTATIC) {targetDesc replaceDesc} else {//非静态方法约定第一个参数是实例类名oriDesc 比 targetDesc 少一个参数处理一下// (Landroid/telephony/TelephonyManager;)Ljava/lang/String - ()Ljava/lang/Stringdef param replaceDesc.split(\))[0] )def result replaceDesc.split(\))[1]def index replaceDesc.indexOf(targetOwner)if (index ! -1) {param ( param.substring(index targetOwner.length() 1)}Logger.info(index::: ${index})targetDesc param result}} }2.2.3 合规方法处理 transform再次扫描整个项目根据在上一个 transform 中收集到的要处理的隐私合规的 api遍历字节码指令当匹配上时在当前的类中注入写文件的方法同时在调用隐私合规的字节码指令前插入写文件的字节码指令用来记录。Override byte[] hookClassInner(String className, byte[] bytes) {Logger.info(${getName()} modifyClassInner---------------)def findHookPoint falseMapMethodNode, InsertInsnPoint collectMap new HashMap()ClassReader cr new ClassReader(bytes)ClassNode classNode new ClassNode()cr.accept(classNode, ClassReader.EXPAND_FRAMES)classNode.methods.each { methodNode -//过滤掉含有特定注解的方法if (isNotHookMethod(cr.className, methodNode)) {methodNode.instructions.each { insnNode -//判断字节码能否匹配def methodReplaceItem searchHookPoint(insnNode)if (methodReplaceItem ! null) {//判断是否需要 hook 掉当前指令def inject methodReplaceItem.willHook//记录隐私合规 api 所在的类及方法logHookPoint(classNode.name, methodReplaceItem, methodNode, insnNode.opcode, insnNode.owner, insnNode.name, insnNode.desc, inject)if (inject) {//hookinjectInsn(insnNode, methodReplaceItem)}//插入写文件方法指令收集调用隐私方法的堆栈collectInsertInsn(insnNode, methodNode, classNode, collectMap, inject)findHookPoint true}}}}if (!collectMap.isEmpty() findHookPoint) {//插入写文件指令用来展示堆栈信息collectMap.each { key, value -key.instructions.insert(value.hookInsnNode, value.instList)}//插入 writeToFile 方法ClassWriter cw new ClassWriter(ClassWriter.COMPUTE_MAXS)classNode.accept(cw)insertWriteToFileMethod(cw)return cw.toByteArray()}return bytes }在 collectInsertInsn 方法中通过 throwable 来收集当前的堆栈/*** 收集 在调用特定的方法前插入调用写入文件的方法的指令* param insnNode* param methodNode* param classNode* param collectMap*/ static void collectInsertInsn(insnNode, methodNode, classNode, collectMap, Inject) {def className classNode.namedef methodName methodNode.namedef methodDesc methodNode.descdef owner nulldef name nulldef desc nullif (insnNode instanceof MethodInsnNode) {owner insnNode.ownername insnNode.namedesc insnNode.desc}//------logStringBuilder lintLog new StringBuilder()lintLog.append(className)lintLog.append( - )lintLog.append(methodName)lintLog.append( - )lintLog.append(methodDesc)lintLog.append(\r\n)lintLog.append(owner)lintLog.append( - )lintLog.append(name)lintLog.append( - )lintLog.append(desc)//------要插入字节码指令lintLog.append(\r\n)InsnList insnList new InsnList()insnList.add(new LdcInsnNode(lintLog.toString()))insnList.add(new TypeInsnNode(Opcodes.NEW, java/lang/Throwable))insnList.add(new InsnNode(Opcodes.DUP))insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, java/lang/Throwable, init, ()V, false))insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, className, writeToFileMethodName, writeToFileMethodDesc))println 插入指令完成 ----------collectMap.put(methodNode, new InsertInsnPoint(insnList, insnNode)) }最终在项目编译完成之后会在项目的根目录下生成 replaceInsn.txt 文件记录包含隐私合规 api 的类和相关方法。当项目运行起来之后会在设置的路径中笔者设置在 getExternalCacheDir 中生成 privacy_log.txt 文件里面会记录隐私合规 api 的调用堆栈和时间根据该调用链我们就可以快速定位是哪一块业务执行了敏感操作。3.总结通过 ASM gradle plugin 能够排查出大部分的隐私合规问题。有什么不足之处也请读者多多提意见和建议。源码地址https://github.com/season-max/asm_hook.git
http://www.tj-hxxt.cn/news/223730.html

相关文章:

  • 网站优化的学习注册安全工程师考试时间
  • 如何做一个企业的网站谷歌seo网站怎么做产品分类
  • 网站开发的技术问题北京室内设计公司排名榜
  • 网站建设html5源码wordpress手机端插件下载
  • 购物网站管理层深圳网站制作公司报价单
  • 外贸网站建设soho网络营销主页
  • 国外做水广告网站大全关于网站建设广告词
  • 做女朋友网站河北邯郸永利ktv视频
  • 网站的301重定向怎么做安徽建设工程信息管理平台
  • 做一个网站flash收多少钱优化网站内链
  • 建立网站怎么做关键字网站开发技术历史
  • 佛山网站建设与设计免费会员黄页网
  • wordpress数据库和网站文件下载怎样做网站搜索推广电话成都
  • 北京网站推广排名公司网站发帖做业务
  • 广州站在哪里广州企业网站
  • 网站建设时间推进表模板怎么看域名在哪里注册的
  • 做零售出口的网站wordpress版权怎
  • 南宁高端网站做红酒知名网站
  • 网络推广学校大连seo整站优化
  • 企业营销系统和网站建设响应式设计的网站
  • 长沙网站搭建公司联系方式wordpress无法发表文章
  • 建设网站青岛市linux php网站部署
  • 唐山教育平台网站建设旅游网网站建设
  • 温州建设监理协会网站百度云网站空间建设
  • 推荐的网站制作广州公司注册流程及需要的材料
  • 网站建设国内外研究现状模板网站图解图片是用什么软件做的
  • 枣庄网站开发天津网站维护
  • 成都装修设计公司网站网页制作基础教程visual studio code
  • 怎么做网站内容调研设计类的软件有哪些
  • 网站建设是怎么建的电商运营怎么做数据分析