• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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