1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.xts.apimapper.adapter 18 19 import com.android.xts.apimapper.asm.ClassNodes 20 import com.android.xts.apimapper.asm.prependArgTypeToMethodDescriptor 21 import com.android.xts.apimapper.asm.toHumanReadableClassName 22 import com.android.xts.apimapper.asm.toHumanReadableDesc 23 import com.android.xts.apimapper.asm.writeByteCodeToPushArguments 24 import com.android.xts.apimapper.asm.writeByteCodeToReturn 25 import org.objectweb.asm.ClassVisitor 26 import org.objectweb.asm.MethodVisitor 27 import org.objectweb.asm.Opcodes 28 import org.objectweb.asm.Opcodes.ACC_PRIVATE 29 import org.objectweb.asm.Opcodes.ACC_STATIC 30 import org.objectweb.asm.Opcodes.ACONST_NULL 31 import org.objectweb.asm.Opcodes.ALOAD 32 import org.objectweb.asm.Type 33 34 const val OPCODE_VERSION = Opcodes.ASM9 35 const val METHOD_CLASS_NAME = "com/android/xts/apimapper/helper/DeviceMethodCallHook" 36 const val HOOK_METHOD_DESC = "(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;" + 37 "Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V" 38 const val HOOK_METHOD_NAME = "onBeforeCall" 39 40 private const val APIMAPPER_BRIDGE_PREFIX = "_apimapper_bridge" 41 42 /** 43 * Inject a hook to log each potential API call. 44 */ 45 class MethodCallHookingAdapter( 46 nextVisitor: ClassVisitor, 47 val settings: HookSettings, 48 val classes: ClassNodes, 49 ) : ClassVisitor(OPCODE_VERSION, nextVisitor) { 50 51 lateinit var className: String 52 private var nextBridgeMethod = 0 53 54 // Map for reusing bridge methods. 55 private val bridgeMethods = mutableMapOf<BridgeKey, String>() 56 57 override fun visit( 58 version: Int, 59 access: Int, 60 name: String, 61 signature: String?, 62 superName: String?, 63 interfaces: Array<out String>?, 64 ) { 65 super.visit(version, access, name, signature, superName, interfaces) 66 className = name 67 } 68 69 override fun visitMethod( 70 access: Int, 71 name: String, 72 descriptor: String, 73 signature: String?, 74 exceptions: Array<out String>?, 75 ): MethodVisitor { 76 return MethodCallHookingMethodVisitor( 77 name, 78 descriptor, 79 super.visitMethod(access, name, descriptor, signature, exceptions) 80 ) 81 } 82 83 private inner class MethodCallHookingMethodVisitor( 84 val methodName: String, 85 val methodDescription: String, 86 val nextVisitor: MethodVisitor, 87 ) : MethodVisitor(OPCODE_VERSION, nextVisitor) { 88 89 override fun visitCode() { 90 super.visitCode() 91 logCall( 92 nextVisitor, 93 null, 94 null, 95 className, 96 methodName, 97 methodDescription, 98 null 99 ) 100 } 101 102 override fun visitMethodInsn( 103 opcode: Int, 104 owner: String, 105 name: String, 106 desc: String, 107 isInterface: Boolean, 108 ) { 109 if (!methodName.startsWith(APIMAPPER_BRIDGE_PREFIX)) { 110 if (settings.shouldInjectHook( 111 classes, 112 className, 113 opcode, 114 owner, 115 name, 116 desc 117 )) { 118 hookMethod( 119 nextVisitor, 120 methodName, 121 opcode, 122 owner, 123 name, 124 desc, 125 isInterface 126 ) 127 return 128 } 129 } 130 super.visitMethodInsn(opcode, owner, name, desc, isInterface) 131 } 132 } 133 134 /** Log the call. */ 135 private fun logCall( 136 visitor: MethodVisitor, 137 callerMethodName: String?, 138 calleeMethodOpcode: Int?, 139 calleeMethodOwner: String, 140 calleeMethodName: String, 141 calleeMethodDesc: String, 142 receiverIndex: Int? 143 ) { 144 if (callerMethodName == null) { 145 visitor.visitInsn(ACONST_NULL) 146 visitor.visitInsn(ACONST_NULL) 147 visitor.visitInsn(ACONST_NULL) 148 } else { 149 visitor.visitLdcInsn(className.toHumanReadableClassName()) 150 visitor.visitLdcInsn(callerMethodName) 151 visitor.visitLdcInsn(calleeMethodOpcode) 152 } 153 visitor.visitLdcInsn(calleeMethodOwner.toHumanReadableClassName()) 154 visitor.visitLdcInsn(calleeMethodName) 155 visitor.visitLdcInsn(calleeMethodDesc.toHumanReadableDesc()) 156 if (receiverIndex == null) { 157 visitor.visitInsn(ACONST_NULL) 158 } else { 159 visitor.visitVarInsn(ALOAD, 0) 160 } 161 162 visitor.visitMethodInsn( 163 Opcodes.INVOKESTATIC, 164 METHOD_CLASS_NAME, 165 HOOK_METHOD_NAME, 166 HOOK_METHOD_DESC, 167 false 168 ) 169 } 170 171 private fun hookMethod( 172 visitor: MethodVisitor, 173 callerMethodName: String, 174 calleeMethodOpcode: Int, 175 calleeMethodOwner: String, 176 calleeMethodName: String, 177 calleeMethodDesc: String, 178 calleeMethodIsInterface: Boolean, 179 ) { 180 if (!(calleeMethodOpcode == Opcodes.INVOKEVIRTUAL || 181 calleeMethodOpcode == Opcodes.INVOKEINTERFACE)) { 182 // In this case, simply inject a method call log. 183 logCall( 184 visitor, 185 callerMethodName, 186 calleeMethodOpcode, 187 calleeMethodOwner, 188 calleeMethodName, 189 calleeMethodDesc, 190 null 191 ) 192 // Call the real method. 193 visitor.visitMethodInsn( 194 calleeMethodOpcode, 195 calleeMethodOwner, 196 calleeMethodName, 197 calleeMethodDesc, 198 calleeMethodIsInterface 199 ) 200 return 201 } 202 203 // If it's a virtual or interface call, call the bridge method to log the real object. 204 val bridgeKey = BridgeKey(calleeMethodOwner, calleeMethodName, calleeMethodDesc) 205 val existing = bridgeMethods[bridgeKey] 206 val bridgeName = existing ?: (APIMAPPER_BRIDGE_PREFIX + nextBridgeMethod++) 207 val bridgeDesc = 208 prependArgTypeToMethodDescriptor( 209 calleeMethodDesc, 210 Type.getType("L$calleeMethodOwner;"), 211 ) 212 213 // Replace the call with a call to the bridge method. 214 visitor.visitMethodInsn( 215 Opcodes.INVOKESTATIC, 216 className, 217 bridgeName, 218 bridgeDesc, 219 false 220 ) 221 222 if (existing != null) { 223 // Bridge method has already created. 224 return 225 } 226 bridgeMethods[bridgeKey] = bridgeName 227 228 // Create a bridge method. 229 super.visitMethod( 230 ACC_PRIVATE or ACC_STATIC, 231 bridgeName, 232 bridgeDesc, 233 null, 234 null, 235 ).let { br -> 236 br.visitCode() 237 238 logCall( 239 br, 240 callerMethodName, 241 calleeMethodOpcode, 242 calleeMethodOwner, 243 calleeMethodName, 244 calleeMethodDesc, 245 0, 246 ) 247 248 // Re-push the arguments to the stack. 249 br.visitVarInsn(ALOAD, 0) 250 writeByteCodeToPushArguments(calleeMethodDesc, br, 1) 251 252 // Call the real method. 253 br.visitMethodInsn( 254 calleeMethodOpcode, 255 calleeMethodOwner, 256 calleeMethodName, 257 calleeMethodDesc, 258 calleeMethodIsInterface 259 ) 260 261 writeByteCodeToReturn(bridgeDesc, br) 262 263 br.visitMaxs(0, 0) 264 } 265 } 266 267 private data class BridgeKey( 268 val className: String, 269 val methodName: String, 270 val methodDesc: String, 271 ) 272 } 273