author : xingjun.xyf
 *  contact file time desc const fridaUnpack = require ( './android/unpack/fridaUnpack' ) ; import { Anti } from "./android/Anti" ; import { Jni } from "./android/jnimgr" ; import { FCCommon } from "./FCCommon" ; import { DMLog } from "./dmlog" ; export namespace FCAnd { export const anti = Anti ; export const jni = Jni ; export const common = FCCommon ; var firstdiscovery = false ; export function getStacks ( ) { return Java . use ( "android.util.Log" ) . getStackTraceString ( Java . use ( "java.lang.Exception" ) . $new ( ) ) + "" ; } export function showStacks ( ) { Java . perform ( function ( ) { DMLog . d ( 'showStacks' , getStacks ( ) ) ; } ) ; } export function hook_uri ( bShowStacks ) { const Uri = Java . use ( 'android.net.Uri' ) ; Uri . parse . implementation = function ( str ) { DMLog . i ( 'hook_uri' , 'str: ' + str ) ; if ( bShowStacks ) { showStacks ( ) ; } return this . parse ( str ) ; } ; } export function hook_url ( bShowStacks ) { const URL = Java . use ( 'java.net.URL' ) ; URL . $init . overload ( 'java.lang.String' ) . implementation = function ( url ) { DMLog . i ( 'hook_url' , 'url: ' + url ) ; if ( bShowStacks ) { showStacks ( ) ; } return this . $init ( url ) ; } ; } export function hook_JSONObject_getString ( pKey ) { const JSONObject = Java . use ( 'org.json.JSONObject' ) ; JSONObject . getString . implementation = function ( key ) { if ( key == pKey ) { DMLog . i ( 'hook_JSONObject_getString' , 'found key: ' + key ) ; showStacks ( ) ; } return this . getString ( key ) ; } ; } export function hook_fastJson ( pKey ) { const fastJson = Java . use ( 'com/alibaba/fastjson/JSONObject' ) ; fastJson . getString . implementation = function ( key ) { if ( key == pKey ) { DMLog . i ( 'hook_fastJson getString' , 'found key: ' + key ) ; showStacks ( ) ; } return this . getString ( key ) ; } ; fastJson . getJSONArray . implementation = function ( key ) { if ( key == pKey ) { DMLog . i ( 'hook_fastJson getJSONArray' , 'found key: ' + key ) ; showStacks ( ) ; } return this . getString ( key ) ; } ; fastJson . getJSONObject . implementation = function ( key ) { if ( key == pKey ) { DMLog . i ( 'hook_fastJson getJSONObject' , 'found key: ' + key ) ; showStacks ( ) ; } return this . getString ( key ) ; } ; fastJson . getInteger . implementation = function ( key ) { if ( key == pKey ) { DMLog . i ( 'hook_fastJson getJSONObject' , 'found key: ' + key ) ; showStacks ( ) ; } return this . getString ( key ) ; } ; } export function hook_Map ( pKey , accurately ) { const Map = Java . use ( 'java.util.Map' ) ; Map . put . implementation = function ( key , val ) { var bRes = false ; if ( accurately ) { bRes = ( key + "" ) == ( pKey ) ; } else { bRes = ( key + "" ) . indexOf ( pKey ) > - 1 ; } if ( bRes ) { DMLog . i ( 'map' , 'key: ' + key ) ; DMLog . i ( 'map' , 'val: ' + val ) ; showStacks ( ) ; } this . put ( key , val ) ; } ; const LinkedHashMap = Java . use ( 'java.util.LinkedHashMap' ) ; LinkedHashMap . put . implementation = function ( key1 , val ) { var bRes = false ; if ( accurately ) { bRes = ( key1 + "" ) == ( pKey ) ; } else { bRes = ( key1 + "" ) . indexOf ( pKey ) > - 1 ; } if ( null != key1 && bRes ) { DMLog . i ( 'LinkedHashMap' , 'key: ' + key1 ) ; DMLog . i ( 'LinkedHashMap' , 'val: ' + val ) ; showStacks ( ) ; } return this . put ( key1 , val ) ; } ; } export function hook_log ( ) { const Log = Java . use ( 'android.util.Log' ) ; Log . d . overload ( 'java.lang.String' , 'java.lang.String' ) . implementation = function ( tag , content ) { DMLog . i ( 'Log d' , 'tag: ' + tag + ', content: ' + content ) ; return 0 ; } ; Log . v . overload ( 'java.lang.String' , 'java.lang.String' ) . implementation = function ( tag , content ) { DMLog . i ( 'Log v' , 'tag: ' + tag + ', content: ' + content ) ; return 0 ; } ; Log . i . overload ( 'java.lang.String' , 'java.lang.String' ) . implementation = function ( tag , content ) { DMLog . i ( 'Log i' , 'tag: ' + tag + ', content: ' + content ) ; return 0 ; } ; Log . w . overload ( 'java.lang.String' , 'java.lang.String' ) . implementation = function ( tag , content ) { DMLog . i ( 'Log w' , 'tag: ' + tag + ', content: ' + content ) ; return 0 ; } ; Log . e . overload ( 'java.lang.String' , 'java.lang.String' ) . implementation = function ( tag , content ) { DMLog . i ( 'Log e' , 'tag: ' + tag + ', content: ' + content ) ; return 0 ; } ; Log . wtf . overload ( 'java.lang.String' , 'java.lang.String' ) . implementation = function ( tag , content ) { DMLog . i ( 'Log wtf' , 'tag: ' + tag + ', content: ' + content ) ; return 0 ; } ; } export function dump_dex_common ( ) { fridaUnpack . unpack_common ( ) ; } export function traceLoadlibrary ( ) { const dlopen_ptr = Module . findExportByName ( null , 'dlopen' ) ; if ( null != dlopen_ptr ) { DMLog . i ( 'traceLoadlibrary' , 'dlopen_ptr: ' + dlopen_ptr ) ; Interceptor . attach ( dlopen_ptr , { onEnter : function ( args ) { DMLog . i ( 'traceLoadlibrary' , 'loadlibrary: ' + args [ 0 ] . readCString ( ) ) ; } } ) ; } else { DMLog . e ( 'traceLoadlibrary' , 'dlopen_ptr is null' ) ; } } export function showModules ( ) { const modules = Process . enumerateModules ( ) ; modules . forEach ( function ( value , index , array ) { DMLog . i ( 'showModules' , JSON . stringify ( value ) ) ; } ) ; } export function traceFopen ( ) { const open_ptr = Module . findExportByName ( null , 'fopen' ) ; if ( null != open_ptr ) { DMLog . i ( 'traceFopen' , 'fopen_ptr: ' + open_ptr ) ; Interceptor . attach ( open_ptr , { onEnter : function ( args ) { DMLog . i ( 'traceFopen' , 'file_path: ' + args [ 0 ] . readCString ( ) ) ; } } ) ; } else { DMLog . e ( 'traceFopen' , 'fopen_ptr is null' ) ; } } param { NativePointer } addr param { string } str export function writeMemory ( addr , str ) { Memory . protect ( addr , str . length , 'rwx' ) ; addr . writeAnsiString ( str ) ; } param res returns { any } export function newString ( res ) { if ( null == res ) { return null ; } const String = Java . use ( 'java.lang.String' ) ; return String . $new ( res ) ; } export function getApplicationContext ( ) { const ActivityThread = Java . use ( 'android.app.ActivityThread' ) ; const Context = Java . use ( 'android.content.Context' ) ; const ctx = Java . cast ( ActivityThread . currentApplication ( ) . getApplicationContext ( ) , Context ) ; return ctx ; } param jbytes export function printByteArray ( jbytes ) { var result = "" ; for ( var i = 0 ; i < jbytes . length ; ++ i ) { result += " " ; result += jbytes [ i ] . toString ( 16 ) ; } return result ; } /**
     * trace java methods 默认类
     */ export const tjm_default_cls = [ 'M:Base64' , 'M:javax.crypto' , 'M:java.security' , 'E:java.lang.String' , ] ; /**
     * trace java methods 对 java.lang.String 类中的默认白名单方法名
     */ export const tjm_default_white_detail = { 'java.lang.String' : { white : true , methods : [ 'toString' , 'getBytes' ] } } ; param clazzes param clsWhitelist param stackFilter export function traceArtMethods ( clazzes ? , clsWhitelist ? , stackFilter ? ) { traceJavaMethods ( clazzes , clsWhitelist , stackFilter ) ; } param clazzes param clsWhitelist stackFilter export function traceJavaMethods ( clazzes ? , clsWhitelist ? , stackFilter ? ) { let dest_cls = [ ] ; let dest_white = { ... tjm_default_white_detail , ... clsWhitelist } ; if ( clazzes != null ) { dest_cls = tjm_default_cls . concat ( clazzes ) ; } else { dest_cls = tjm_default_cls ; } traceJavaMethods_custom ( dest_cls , dest_white , stackFilter ) ; } param clazzes param clsWhitelist param stackFilter export function traceJavaMethods_custom ( clazzes , clsWhitelist ? , stackFilter ? ) { function match ( destCls , curClsName ) { let mode = destCls [ 0 ] ; let ex = destCls . substr ( 2 ) ; if ( mode == 'E' ) { return ex == curClsName ; } else { return curClsName . match ( ex ) ; } } function sendContent ( obj ) { let str = JSON . stringify ( obj ) ; let stacks = null ; if ( null != stackFilter && str . indexOf ( stackFilter ) > - 1 ) { stacks = getStacks ( ) ; obj [ 'stacks' ] = stacks ; if ( false == firstdiscovery ) { obj [ 'firstdiscovery' ] = true ; firstdiscovery = true ; } str = JSON . stringify ( obj ) ; } send ( str ) ; } function getMethodDescription ( clsname , overload ) { let argumentTypes = overload . argumentTypes . map ( val => val . className ) . toString ( ) ; let desc = `${ overload . returnType . className } ${ clsname }#${ overload . methodName }(${ argumentTypes })` ; return desc ; } function traceJavaMethodsCore ( clsname ) { const tag = 'traceJavaMethodsCore' ; let detail = null ; if ( null != clsWhitelist ) { detail = clsWhitelist [ clsname ] ; } let cls = Java . use ( clsname ) ; let methods = cls . class . getDeclaredMethods ( ) ; DMLog . i ( tag , 'trace cls: ' + clsname + ', method size: ' + methods . length ) ; methods . forEach ( function ( method ) { let methodName = method . getName ( ) ; if ( null != detail && typeof ( detail ) == 'object' ) { if ( ( detail . methods . indexOf ( methodName ) > - 1 ) != detail . white ) { return true ; } } if ( 'invoke' == methodName ) { return true ; } let methodOverloads = cls [ methodName ] . overloads ; if ( null != methodOverloads ) { methodOverloads . forEach ( function ( overload ) { try { let methodDesc = getMethodDescription ( clsname , overload ) ; DMLog . i ( tag , 'hookmethod: ' + methodDesc ) ; overload . implementation = function ( ) { let tid = Process . getCurrentThreadId ( ) ; let tname = Java . use ( "java.lang.Thread" ) . currentThread ( ) . getName ( ) ; sendContent ( { tid : tid , status : 'entry' , tname : tname , classname : clsname , method : methodDesc , args : arguments } ) ; let retval = overload . apply ( this , arguments ) ; sendContent ( { tid : tid , status : 'exit' , tname : tname , classname : clsname , method : methodDesc , retval : retval } ) ; return retval ; } ; } catch ( e ) { DMLog . d ( tag , 'overload.implementation exception:\t' + overload . methodName + "\t" + e . toString ( ) ) ; } } ) ; } } ) ; let constructors = cls . class . getConstructors ( ) ; if ( null != constructors && ( null == detail || detail . methods . indexOf ( '$init' ) > - 1 ) ) { try { let methodOverloads = cls [ '$init' ] . overloads ; methodOverloads . forEach ( function ( overload ) { overload . implementation = function ( ) { let tid = Process . getCurrentThreadId ( ) ; let tname = Java . use ( "java.lang.Thread" ) . currentThread ( ) . getName ( ) ; sendContent ( { tid : tid , status : 'entry' , tname : tname , classname : clsname + '_$init' , method : overload . holder . toString ( ) , method_ : overload . _p [ 0 ] , args : arguments } ) ; const retval = this [ '$init' ] . apply ( this , arguments ) ; sendContent ( { tid : tid , status : 'exit' , tname : tname , classname : clsname , method : overload . holder . toString ( ) , retval : retval } ) ; return retval ; } ; } ) ; } catch ( e ) { } } } Java . enumerateLoadedClassesSync ( ) . forEach ( ( curClsName , index , array ) => { clazzes . forEach ( ( destCls ) => { if ( match ( destCls , curClsName ) ) { traceJavaMethodsCore ( curClsName ) ; return false ; } } ) ; } ) ; } export function toJSONString ( obj ) { if ( null == obj ) { return "obj is null" ; } let resstr = "" ; let GsonBuilder = null ; try { GsonBuilder = Java . use ( 'com.google.gson.GsonBuilder' ) ; } catch ( e ) { FCAnd . registGson ( ) ; GsonBuilder = Java . use ( 'com.google.gson.GsonBuilder' ) ; } if ( null != GsonBuilder ) { try { const gson = GsonBuilder . $new ( ) . serializeNulls ( ) . serializeSpecialFloatingPointValues ( ) . disableHtmlEscaping ( ) . setLenient ( ) . create ( ) ; resstr = gson . toJson ( obj ) ; } catch ( e ) { DMLog . e ( 'gson.toJson' , 'exceipt: ' + e . toString ( ) ) ; resstr = FCAnd . parseObject ( obj ) ; } } return resstr ; } export function parseObject ( data ) { try { const declaredFields = data . class . getDeclaredFields ( ) ; let res = { } ; for ( let i = 0 ; i < declaredFields . length ; i ++ ) { const field = declaredFields [ i ] ; field . setAccessible ( true ) ; const type = field . getType ( ) ; let fdata = field . get ( data ) ; if ( null != fdata ) { if ( type . getName ( ) != "[B" ) { fdata = fdata . toString ( ) ; } else { fdata = Java . array ( 'byte' , fdata ) ; fdata = JSON . stringify ( fdata ) ; } } res [ field . getName ( ) ] = fdata ; } return JSON . stringify ( res ) ; } catch ( e ) { return "parseObject except: " + e . toString ( ) ; } } export function registGson ( ) { try { let dexpath = '/data/local/tmp/fclibs/gson.jar' ; Java . openClassFile ( dexpath ) . load ( ) ; } catch ( e ) { DMLog . e ( 'registGson' , 'exception, please try to run `setupAndorid.py`' ) ; } } param clsname param callback export function useWithDexClassLoader ( clsname , callback ) { const tag = 'useWithDexClassLoader' ; var dexclassLoader = Java . use ( "dalvik.system.DexClassLoader" ) ; dexclassLoader . $init . implementation = function ( dexPath , optimizedDirectory , librarySearchPath , parent ) { DMLog . d ( tag , "dexPath: " + dexPath ) ; DMLog . d ( tag , "optimizedDirectory: " + optimizedDirectory ) ; DMLog . d ( tag , "librarySearchPath: " + librarySearchPath ) ; DMLog . d ( tag , "parent: " + parent ) ; this . $init ( dexPath , optimizedDirectory , librarySearchPath , parent ) ; let cls = this . loadClass ( clsname ) ; if ( null != cls ) { DMLog . w ( 'dex_loadclass' , 'found: ' + clsname ) ; callback ( cls ) ; } } ; } param clsname param callback export function useWhenLoadClass ( clsname , callback ) { const ClassLoader = Java . use ( 'java.lang.ClassLoader' ) ; ClassLoader . loadClass . overload ( 'java.lang.String' ) . implementation = function ( name ) { const cls = this . loadClass ( name ) ; if ( name . indexOf ( clsname ) > - 1 ) { DMLog . w ( 'useWhenLoadClass' , `name: ${ clsname } matched!` ) ; try { const clsFactory = Java . ClassFactory . get ( this ) ; const useCls = clsFactory . use ( clsname ) ; DMLog . e ( 'loadClass' , 'name: ' + name ) ; callback ( useCls ) ; } catch ( e ) { DMLog . e ( 'useWhenLoadClass' , 'exception: ' + e ) ; } } return cls ; } ; } param clsname param callback export function useWithInMemoryDexClassLoader ( clsname , callback ) { const tag = 'useWithInMemoryDexClassLoader' ; try { const InMemoryDexClassLoader = Java . use ( 'dalvik.system.InMemoryDexClassLoader' ) ; InMemoryDexClassLoader . $init . overload ( 'java.nio.ByteBuffer' , 'java.lang.ClassLoader' ) . implementation = function ( buff , loader ) { this . $init ( buff , loader ) ; let clsFactory = Java . ClassFactory . get ( this ) ; try { let result = clsFactory . use ( clsname ) ; DMLog . w ( tag , JSON . stringify ( result ) ) ; callback ( result ) ; } catch ( e ) { DMLog . e ( tag , `${ clsname } not found: ${ e }` ) ; } } ; } catch ( e ) { DMLog . e ( tag , e . toString ( ) ) ; } } export function useWithBaseDexClassLoader ( clsname , callback ) { const tag = 'useWithBaseDexClassLoader' ; var dexclassLoader = Java . use ( "dalvik.system.BaseDexClassLoader" ) ; dexclassLoader . $init . overload ( 'java.lang.String' , 'java.io.File' , 'java.lang.String' , 'java.lang.ClassLoader' ) . implementation = function ( dexPath , optimizedDirectory , librarySearchPath , parent ) { DMLog . d ( tag , "dexPath: " + dexPath ) ; DMLog . d ( tag , "optimizedDirectory: " + optimizedDirectory ) ; DMLog . d ( tag , "librarySearchPath: " + librarySearchPath ) ; DMLog . d ( tag , "parent: " + parent ) ; this . $init ( dexPath , optimizedDirectory , librarySearchPath , parent ) ; let clsFactory = Java . ClassFactory . get ( this ) ; try { let result = clsFactory . use ( clsname ) ; DMLog . w ( tag , JSON . stringify ( result ) ) ; callback ( result ) ; } catch ( e ) { DMLog . e ( tag , `${ clsname } not found: ${ e }` ) ; } } ; } export function showNativeStacks ( context ) { DMLog . i ( 'showNativeStacks' , '\tBacktrace:\n\t' + Thread . backtrace ( context , Backtracer . ACCURATE ) . map ( DebugSymbol . fromAddress ) . join ( '\n\t' ) ) ; } export function hook_send_recv ( ) { var myModule = Process . getModuleByName ( 'libc.so' ) ; var myFuncs = [ 'recv' , 'send' ] ; myModule . enumerateExports ( ) . filter ( module_export => module_export . type === 'function' && myFuncs . some ( fName => module_export . name . includes ( fName ) ) ) . forEach ( module_export => { Interceptor . attach ( module_export . address , { onEnter : function ( args ) { const tag = module_export . name + "_onEnter" ; var fd = args [ 0 ] . toInt32 ( ) ; var socktype = Socket . type ( fd ) ; var sockaddr = Socket . peerAddress ( fd ) ; if ( ( socktype !== 'tcp' && socktype !== 'tcp6' ) || sockaddr === null ) return ; try { var len = args [ 2 ] . toInt32 ( ) ; this . buf = new NativePointer ( args [ 1 ] ) ; var data = { 'event' : module_export . name , 'fd' : fd , 'sockaddr' : sockaddr , 'socktype' : socktype } ; DMLog . i ( tag , '\n' ) ; DMLog . i ( tag , JSON . stringify ( data ) ) ; FCAnd . showNativeStacks ( this . context ) ; } catch ( err ) { DMLog . e ( tag , err ) ; } } , onLeave : function ( retval ) { if ( undefined != this . buf ) { const retlen = retval . toInt32 ( ) ; DMLog . i ( module_export . name + '_onLeave' , "size:" + retval ) ; if ( - 1 != retlen ) { DMLog . i ( module_export . name + '_onLeave' , "\n" + hexdump ( this . buf , { offset : 0 , length : retlen , header : true , ansi : false } ) ) ; } } } } ) ; } ) ; } param addr param size param pattern param distarr export function replaceMemoryData ( addr , size , pattern , distarr , replaceAll ) { const tag = 'replaceMemoryData' ; let dest = Memory . scanSync ( addr , size , pattern ) ; if ( null != dest && dest . length > 0 ) { DMLog . i ( tag , 'found dest' ) ; if ( replaceAll ) { dest . forEach ( function ( match ) { match . address . writeByteArray ( distarr ) ; DMLog . i ( tag , "foreach replaced address: " + match . address ) ; } ) ; } else { dest [ 0 ] . address . writeByteArray ( distarr ) ; DMLog . i ( tag , "replaced address: " + dest [ 0 ] . address ) ; } } } param clsname export function findClass ( clsname ) { FCAnd . useWhenLoadClass ( clsname , function ( cls ) { DMLog . i ( 'findclass useWhenLoadClass' , "" + cls ) ; } ) ; FCAnd . useWithDexClassLoader ( clsname , function ( cls ) { DMLog . i ( 'findclass useWithDexClassLoader' , "" + cls ) ; } ) ; FCAnd . useWithBaseDexClassLoader ( clsname , function ( cls ) { DMLog . i ( 'findclass useWithBaseDexClassLoader' , "" + cls ) ; } ) ; FCAnd . useWithInMemoryDexClassLoader ( clsname , function ( cls ) { DMLog . i ( 'findclass useWithInMemoryDexClassLoader' , "" + cls ) ; } ) ; } param clsname param callback export function enumerateClassLoadersAndUse ( clsname , callback ) { const tag = 'enumerateClassLoadersAndUse' ; Java . enumerateClassLoaders ( { onMatch ( loader ) { try { let cls = loader . loadClass ( clsname ) ; if ( null != cls ) { DMLog . i ( tag , "found cls: " + cls ) ; let cf = Java . ClassFactory . get ( loader ) ; let cls1 = cf . use ( clsname ) ; callback ( cls1 ) ; } } catch ( e ) { DMLog . e ( tag , e . toString ( ) ) ; } } , onComplete ( ) { DMLog . i ( tag , 'completed .' ) ; } } ) ; } param soname param offsetAddr param callback export function attachWhenSoLoad ( soname , offsetAddr , callback ) { const VERSION = Java . use ( 'android.os.Build$VERSION' ) ; let dlopenFuncName = "android_dlopen_ext" ; if ( VERSION . SDK_INT . value <= 23 ) { dlopenFuncName = "dlopen" ; } Interceptor . attach ( Module . findExportByName ( null , dlopenFuncName ) ! , { onEnter : function ( args ) { this . sopath = args [ 0 ] . readCString ( ) ; } , onLeave : function ( retval ) { let sopath = this . sopath ; DMLog . d ( 'attachWhenSoLoad dlopen' , `sopath: ${ sopath }` ) ; if ( null != sopath && sopath . indexOf ( soname ) > - 1 ) { let mod = Module . load ( sopath ) ; Interceptor . attach ( mod . base . add ( offsetAddr ) , callback ) ; } } } ) ; } }