@@ -95,6 +95,7 @@ function registerHook(
9595
9696 const event = {
9797 id : generateUUID ( ) ,
98+ type : "hook" ,
9899 category : categoryName ,
99100 time : new Date ( ) . toISOString ( ) ,
100101 class : clazz ,
@@ -118,26 +119,197 @@ function registerHook(
118119}
119120
120121/**
121- * Takes an array of objects usually defined in the `hooks.js` file of a DEMO and loads all classes and functions stated in there.
122- * @param {[object] } hook - Contains a list of objects which contains all methods which will be overloaded. (e.g., [{class: "android.security.keystore.KeyGenParameterSpec$Builder", methods: [ "setBlockModes"]}])
123- * @param {string } categoryName - OWASP MAS category for easier identification (e.g., "CRYPTO")
124- * @param {function } callback - Callback function. The function takes the information gathered as JSON string.
122+ * Finds the overload index that matches the given argument types.
123+ * @param {Object } methodHandle - Frida method handle with overloads.
124+ * @param {string[] } argTypes - Array of argument type strings (e.g., ["android.net.Uri", "android.content.ContentValues"]).
125+ * @returns {number } The index of the matching overload, or -1 if not found.
126+ */
127+ function findOverloadIndex ( methodHandle , argTypes ) {
128+ for ( var i = 0 ; i < methodHandle . overloads . length ; i ++ ) {
129+ var overload = methodHandle . overloads [ i ] ;
130+ var parameterTypes = parseParameterTypes ( overload . toString ( ) ) ;
131+
132+ if ( parameterTypes . length !== argTypes . length ) {
133+ continue ;
134+ }
135+
136+ var match = true ;
137+ for ( var j = 0 ; j < argTypes . length ; j ++ ) {
138+ if ( parameterTypes [ j ] !== argTypes [ j ] ) {
139+ match = false ;
140+ break ;
141+ }
142+ }
143+
144+ if ( match ) {
145+ return i ;
146+ }
147+ }
148+ return - 1 ;
149+ }
150+
151+ /**
152+ * Builds a normalized list of hook operations for a single hook definition.
153+ * Each operation contains clazz, method, overloadIndex, and args array (decoded parameter types).
154+ * This centralizes selection logic used for both summary emission and hook registration.
155+ *
156+ * The function supports several hook configuration scenarios:
157+ * - If both `methods` and `overloads` are specified, the configuration is considered invalid and no operations are returned.
158+ * - If a single `method` and an explicit list of `overloads` are provided, only those overloads are considered.
159+ * - If only `methods` is provided, all overloads for each method are included.
160+ * - If only `method` is provided, all overloads for that method are included.
161+ * - If neither is provided, or if the configuration is invalid, no operations are returned.
162+ *
163+ * Error handling:
164+ * - If an explicit overload is not found, it is skipped and not included in the operations.
165+ * - If an exception occurs during processing, it is logged and the function returns the operations collected so far.
166+ *
167+ * @param {object } hook - Hook definition object. Supported formats:
168+ * - { class: string, method: string }
169+ * - { class: string, methods: string[] }
170+ * - { class: string, method: string, overloads: Array<{ args: string[] }> }
171+ * @returns {{operations: Array<{clazz:string, method:string, overloadIndex:number, args:string[]}>, count:number} }
172+ *
173+ * @example
174+ * // Hook all overloads of a single method
175+ * buildHookOperations({ class: "android.net.Uri", method: "parse" });
176+ *
177+ * @example
178+ * // Hook all overloads of multiple methods
179+ * buildHookOperations({ class: "android.net.Uri", methods: ["parse", "toString"] });
180+ *
181+ * @example
182+ * // Hook specific overloads of a method
183+ * buildHookOperations({
184+ * class: "android.net.Uri",
185+ * method: "parse",
186+ * overloads: [
187+ * { args: ["java.lang.String"] },
188+ * { args: ["android.net.Uri"] }
189+ * ]
190+ * });
191+ *
192+ * @example
193+ * // Invalid configuration: both methods and overloads
194+ * buildHookOperations({
195+ * class: "android.net.Uri",
196+ * methods: ["parse"],
197+ * overloads: [{ args: ["java.lang.String"] }]
198+ * });
199+ * // Returns { operations: [], count: 0 }
125200 */
126- function registerAllHooks ( hook , categoryName , callback ) {
127- for ( const m in hook . methods ) {
201+ function buildHookOperations ( hook ) {
202+ var operations = [ ] ;
203+ var errors = [ ] ;
204+
205+ try {
206+ // Invalid configuration: methods + overloads (logged elsewhere)
207+ if ( hook . methods && hook . overloads && hook . overloads . length > 0 ) {
208+ var errInvalid = "Invalid hook configuration for " + hook . class + ": 'overloads' is only supported with a singular 'method', not with 'methods'." ;
209+ console . error ( errInvalid ) ;
210+ errors . push ( errInvalid ) ;
211+ return { operations : operations , count : 0 , errors : errors , errorCount : errors . length } ;
212+ }
213+
214+ // Explicit overload list for single method
215+ if ( hook . method && hook . overloads && hook . overloads . length > 0 ) {
128216 try {
129- var toHook = Java . use ( hook . class ) [ hook . methods [ m ] ] ;
217+ var handle = Java . use ( hook . class ) [ hook . method ] ;
218+ for ( var o = 0 ; o < hook . overloads . length ; o ++ ) {
219+ var def = hook . overloads [ o ] ;
220+ var argsExplicit = Array . isArray ( def . args ) ? def . args : [ ] ;
221+ var idx = findOverloadIndex ( handle , argsExplicit ) ;
222+ if ( idx !== - 1 ) {
223+ var params = parseParameterTypes ( handle . overloads [ idx ] . toString ( ) ) ;
224+ operations . push ( { clazz : hook . class , method : hook . method , overloadIndex : idx , args : params } ) ;
225+ } else {
226+ console . warn (
227+ "[frida-android] Warning: Overload not found for class '" +
228+ hook . class +
229+ "', method '" +
230+ hook . method +
231+ "', args [" +
232+ argsExplicit . join ( ", " ) +
233+ "]. This hook will be skipped."
234+ ) ;
235+ errors . push ( "Overload not found for " + hook . class + ":" + hook . method + " with args [" + argsExplicit . join ( ", " ) + "]" ) ;
236+ }
237+ }
238+ } catch ( e ) {
239+ var errMsg = "Failed to process method '" + hook . method + "' in class '" + hook . class + "': " + e ;
240+ console . warn ( "Warning: " + errMsg ) ;
241+ errors . push ( errMsg ) ;
242+ }
243+ return { operations : operations , count : operations . length , errors : errors , errorCount : errors . length } ;
244+ }
130245
131- var overloadCount = toHook . overloads . length ;
246+ // Single method without explicit overloads: all overloads
247+ if ( hook . method && ( ! hook . overloads || hook . overloads . length === 0 ) ) {
248+ try {
249+ var handleAll = Java . use ( hook . class ) [ hook . method ] ;
250+ for ( var i = 0 ; i < handleAll . overloads . length ; i ++ ) {
251+ var paramsAll = parseParameterTypes ( handleAll . overloads [ i ] . toString ( ) ) ;
252+ operations . push ( { clazz : hook . class , method : hook . method , overloadIndex : i , args : paramsAll } ) ;
253+ }
254+ } catch ( e ) {
255+ var errMsg2 = "Failed to process method '" + hook . method + "' in class '" + hook . class + "': " + e ;
256+ console . warn ( "Warning: " + errMsg2 ) ;
257+ errors . push ( errMsg2 ) ;
258+ }
259+ return { operations : operations , count : operations . length , errors : errors , errorCount : errors . length } ;
260+ }
132261
133- for ( var i = 0 ; i < overloadCount ; i ++ ) {
134- registerHook ( hook . class , hook . methods [ m ] , i , categoryName , callback , hook . maxFrames ) ;
262+ // Multiple methods: all overloads for each
263+ if ( hook . methods ) {
264+ for ( var m = 0 ; m < hook . methods . length ; m ++ ) {
265+ var mName = hook . methods [ m ] ;
266+ try {
267+ var handleEach = Java . use ( hook . class ) [ mName ] ;
268+ for ( var j = 0 ; j < handleEach . overloads . length ; j ++ ) {
269+ var paramsEach = parseParameterTypes ( handleEach . overloads [ j ] . toString ( ) ) ;
270+ operations . push ( { clazz : hook . class , method : mName , overloadIndex : j , args : paramsEach } ) ;
271+ }
272+ } catch ( e ) {
273+ var errMsg3 = "Failed to process method '" + mName + "' in class '" + hook . class + "': " + e ;
274+ console . warn ( "Warning: " + errMsg3 ) ;
275+ errors . push ( errMsg3 ) ;
135276 }
136- } catch ( err ) {
137- console . error ( err )
138- console . error ( `Problem when overloading ${ hook . class } :${ hook . methods [ m ] } ` ) ;
139277 }
278+ return { operations : operations , count : operations . length , errors : errors , errorCount : errors . length } ;
279+ }
280+ } catch ( e ) {
281+ // Log the error to aid debugging; returning partial results
282+ var errMsg4 = "Error in buildHookOperations for hook: " + ( hook && hook . class ? hook . class : "<unknown>" ) + ": " + e ;
283+ console . error ( errMsg4 ) ;
284+ errors . push ( errMsg4 ) ;
285+ }
286+
287+ return { operations : operations , count : operations . length , errors : errors , errorCount : errors . length } ;
288+ }
289+
290+ /**
291+ * Takes an array of objects usually defined in the `hooks.js` file of a DEMO and loads all classes and functions stated in there.
292+ * @param {[object] } hook - Contains a list of objects which contains all methods which will be overloaded.
293+ * Basic format: {class: "android.security.keystore.KeyGenParameterSpec$Builder", methods: ["setBlockModes"]}
294+ * With overloads: {class: "android.content.ContentResolver", method: "insert", overloads: [{args: ["android.net.Uri", "android.content.ContentValues"]}]}
295+ * @param {string } categoryName - OWASP MAS category for easier identification (e.g., "CRYPTO")
296+ * @param {function } callback - Callback function. The function takes the information gathered as JSON string.
297+ * @param {{operations: Array<{clazz:string, method:string, overloadIndex:number, args:string[]}>, count:number} } [cachedOperations] - Optional pre-computed hook operations to avoid redundant processing.
298+ */
299+ function registerAllHooks ( hook , categoryName , callback , cachedOperations ) {
300+ if ( hook . methods && hook . overloads && hook . overloads . length > 0 ) {
301+ console . error ( `Invalid hook configuration for ${ hook . class } : 'overloads' is only supported with a singular 'method', not with 'methods'.` ) ;
302+ return ;
303+ }
304+ var built = cachedOperations || buildHookOperations ( hook ) ;
305+ built . operations . forEach ( function ( op ) {
306+ try {
307+ registerHook ( op . clazz , op . method , op . overloadIndex , categoryName , callback , hook . maxFrames ) ;
308+ } catch ( err ) {
309+ console . error ( err ) ;
310+ console . error ( `Problem when overloading ${ op . clazz } :${ op . method } #${ op . overloadIndex } ` ) ;
140311 }
312+ } ) ;
141313}
142314
143315Java . perform ( function ( ) {
@@ -146,8 +318,62 @@ Java.perform(function () {
146318 console . log ( JSON . stringify ( event , null , 2 ) )
147319 }
148320
321+ // Pre-compute hook operations once to avoid redundant processing
322+ var hookOperationsCache = [ ] ;
149323 target . hooks . forEach ( function ( hook , _ ) {
150- registerAllHooks ( hook , target . category , callback ) ;
324+ hookOperationsCache . push ( {
325+ hook : hook ,
326+ built : buildHookOperations ( hook )
327+ } ) ;
328+ } ) ;
329+
330+ // Emit an initial summary of all overloads that will be hooked
331+ try {
332+ // Aggregate map nested by class then method
333+ var aggregate = { } ;
334+ var total = 0 ;
335+ var errors = [ ] ;
336+ var totalErrors = 0 ;
337+ hookOperationsCache . forEach ( function ( cached ) {
338+ total += cached . built . count ;
339+ if ( cached . built . errors && cached . built . errors . length ) {
340+ Array . prototype . push . apply ( errors , cached . built . errors ) ;
341+ totalErrors += cached . built . errors . length ;
342+ }
343+ cached . built . operations . forEach ( function ( op ) {
344+ if ( ! aggregate [ op . clazz ] ) {
345+ aggregate [ op . clazz ] = { } ;
346+ }
347+ if ( ! aggregate [ op . clazz ] [ op . method ] ) {
348+ aggregate [ op . clazz ] [ op . method ] = [ ] ;
349+ }
350+ aggregate [ op . clazz ] [ op . method ] . push ( op . args ) ;
351+ } ) ;
352+ } ) ;
353+
354+ var overloadList = [ ] ;
355+ for ( var clazz in aggregate ) {
356+ if ( ! aggregate . hasOwnProperty ( clazz ) ) continue ;
357+ var methodsMap = aggregate [ clazz ] ;
358+ for ( var methodName in methodsMap ) {
359+ if ( ! methodsMap . hasOwnProperty ( methodName ) ) continue ;
360+ var entries = methodsMap [ methodName ] . map ( function ( argsArr ) {
361+ return { args : argsArr } ;
362+ } ) ;
363+ overloadList . push ( { class : clazz , method : methodName , overloads : entries } ) ;
364+ }
365+ }
366+
367+ var summary = { type : "summary" , hooks : overloadList , totalHooks : total , errors : errors , totalErrors : totalErrors } ;
368+ console . log ( JSON . stringify ( summary , null , 2 ) ) ;
369+ } catch ( e ) {
370+ // If summary fails, don't block hooking
371+ console . error ( "Summary generation failed, but hooking will continue. Error:" , e ) ;
372+ }
373+
374+ // Register hooks using cached operations
375+ hookOperationsCache . forEach ( function ( cached ) {
376+ registerAllHooks ( cached . hook , target . category , callback , cached . built ) ;
151377 } ) ;
152378
153379} ) ;
0 commit comments