启动 1 2 3 4 5 6 7 8 9 adb push xxx.apk /sdcard/xxx.apk adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27043 adb shell su cd /data/local/tmp ./frida-server frida -U -l .\t1.js -f demo2.jni.com.myapplication
调试 add 调试参数 在xml里面加上
1 android:debuggable="true"
调试init_array和JNI_Onload 用frida spawn,加pause参数(16版本就–pause,14及以前的版本就不用加)。然后用ida attach后运行,frida命令行输入%resume后点击same就可以调试上,且可以调试init_array和JNI_Onload这些位置的函数
QBDI
问题大全 logcat中文乱码
kill frida-server 1 2 netstat -tunlp kill -9 {pid}
frida检测
遍历运行的进程列表从而检查fridaserver是否在运行
遍历data/local/tmp目录查看有无frida相关文件
遍历映射库,检测frida相关so
检查27042这个默认端口
内存中扫描frida特征字符串 “LIBFRIDA”
frida使用D-Bus协议通信,我们为每个开放的端口发送D-Bus的认证消息
检测有无frida特征的线程名,如:gum-js-loop
简单绕过检测
1 2 adb shell su -c "/data/local/tmp/hs1604 -l 0.0.0.0:1234 &" frida -H 127.0.0.1:1234 -f xxx -l xxx.js
frida-server和android_server的名字不要直接写本名,改成as,fs这种
用hluwa_server代替frida_server
server位置不要放在data/local/tmp,可以放在data/目录下
脚本 python脚本 加载脚本 1 2 3 4 5 6 7 8 9 10 11 import timeimport fridadevice = frida.get_remote_device() pid = device.spawn(["com.kanxue.pediy1" ]) device.resume(pid) time.sleep(1 ) session = device.attach(pid) with open ("s1.js" ) as f: script = session.create_script(f.read()) script.load() input ()
获取最前端进程 1 2 3 4 import fridardev = frida.get_remote_device() front_app = rdev.get_frontmost_application() print (front_app)
基础js脚本 枚举所有的类 enumerateLoadedClasses 这个函数拥有两个回调函数onMatch和onComplete,其中onMatch的参数就是类的信息。这个函数会遍历所有类,所以我们如果再onMatch处输出其参数就可以获得所有类的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 setTimeout (function ( ){ Java .perform (function ( ){ console .log ("n[*] enumerating classes..." ); Java .enumerateLoadedClasses ({ onMatch : function (_className ){ console .log ("[*] found instance of '" +_className+"'" ); }, onComplete : function ( ){ console .log ("[*] class enuemration complete" ); } }); }); });
Java.enumerateLoadedClassesSync()也能实现这个功能,不过它返回的是数组
显示安卓系统版本号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function frida_Java ( ) { Java .perform (function ( ) { if (Java .available ) { console .log ("" ,Java .androidVersion ); }else { console .log ("error" ); } }); } setImmediate (frida_Java,0 );
枚举类加载器 Java .enumerateClassLoaders 该api枚举的是java vm中的类加载器,也是俩回调函数,onMatch和onComplete,其中onMatch的参数是loader,因为是枚举类加载器嘛。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function frida_Java ( ) { Java .perform (function ( ) { if (Java .available ) { Java .enumerateClassLoaders ({ onMatch : function (loader ) { console .log ("" ,loader); }, onComplete : function ( ) { console .log ("end" ); } }); }else { console .log ("error" ); } }); } setImmediate (frida_Java,0 );
该api很重要,java.perform(fn)主要用于当前线程附加到java vm并调用fn方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function frida_Java ( ) { Java .perform (function ( ) { if (Java .available ) { console .log ("hello" ); }else { console .log ("error" ); } }); } setImmediate (frida_Java,0 );
也有个兄弟Java.performNow(fn)
获取类Java.use Java.use(classname),可以动态获取classname的定义,然后对其调用$new()可以调用构造函数,来从中实例化对象,想要回收类的时候可以调用$Dispose()方法显式释放
1 2 3 4 5 6 7 8 9 10 11 Java .perform (function ( ) { var Activity = Java .use ('android.app.Activity' ); var Exception = Java .use ('java.lang.Exception' ); Activity .onResume .implementation = function ( ) { throw Exception .$new('Oh noes!' ); }; });
扫描实例类Java.choose 堆上找实例化的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 Java .perform (function ( ) { Java .choose ("android.view.View" , { onMatch :function (instance ){ console .log (instance); }, onComplete :function ( ) { console .log ("end" ) }}); });
类型转换器Java.cast 将指定变量转换成需要的类型。通常在拦截so层时会用此函数将jstring,jarray转换后查看其值
定义数组Java.array js中定义java数组的api
1 2 3 4 5 6 7 8 9 10 11 Java .perform (function ( ) { var intarr = Java .array ('int' , [ 1003 , 1005 , 1007 ]); var bytearr = Java .array ('byte' , [ 0x48 , 0x65 , 0x69 ]); for (var i=0 ;i<bytearr.length ;i++) { console .log (bytearr[i]) } });
注册类Java.registerClass(spec) Java.registerClass
:创建一个新的Java
类并返回一个包装器,其中规范是一个包含:name
:指定类名称的字符串。superClass
:(可选)父类。要从 java.lang.Objec
t 继承的省略。implements
:(可选)由此类实现的接口数组。fields
:(可选)对象,指定要公开的每个字段的名称和类型。methods
:(可选)对象,指定要实现的方法。
1 2 3 4 5 6 7 Java .perform (function ( ) { var hellojni = Java .registerClass ({ name : 'com.roysue.roysueapplication.hellojni' }); console .log (hellojni.addInt (1 ,2 )); });
官方操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 var SomeBaseClass = Java .use ('com.example.SomeBaseClass' );var X509TrustManager = Java .use ('javax.net.ssl.X509TrustManager' );var MyWeirdTrustManager = Java .registerClass ({ name : 'com.example.MyWeirdTrustManager' , superClass : SomeBaseClass , implements : [X509TrustManager], fields : { description : 'java.lang.String' , limit : 'int' , }, methods : { $init : function ( ) { console .log ('Constructor called' ); }, checkClientTrusted : function (chain, authType ) { console .log ('checkClientTrusted' ); }, checkServerTrusted : [{ returnType : 'void' , argumentTypes : ['[Ljava.security.cert.X509Certificate;' , 'java.lang.String' ], implementation : function (chain, authType ) { console .log ('checkServerTrusted A' ); } }, { returnType : 'java.util.List' , argumentTypes : ['[Ljava.security.cert.X509Certificate;' , 'java.lang.String' , 'java.lang.String' ], implementation : function (chain, authType, host ) { console .log ('checkServerTrusted B' ); return null ; } }], getAcceptedIssuers : function ( ) { console .log ('getAcceptedIssuers' ); return []; }, } });
实现了证书类的javax.net.ssl.X509TrustManager
类,,这里就是相当于自己在目标进程中重新创建了一个类,实现了自己想要实现的类构造,重构造了其中的三个接口函数、从而绕过证书校验
Java.vm对象 可以拿到JNI层的JNIEnv对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function frida_Java ( ) { Java .perform (function ( ) { Interceptor .attach (Module .findExportByName ("libhello.so" , "Java_com_roysue_roysueapplication_hellojni_getStr" ), { onEnter : function (args ) { console .log ("getStr" ); }, onLeave :function (retval ){ var env = Java .vm .getEnv (); var jstring = env.newStringUtf ('roysue' ); retval.replace (jstring); console .log ("getSum方法返回值为:roysue" ) } }); } setImmediate (frida_Java,0 );
Interceptor.attach 该对象功能十分强大,函数原型是Interceptor.attach(target, callbacks)
:参数target
是需要拦截的位置的函数地址,也就是填某个so
层函数的地址即可对其拦截,target
是一个NativePointer
参数,用来指定你想要拦截的函数的地址,NativePointer
我们也学过是一个指针。需要注意的是对于Thumb
函数需要对函数地址+1
,callbacks
则是它的回调函数,分别是以下两个回调函数:
onEnter:
函数(args
):回调函数,给定一个参数args
,可用于读取或写入参数作为 NativePointer
对象的数组。
onLeave:
函数(retval
):回调函数给定一个参数 retval
,该参数是包含原始返回值的 NativePointer
派生对象。可以调用 retval.replace(1337)
以整数 1337
替换返回值,或者调用 retval.replace(ptr("0x1234"))
以替换为指针。请注意,此对象在 OnLeave
调用中回收,因此不要将其存储在回调之外并使用它。如果需要存储包含的值,请制作深副本,例如:ptr(retval.toString())
。
1 2 3 4 5 6 7 8 9 10 11 12 13 Interceptor .attach (Module .getExportByName ('libc.so' , 'read' ), { onEnter : function (args ) { this .fileDescriptor = args[0 ].toInt32 (); }, onLeave : function (retval ) { if (retval.toInt32 () > 0 ) { } } });
通过我们对Interceptor.attach
函数有一些基本了解了~它还包含一些属性。
索引
属性
含义
1
returnAddress
返回地址,类型是NativePointer
2
context
上下文:具有键pc
和sp
的对象,它们是分别为ia32/x64/arm
指定EIP/RIP/PC
和ESP/RSP/SP的NativePointer
对象。其他处理器特定的键也可用,例如eax、rax、r0、x0
等。也可以通过分配给这些键来更新寄存器值。
3
errno
当前errno
值
4
lastError
当前操作系统错误值
5
threadId
操作系统线程ID
6
depth
相对于其他调用的调用深度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function frida_Interceptor ( ) { Java .perform (function ( ) { Interceptor .attach (Module .findExportByName ("libhello.so" , "Java_com_roysue_roysueapplication_hellojni_getSum" ), { onEnter : function (args ) { console .log ('Context information:' ); console .log ('Context : ' + JSON .stringify (this .context )); console .log ('Return : ' + this .returnAddress ); console .log ('ThreadId : ' + this .threadId ); console .log ('Depth : ' + this .depth ); console .log ('Errornr : ' + this .err ); }, onLeave :function (retval ){ } }); }); } setImmediate (frida_Interceptor,0 );
Interceptor.detachAll 让attach附加拦截的回调函数失效
Interceptor.replace 替换自己实现的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function frida_Interceptor ( ) { Java .perform (function ( ) { var add_method = new NativeFunction (Module .findExportByName ('libhello.so' , 'c_getSum' ), 'int' ,['int' ,'int' ]); console .log ("result:" ,add_method (1 ,2 )); Interceptor .replace (add_method, new NativeCallback (function (a, b ) { return 123 ; }, 'int' , ['int' , 'int' ])); console .log ("result:" ,add_method (1 ,2 )); }); }
new NativePointer(s) nativePointer对象相当于指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function frida_NativePointer ( ) { Java .perform (function ( ) { const ptr1 = new NativePointer ("100" ); console .log ("ptr1:" ,ptr1); const ptr2 = new NativePointer ("0x64" ); console .log ("ptr2:" ,ptr2); const ptr3 = new NativePointer (100 ); console .log ("ptr3:" ,ptr3); }); } setImmediate (frida_NativePointer,0 );输出如下,都会自动转为十六进制的0x64 ptr1 : 0x64 ptr2 : 0x64 ptr3 : 0x64
运算符和指针读写api
下脚本是readByteArray示例
1 2 3 4 5 6 7 8 9 10 function frida_NativePointer ( ) { Java .perform (function ( ) { console .log ("" ); var pointer = Process .findModuleByName ("libc.so" ).base ; console .log (pointer.readByteArray (0x10 )); }); } setImmediate (frida_NativePointer,0 );
输出就是libc.so的前16个字节
readPointer() 定义是从此内存位置读取NativePointer
1 2 3 4 5 var pointer = Process .findModuleByName ("libc.so" ).base ;console .log (pointer.readByteArray (0x10 ));console .log ("readPointer():" +pointer.readPointer ());输出如下。 readPointer ():0x464c457f
writePointer(ptr) 读取ptr指针地址到当前指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 console .log ("pointer :" +pointer); const r = Memory .alloc (4 ); r.writePointer (pointer); var buffer = Memory .readByteArray (r, 4 ); console .log (buffer); 输出如下。 pointer :0xf588f000 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 00 f0 88 f5 ....
readS32()、readU32() 从该内存位置读取有符号或无符号8/16/32/etc
或浮点数/双精度值,并将其作为数字返回。这里拿readS32()、readU32()
作为演示.
1 2 3 4 5 6 7 8 9 10 11 console .log (pointer.readS32 ()); console .log (pointer.readU32 ()); 输出如下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF ............1179403647 == 0x464c457f 1179403647 == 0x464c457f
writeS32()、writeU32() 将有符号或无符号8/16/32/
等或浮点数/双精度值写入此内存位置。
1 2 3 4 5 6 7 8 9 10 const r = Memory .alloc (4 ); r.writeS32 (0x12345678 ); console .log (r.readByteArray (0x10 )); 输出如下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 78 56 34 12 00 00 00 00 00 00 00 00 00 00 00 00 xV4.............
readByteArray(length))、writeByteArray(bytes) readByteArray(length))
连续读取内存length
个字节,、writeByteArray
连续写入内存bytes
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var arr = [ 0x72 , 0x6F , 0x79 , 0x73 , 0x75 , 0x65 ]; const r = Memory .alloc (arr.length ); Memory .writeByteArray (r,arr); var buffer = Memory .readByteArray (r, arr.length ); console .log ("Memory.readByteArray:" ); console .log (hexdump (buffer, { offset : 0 , length : arr.length , header : true , ansi : false })); 输出如下。 Memory .readByteArray : 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 00000000 72 6f 79 73 75 65 roysue
readCString([size = -1])、writeUtf8String(str) readCString
功能是读取指针地址位置的字节字符串,对应的writeUtf8String
是写入指针地址位置的字符串处。(这里的r
是接着上面的代码的变量)。
1 2 3 4 5 6 console .log ("readCString():" +r.readCString ());const newPtrstr = r.writeUtf8String ("haha" );console .log ("readCString():" +newPtrstr.readCString ());
NativeFunction 创建新的NativeFunction
以调用address
处的函数(用NativePointer
指定),其中return Type
指定返回类型,argTypes
数组指定参数类型。如果不是系统默认值,还可以选择指定ABI
。对于可变函数,添加一个‘.’固定参数和可变参数之间的argTypes
条目,我们来看看官方的例子。
1 2 3 4 5 6 7 8 var friendlyFunctionName = new NativeFunction (friendlyFunctionPtr, 'void' , ['pointer' , 'pointer' ]); var returnValue = Memory .alloc (sizeOfLargeObject);friendlyFunctionName (returnValue, thisPtr);
函数定义格式为new NativeFunction(address, returnType, argTypes[, options]),
参照这个格式能够创建函数并且调用!returnType和argTypes[,]
分别可以填void、pointer、int、uint、long、ulong、char、uchar、float、double、int8、uint8、int16、uint16、int32、uint32、int64、uint64
这些类型,根据函数的所需要的type来定义即可。
在定义的时候必须要将参数类型个数和参数类型以及返回值完全匹配,假设有三个参数都是int
,则new NativeFunction(address, returnType, ['int', 'int', 'int'])
,而返回值是int
则new NativeFunction(address, 'int', argTypes[, options])
,必须要全部匹配,并且第一个参数一定要是函数地址指针。
NativeCallback对象 new NativeCallback(func,rereturn Type,argTypes[,ABI]):
创建一个由JavaScript
函数func
实现的新NativeCallback
,其中return Type
指定返回类型,argTypes
数组指定参数类型。您还可以指定ABI
(如果不是系统默认值)。有关支持的类型和Abis的详细信息,请参见NativeFunction
。注意,返回的对象也是一个NativePointer
,因此可以传递给Interceptor#replace
。当将产生的回调与Interceptor.replace()
一起使用时,将调用func,并将其绑定到具有一些有用属性的对象,就像Interceptor.Attach()
中的那样。我们来看一个例子。如下,利用NativeCallback
做一个函数替换。
1 2 3 4 5 6 7 8 9 10 Java .perform (function ( ) { var add_method = new NativeFunction (Module .findExportByName ('libhello.so' , 'c_getSum' ), 'int' ,['int' ,'int' ]); console .log ("result:" ,add_method (1 ,2 )); Interceptor .replace (add_method, new NativeCallback (function (a, b ) { return 123 ; }, 'int' , ['int' , 'int' ])); console .log ("result:" ,add_method (1 ,2 )); });
string2byte 用于将jdk的string转成byte形式
1 2 3 4 function stringToBytes (str ) { var javaString = Java .use ('java.lang.String' ); return javaString.$new(str).getBytes (); }
context参数调用 用于hook时参数类型有context的情况
1 Java .use ('android.app.ActivityThread' ).currentApplication ();
js脚本收集 java层主动调用 如果方法不是静态,可以通过下方的new或者用choose先获得内存中类实例然后调用类方法
1 2 3 4 5 6 7 8 9 function hook_decrypt ( ){ Java .perform (function ( ){ var a = new Int8Array ([114 ,514 ]) var verify = Java .use ({class_name}).$new(); var bignum = Java .use ("java.math.BigInteger" ).$new(114514 ); verify.decrypt (a, bignum).overload ('[B' , 'java.math.BigInteger' ); }) }
如果是静态,不用new
1 2 var verify = Java .use ("{class_name}" );verify.verifyPassword (a, b);
ssl unpinning(okhttp4.9.1) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 try { var CertificatePinner = Java .use ('okhttp3.CertificatePinner' ); CertificatePinner .check .overload ('java.lang.String' , 'java.util.List' ).implementation = function (str ) { writeFile ('! Intercepted okhttp4 in [check()]: ' + str); return ; }; try { CertificatePinner .check$okhttp .implementation = function (str, _ ) { writeFile ('! Intercepted okhttp4 in [check$okhttp]: ' + str); return ; }; } catch (ex) { writeFile ("is this Okhttp3 ?!" ); } writeFile ('* Setup okhttp4 pinning' ) } catch (err) { writeFile ('* Unable to hook into okhttp4 pinner' ) writeFile (err); }
hook dlopen 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function hook_dlopen ( ) { var dlopenAddr = Module .findExportByName (null , "dlopen" ); if (dlopenAddr == null ) { dlopenAddr = Module .findExportByName (null , "android_dlopen_ext" ); } Interceptor .attach (dlopenAddr, { onEnter : function (args ) { var pathptr = args[0 ]; if (pathptr !== undefined && pathptr != null ) { var path = ptr (pathptr).readCString (); console .log ("[dlopen]" , path); this .can_hook_lib = false ; this .can_hook_libmono = false ; if (path.indexOf ("libil2cpp.so" ) >= 0 ) { this .can_hook_lib = true ; } } }, onLeave : function (retval ) { if (this .can_hook_lib ) { var libso_address = Module .findBaseAddress ("libil2cpp.so" ) hook_input (libso_address) } } }) }
hook exit 1 2 3 4 var sysexit = Java .use ("java.lang.System" );sysexit.exit .implementation = function (a0 ){ console .log ("bypass exit" ); }
hook pthread_create 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function pthread_hook ( ){ var pthread_create_origin = Module .findExportByName ("libc.so" ,"pthread_create" ); var pthread_create_replace = new NativeFunction (pthread_create_origin,"int" ,["pointer" ,"pointer" ,"pointer" ,"pointer" ]); Interceptor .replace (pthread_create_origin,new NativeCallback (function (ptr0,ptr1,ptr2,ptr3 ){ var retval = ptr (0 ); if (ptr1.isNull () && ptr3.isNull ()){ send ("bypass the pthread" ); } else { retval = pthread_create_replace (ptr0,ptr1,ptr2,ptr3); } send ("pthread execute next is print_secert" ); print_secert (); return retval; },"int" ,["pointer" ,"pointer" ,"pointer" ,"pointer" ])); }
stalker简单使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Interceptor .attach (tatgetAddr, { onEnter : function (args ){ console .log ("enter tatgetAddr" ); this .pid = Process .getCurrentThreadId (); Stalker .follow (this .pid ,{ events :{ call :false , ret :false , exec :false , block :false , compile :false }, onReceive :function (events ){ }, transform : function (iterator ) { var instruction = iterator.next (); const startAddress = instruction.address ; if (count <= 100 ){ do { console .log (instruction.address .sub (base_addr) + "\t:\t" + instruction); iterator.keep (); } while ((instruction = iterator.next ()) !== null ); } count += 1 ; }, onCallSummary :function (summary ){ } }); },onLeave : function (retval ){ Stalker .unfollow (this .pid ); console .log ("leave tatgetAddr" ); sleep (5000000000000 ).then (()=> {}) } });
打印调用栈的log 1 console .log (Java .use ("android.util.Log" ).getStackTraceString (Java .use ("java.lang.Throwable" ).$new()));
可以输出调用栈
找interface的实现
hook onclick 可以加上上方的打印调用栈的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 var jclazz = null ;var jobj = null ;function getObjClassName (obj ) { if (!jclazz) { var jclazz = Java .use ("java.lang.Class" ); } if (!jobj) { var jobj = Java .use ("java.lang.Object" ); } return jclazz.getName .call (jobj.getClass .call (obj)); } function watch (obj, mtdName ) { var listener_name = getObjClassName (obj); var target = Java .use (listener_name); if (!target || !mtdName in target) { return ; } target[mtdName].overloads .forEach (function (overload ) { overload.implementation = function ( ) { console .log ("[WatchEvent] " + mtdName + ": " + getObjClassName (this )) return this [mtdName].apply (this , arguments ); }; }) } function OnClickListener ( ) { Java .perform (function ( ) { Java .use ("android.view.View" ).setOnClickListener .implementation = function (listener ) { if (listener != null ) { watch (listener, 'onClick' ); } return this .setOnClickListener (listener); }; Java .choose ("android.view.View$ListenerInfo" , { onMatch : function (instance ) { instance = instance.mOnClickListener .value ; if (instance) { console .log ("mOnClickListener name is :" + getObjClassName (instance)); watch (instance, 'onClick' ); } }, onComplete : function ( ) { } }) }) } setImmediate (OnClickListener );
hook linker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 var mprotect_cnt = 0 function sleep (delay ) { var start = (new Date ()).getTime (); while ((new Date ()).getTime () - start < delay) { continue ; } } function hook_svc_mprotect ( ) { let base_svc_mprotect = Module .findBaseAddress ("libSecShell.so" ); if (base_svc_mprotect != null ) { console .log ("base_svc_mprotect : " + base_svc_mprotect) }else { return ; } let svc_mprotect = base_svc_mprotect.add (0xC0778 ); Interceptor .attach (svc_mprotect, { onEnter : function (args ) { console .log ("==========================================" ) console .log ("svc_mprotect: start = " + args[0 ] + " , len = " + args[1 ] + " , ATTRIBUTES = " + args[2 ]) mprotect_cnt += 1 console .log (hexdump (base_svc_mprotect.add (0x281B4 ))) }, onLeave : function ( ){ console .log ("svc_mprotect leave" ) console .log ("==========================================" ) } }) } function dis (address, number ) { for (var i = 0 ; i < number; i++) { var ins = Instruction .parse (address); console .log ("address:" + address + "--dis:" + ins.toString ()); address = ins.next ; } } function hook ( ) { var linkermodule if (Process .pointerSize == 4 ) { linkermodule = Process .findModuleByName ("linker" ); }else if (Process .pointerSize == 8 ) { linkermodule = Process .findModuleByName ("linker64" ); } var call_function_addr = null ; var symbols = linkermodule.enumerateSymbols (); for (var i = 0 ; i < symbols.length ; i++) { var symbol = symbols[i]; if (symbol.name .indexOf ("__dl__ZL13call_functionPKcPFviPPcS2_ES0_" ) != -1 ) { call_function_addr = symbol.address ; } } Interceptor .attach (call_function_addr, { onEnter : function (args ) { var type = ptr (args[0 ]).readUtf8String (); var address = args[1 ]; var sopath = ptr (args[2 ]).readUtf8String (); console .log ("loadso:" + sopath + "--addr:" + address + "--type:" + type); if (sopath.indexOf ("libSecShell.so" ) != -1 ) { var libnativemodule = Process .getModuleByName ("libSecShell.so" ); var base = libnativemodule.base ; hook_svc_mprotect () } } }) } function main ( ) { hook (); } setImmediate (main)
frida指令收集 adb指令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 # 查看当前连接设备 adb devices # 多个设备时指定设备 adb -s 设备号 其他指令 # adb -s 127.0.0.1:21503 shell # adb -s FA6AE0309067 shell # 127.0.0.1:5555 蓝叠 # 127.0.0.1:7555 MUMU模拟器 # 127.0.0.1:62001 夜游神模拟器 # 127.0.0.1:21503 逍遥模拟器 # 查看Android处理器架构 adb shell getprop ro.product.cpu.abi # 安装APP adb install xxx.apk # 安装APP,已经存在,覆盖安装 adb install -r xxx.apk # 卸载APP adb uninstall app包名 # 卸载APP,保留数据 adb uninstall -k app包名 # 往手机传递文件 adb push 文件名 手机路径 # 从手机端获取文件 adb pull 手机路径/文件名 # 移动文件 mv /sdcard/Download/frida-server-12.8.0 /data/local/tmp/ # 修改文件权限 chmod 777 /data/local/tmp/frida-server-12.8.0 # 查看日志 adb logcat # 清日志 adb logcat -c # 手机端安装的所有app包名 adb shell pm list packages # 查看当前包名和主Activity adb shell dumpsys window | findstr mCurrentFocus # 启动APP adb shell am start 包名/主Activity # adb shell am start com.autonavi.minimap/com.autonavi.map.activity.NewMapActivity # 关闭App adb shell am force-stop 包名 # adb shell am force-stop com.autonavi.minimap # 屏幕截图 adb shell screencap 手机路径/xxx.png # 录制视频 adb shell screenrecord 手机路径/xxx.mp4
frida命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 启动frida-server(模拟器) ./data/local/tmp/frida-server-12.8.0-android-x86 # 启动frida-server(Pixel真机) ./data/local/tmp/frida-server-12.8.0-android-arm64 # 转发端口 adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27043 # 列举出来所有连接到电脑上的设备 frida-ls-devices # 连接到指定设备 frida-ps -D tcp # 列举出来设备上的所有进程 frida-ps -U # 列举出来设备上的所有应用程序 frida-ps -Ua # 列举出来设备上的所有已安装应用程序和对应的名字 frida-ps -Uai # 跟踪某个函数 frida-trace -U -f Name -i "函数名" # frida-trace -U -f com.autonavi.minimap -i "getRequestParams" # 跟踪某个方法 frida-trace -U -f Name -m "方法名" # frida-trace -U -f com.autonavi.minimap -m "MapLoader"