frida环境搭建
根据机器cpu架构选择下载frida: frida sever
解压获得frida-server-x.x.x-x,将该文件上传至Android /data/local/tmp下
赋予该文件执行权限,chmod 777 frida-server-x.x.x-x
运行该文件, ./frida-server-x.x.x-x
python安装frida,安装的版本一定与下载的文件版本对应
1 pip3 install frida-tools
frida工具介绍 安装完成后在python的Scripts目录下会有frida ,frida-trace,frida-ps,frida-discover ,frida-kill ,frida-ls-devices,6个EXE可执行文件。
frida 1. frida -U 包名 ---调试连接到电脑上设备中的应用
2. frida 进程名 ---打开本地进程
3. frida -U -f 进程名 -l 脚本 --- 进程注入js脚本
frida–ps 用于列出进程的命令行工具
1. frida-ps -U ---列出设备上的所有进程
2. frida-ps -Ua ---列出设备上的所有应用名称
3. frida-ps -Uai ---列举出来设备上的所有已安装应用程序和对应的名字
4. frida-ps -D PID ---连接指定设备
5. frida-ps -D <DEVICE-ID> -a ---检查进程是否被杀死
frida-trace 动态跟踪函数调用的工具
1. frida-trace -i "recv" 包名 跟踪recv方法
2. frida-trace -m "-[NSString isEqualToString]" 包名 ---追踪oc isEqualToString方法
frida-discover 1. frida-discover -n 包名/frida-discover -p pid ---发现程序内部函数
frida-kill 终止进程的命令行工具
1. frida-kill -D <DEVICE-ID> <PID> ---杀死进程
frida-ls-devices
列出所有连接到电脑上的设备 注:
frida-ps -Uai 查看所有安装的应用。
frida-ps -D cca1b9055 -a -i 可以指定查看某个设备的进程。
frida-trace 用于跟踪函数或者 Objective-C 方法的调用-i 跟踪某个函数,-x 排除某个函数。-m 跟踪某个 Objective-C 方法,-M 排除某个 Objective-C 方法。-a 跟踪某一个地址,需要指名模块的名称。
frida编译 用于学习frida原理及去特征
编译环境
ubuntu
python3
node(v14.14.0)
NDK
编译过程
安装依赖
以下是编译需要的环境,如果有则不需要在安装,node推荐去官网下载(https://nodejs.org)
1 sudo apt-get install build-essential curl git lib32stdc++-9-dev libc6-dev-i386 nodejs npm python3-dev python3-pip
拉源码
1 git clone --recurse-submodules https://gi thub.com/frida/ frida.git
设置环境变量
1 2 3 4 androidNdk="/Users/cola/Library/Android/sdk/ndk/android-ndk-r22b" export PATH=$androidNdk :$PATH export ANDROID_NDK_ROOT=$androidNdk export NDK_HOME=$androidNdk
编译(第一次编译会下载编译工具,用梯子快一些不会出现奇怪的问题)
编译完成
编译后的文件在build目录下
1 2 3 build/frida-android-arm64/bin/frida-server build/frida-android-arm64/lib/frida-gadget.so .......
API hexdump 1 2 3 4 5 6 7 8 9 10 11 12 13 var libc = Module.findBaseAddress('libc.so' );console .log(hexdump(libc, { offset : 0 , length : 64 , header : true , ansi : true })); 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............00000010 03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00 ..(.........4 ...00000020 34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00 4. ......4 . ...(.00000030 1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00 ........4 ...4 ...
send send是在python层定义的on_message回调函数,jscode内所有的信息都被监控script.on(‘message’, on_message),当输出信息的时候on_message函数会拿到其数据再通过format转换, 其最重要的功能也是最核心的是能够直接将数据以json格式输出,当然数据是二进制的时候也依然是可以使用send,十分方便.
声明变量类型
索引
API
含义
1
new Int64(v)
定义一个有符号Int64类型的变量值为v,参数v可以是字符串或者以0x开头的的十六进制值
2
new UInt64(v)
定义一个无符号Int64类型的变量值为v,参数v可以是字符串或者以0x开头的的十六进制值
3
new NativePointer(s)
定义一个指针,指针地址为s
4
ptr(“0”)
定义一个指针
example:
1 2 3 4 5 6 7 8 9 10 11 12 13 Java.perform(function ( ) { console .log("" ); console .log("new Int64(1):" +new Int64(1 )); console .log("new UInt64(1):" +new UInt64(1 )); console .log("new NativePointer(0xEC644071):" +new NativePointer(0xEC644071 )); console .log("new ptr('0xEC644071'):" +new ptr(0xEC644071 )); }); 输出效果如下: new Int64(1 ):1 new UInt64(1 ):1 new NativePointer(0xEC644071 ):0xec644071 new ptr('0xEC644071' ):0xec644071
索引
API
含义
1
add(rhs)、sub(rhs)、and(rhs)、or(rhs)、xor(rhs)
加、减、逻辑运算
2
shr(N)、shl(n)
向右/向左移位n位生成新的Int64
3
Compare(Rhs)
返回整数比较结果
4
toNumber()
转换为数字
5
toString([radix=10])
转换为可选基数的字符串(默认为10)
example:
1 2 3 4 5 6 7 8 9 10 11 12 13 Java.perform(function ( ) { console .log("" ); console .log("new Int64(1):" +new Int64(1 )); console .log("new UInt64(1):" +new UInt64(1 )); console .log("new NativePointer(0xEC644071):" +new NativePointer(0xEC644071 )); console .log("new ptr('0xEC644071'):" +new ptr(0xEC644071 )); }); 输出效果如下: new Int64(1 ):1 new UInt64(1 ):1 new NativePointer(0xEC644071 ):0xec644071 new ptr('0xEC644071' ):0xec644071
Process对象 Process.id 返回附加目标进程的PID
Process.isDebuggerAttached() 检测当前是否对目标程序已经附加
Process.enumerateModules() 枚举当前加载的模块,返回模块对象的数组。
Process.enumerateModules()会枚举当前所有已加载的so模块,并且返回了数组Module对象.
example:
在js中能够直接使用Process对象的所有api,调用了Process.enumerateModules()方法之后会返回一个数组,数组中存储N个叫Module的对象,for循环遍历,使用下标的方式调用了Module对象的name属性,name是so模块的名称
1 2 3 4 5 6 7 8 9 function frida_Process() { Java.perform (function () { var process_Obj_Module_Arr = Process.enumerateModules(); for (var i = 0 ; i < process_Obj_Module_Arr.length; i++) { console.log("",process_Obj_Module_Arr[i].name); } }); } setImmediate(frida_Process,0 );
Process.enumerateThreads() 枚举当前所有的线程,返回包含以下属性的对象数组:
索引
属性
含义
1
id
线程id
2
state
当前运行状态有running, stopped, waiting, uninterruptible or halted
3
context
带有键pc和sp的对象,它们是分别为ia32/x64/arm指定EIP/RIP/PC和ESP/RSP/SP的NativePointer对象。也可以使用其他处理器特定的密钥,例如eax、rax、r0、x0等。
examlpe:
1 2 3 4 5 6 7 8 9 10 11 12 function frida_Process ( ) { Java.perform(function ( ) { var enumerateThreads = Process.enumerateThreads(); for (var i = 0 ; i < enumerateThreads.length; i++) { console .log("" ); console .log("id:" ,enumerateThreads[i].id); console .log("state:" ,enumerateThreads[i].state); console .log("context:" ,JSON .stringify(enumerateThreads[i].context)); } }); } setImmediate(frida_Process,0 );
Process.getCurrentThreadId() 获取此线程的操作系统特定 ID 作为数字
Module对象 Module对象的属性
索引
属性
含义
1
name
模块名称
2
base
模块地址,其变量类型为NativePointer
3
size
大小
4
path
完整文件系统路径
Module对象的API
索引
API
含义
1
Module.load()
加载指定so文件,返回一个Module对象
2
enumerateImports()
枚举所有Import库函数,返回Module数组对象
3
enumerateExports()
枚举所有Export库函数,返回Module数组对象
4
enumerateSymbols()
枚举所有Symbol库函数,返回Module数组对象
5
Module.findExportByName(exportName)、Module.getExportByName(exportName)
寻找指定so中export库中的函数地址
6
Module.findBaseAddress(name)、Module.getBaseAddress(name)
返回so的基地址
Module.load() 主要用于加载指定so文件,返回一个Module对象。
example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function frida_Module ( ) { Java.perform(function ( ) { const hooks = Module.load('libhello.so' ); console .log("模块名称:" ,hooks.name); console .log("模块地址:" ,hooks.base); console .log("大小:" ,hooks.size); console .log("文件系统路径" ,hooks.path); }); } setImmediate(frida_Module,0 ); 输出如下: 模块名称: libhello.so 模块地址: 0xdf2d3000 大小: 24576 文件系统路径 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/ arm/libhello.so
Process.EnumererateModules() 对上面的的Process.EnumererateModules()对象输出进行补全,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function frida_Module ( ) { Java.perform(function ( ) { var process_Obj_Module_Arr = Process.enumerateModules(); for (var i = 0 ; i < process_Obj_Module_Arr.length; i++) { if (process_Obj_Module_Arr[i].path.indexOf("hello" )!=-1 ) { console .log("模块名称:" ,process_Obj_Module_Arr[i].name); console .log("模块地址:" ,process_Obj_Module_Arr[i].base); console .log("大小:" ,process_Obj_Module_Arr[i].size); console .log("文件系统路径" ,process_Obj_Module_Arr[i].path); } } }); } setImmediate(frida_Module,0 ); 输出如下: 模块名称: libhello.so 模块地址: 0xdf2d3000 大小: 24576 文件系统路径 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/ arm/libhello.so
enumerateImports() 该API会枚举模块中所有中的所有Import函数,示例代码如下。
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 function frida_Module ( ) { Java.perform(function ( ) { const hooks = Module.load('libhello.so' ); var Imports = hooks.enumerateImports(); for (var i = 0 ; i < Imports.length; i++) { console .log("type:" ,Imports[i].type); console .log("name:" ,Imports[i].name); console .log("module:" ,Imports[i].module); console .log("address:" ,Imports[i].address); } }); } setImmediate(frida_Module,0 ); 输出如下: [Google Pixel::com.roysue.roysueapplication]-> type: function name : __cxa_atexit module : /system /lib /libc .so address : 0xf58f4521 type : function name : __cxa_finalize module : /system /lib /libc .so address : 0xf58f462d type : function name : __stack_chk_fail module : /system /lib /libc .so address : 0xf58e2681 ...
enumerateExports() 该API会枚举模块中所有中的所有Export函数,示例代码如下。
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 frida_Module ( ) { Java.perform(function ( ) { const hooks = Module.load('libhello.so' ); var Exports = hooks.enumerateExports(); for (var i = 0 ; i < Exports.length; i++) { console .log("type:" ,Exports[i].type); console .log("name:" ,Exports[i].name); console .log("address:" ,Exports[i].address); } }); } setImmediate(frida_Module,0 ); 输出如下: [Google Pixel::com.roysue.roysueapplication]-> type: function name : Java_com_roysue_roysueapplication_hellojni_getSum address : 0xdf2d411b type : function name : unw_save_vfp_as_X address : 0xdf2d4c43 type : function address : 0xdf2d4209 type : function ...
enumerateSymbols() 该API会枚举模块中所有中的符号表,示例代码如下。
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 frida_Module ( ) { Java.perform(function ( ) { const hooks = Module.load('libc.so' ); var Symbol = hooks.enumerateSymbols(); for (var i = 0 ; i < Symbol .length; i++) { console .log("isGlobal:" ,Symbol [i].isGlobal); console .log("type:" ,Symbol [i].type); console .log("section:" ,JSON .stringify(Symbol [i].section)); console .log("name:" ,Symbol [i].name); console .log("address:" ,Symbol [i].address); } }); } setImmediate(frida_Module,0 ); 输出如下:isGlobal : true type : function section : {"id" :"13.text" ,"protection" :"r-x" }name : _Unwind_GetRegionStartaddress : 0xf591c798 isGlobal : true type : function section : {"id" :"13.text" ,"protection" :"r-x" }name : _Unwind_GetTextRelBaseaddress : 0xf591c7cc ...
Module.findExportByName(exportName),Module.getExportByName(exportName) 返回so文件中Export函数库中函数名称为exportName函数的绝对地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 function frida_Module ( ) { Java.perform(function ( ) { Module.getExportByName('libhello.so' , 'c_getStr' ) console .log("Java_com_roysue_roysueapplication_hellojni_getStr address:" ,Module.findExportByName('libhello.so' , 'Java_com_roysue_roysueapplication_hellojni_getStr' )); console .log("Java_com_roysue_roysueapplication_hellojni_getStr address:" ,Module.getExportByName('libhello.so' , 'Java_com_roysue_roysueapplication_hellojni_getStr' )); }); } setImmediate(frida_Module,0 ); 输出如下: Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d
Module.findBaseAddress(name)、Module.getBaseAddress(name) 返回name模块的基地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 function frida_Module ( ) { Java.perform(function ( ) { var name = "libhello.so" ; console .log("so address:" ,Module.findBaseAddress(name)); console .log("so address:" ,Module.getBaseAddress(name)); }); } setImmediate(frida_Module,0 ); 输出如下: so address: 0xdf2d3000 so address: 0xdf2d3000
Memory对象 Memory的一些API通常是对内存处理
Memory.scan搜索内存数据 其主要功能是搜索内存中以address地址开始,搜索长度为size,需要搜是条件是pattern,callbacks搜索之后的回调函数;此函数相当于搜索内存的功能。
我们来直接看例子,然后结合例子讲解,如下图。
如果我想搜索在内存中112A地址的起始数据要怎么做,代码示例如下
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 function frida_Memory ( ) { Java.perform(function ( ) { var module = Process.findModuleByName("libhello.so" ); var pattern = "03 49 ?? 50 20 44" ; console .log("base:" +module .base) var res = Memory.scan(module .base, module .size, pattern, { onMatch : function (address, size ) { console .log('搜索到 ' +pattern +" 地址是:" + address.toString()); }, onError : function (reason ) { console .log('搜索失败' ); }, onComplete : function ( ) { console .log("搜索完毕" ) } }); }); } setImmediate(frida_Memory,0 );
onMatch:function(address,size):使用包含作为NativePointer的实例地址的address和指定大小为数字的size调用,此函数可能会返回字符串STOP以提前取消内存扫描。
onError:Function(Reason):当扫描时出现内存访问错误时使用原因调用。
onComplete:function():当内存范围已完全扫描时调用。
执行流程:搜索libhello.so文件在内存中的数据,搜索以pattern条件的在内存中能匹配的数据。搜索到之后根据回调函数返回数据。
我们来看看执行结果如下:
1 2 搜索到 03 49 ?? 50 20 44 地址是:0xdf2d412a 搜索完毕
我们要如何验证搜索到底是不是图1-5中112A地址,其实很简单。so的基址是0xdf2d3000,而搜到的地址是0xdf2d412a,我们只要df2d412a-df2d3000=112A。就是说我们已经搜索到了!
搜索内存数据Memory.scanSync 功能与Memory.scan一样,只不过它是返回多个匹配到条件的数据。 代码示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 function frida_Memory ( ) { Java.perform(function ( ) { var module = Process.findModuleByName("libhello.so" ); var pattern = "03 49 ?? 50 20 44" ; var scanSync = Memory.scanSync(module .base, module .size, pattern); console .log("scanSync:" +JSON .stringify(scanSync)); }); } setImmediate(frida_Memory,0 ); 输出如下,可以看到地址搜索出来是一样的scanSync :[{"address" :"0xdf2d412a" ,"size" :6 }]
内存分配Memory.alloc 在目标进程中的堆上申请size大小的内存,并且会按照Process.pageSize对齐,返回一个NativePointer,并且申请的内存如果在JavaScript里面没有对这个内存的使用的时候会自动释放的。也就是说,如果你不想要这个内存被释放,你需要自己保存一份对这个内存块的引用。
使用案例如下
1 2 3 4 5 6 7 8 9 10 11 12 function frida_Memory ( ) { Java.perform(function ( ) { const r = Memory.alloc(10 ); console .log(hexdump(r, { offset : 0 , length : 10 , header : true , ansi : false })); }); } setImmediate(frida_Memory,0 );
以上代码在目标进程中申请了10字节的空间~输出如下
1 2 3 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789 ABCDEFe8142070 00 00 00 00 00 00 00 00 00 00 .ELF......
可以看到在0xdfe4cd40处申请了10个字节内存空间~
也可以使用:
Memory.allocUtf8String(str) 分配utf字符串
Memory.allocUtf16String 分配utf16字符串
Memory.allocAnsiString 分配ansi字符串
Memory.copy内存复制 如同c api memcp一样调用,使用案例如下。
从module.base中复制10个字节的内存到新年申请的r内
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 function frida_Memory ( ) { Java.perform(function ( ) { var module = Process.findModuleByName("libhello.so" ); var pattern = "03 49 ?? 50 20 44" ; var scanSync = Memory.scanSync(module .base, module .size, pattern); const r = Memory.alloc(10 ); Memory.copy(r,module .base,10 ); console .log(hexdump(r, { offset : 0 , length : 10 , header : true , ansi : false })); }); } setImmediate(frida_Memory,0 ); 输出如下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF e8142070 7f 45 4c 46 01 01 01 00 00 00 .ELF......
写入内存Memory.writeByteArray 将字节数组写入一个指定内存,代码示例如下:
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_Memory ( ) { Java.perform(function ( ) { var arr = [ 0x72 , 0x6F , 0x79 , 0x73 , 0x75 , 0x65 ]; const r = Memory.alloc(arr.length); Memory.writeByteArray(r,arr); console .log(hexdump(r, { offset : 0 , length : arr.length, header : true , ansi : false })); }); } setImmediate(frida_Memory,0 ); 输出如下。 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 72 6f 79 73 75 65 roysue
读取内存Memory.readByteArray 将一个指定地址的数据,代码示例如下:
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 function frida_Memory ( ) { Java.perform(function ( ) { 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 })); }); }); } setImmediate(frida_Memory,0 ); 输出如下。 [Google Pixel::com.roysue.roysueapplication]-> Memory.readByteArray: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 72 6f 79 73 75 65 roysue
Java对象 Java.available 该函数一般用来判断当前进程是否加载了JavaVM,Dalvik或ART虚拟机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function frida_Java ( ) { Java.perform(function ( ) { if (Java.available) { console .log("hello java vm" ); }else { console .log("error" ); } }); } setImmediate(frida_Java,0 ); 输出如下。 hello java vm
Java.androidVersion 显示android系统版本号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function frida_Java() { Java.perform (function () { //作为判断用 if (Java.available) { //注入的逻辑代码 console.log("",Java.androidVersion); }else { //未能正常加载JAVA VM console.log("error"); } }); } setImmediate(frida_Java,0 ); 输出如下。9 因为我的系统版本是9 版本~
枚举类Java.enumerateLoadedClasses 该API枚举当前加载的所有类信息,它有一个回调函数分别是onMatch、onComplete函数,我们来看看代码示例以及效果!
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 function frida_Java() { Java.perform (function () { if (Java.available) { //console.log("",Java.androidVersion); //枚举当前加载的所有类 Java.enumerateLoadedClasses({ //每一次回调此函数时其参数className就是类的信息 onMatch: function (className) { //输出类字符串 console.log("",className); }, //枚举完毕所有类之后的回调函数 onComplete: function () { //输出类字符串 console.log("输出完毕"); } }); }else { console.log("error"); } }); } setImmediate(frida_Java,0 );
枚举类加载器Java.enumerateLoadedClasses 该api枚举Java VM中存在的类加载器,其有一个回调函数,分别是onMatch: function (loader)与onComplete: function () 它也有一个好兄弟叫Java.enumerateClassLoadersSync()也是返回的数组
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 );
Java.perform(fn)主要用于当前线程附加到Java VM并且调用fn方法 它也有一个好兄弟。Java.performNow(fn)~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function frida_Java ( ) { Java.perform(function ( ) { if (Java.available) { console .log("hello" ); }else { console .log("error" ); } }); } setImmediate(frida_Java,0 ); 输出如下。 [Google Pixel::com.roysue.roysueapplication]-> hello
获取类Java.use Java.use(className),动态获取className的类定义,通过对其调用$new()来调用构造函数,可以从中实例化对象。当想要回收类时可以调用$Dispose()方法显式释放,当然也可以等待JavaScript的垃圾回收机制,当实例化一个对象之后,可以通过其实例对象调用类中的静态或非静态的方法,官方代码示例定义如下。
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 14 15 16 17 18 Java.perform(function ( ) { Java.choose("android.view.View" , { onMatch :function (instance ) { console .log(instance); }, onComplete :function ( ) { console .log("end" ) }}); }); 输出如下: android.view.View{2292774 V.ED..... ......ID 0 ,1794 -1080 ,1920 #1020030 android:id/navigationBarBackground} android.view.View{d43549d V.ED..... ......ID 0 ,0 -1080 ,63 #102002f android:id/statusBarBackground} end
类型转换器Java.cast Java.cast(handle, klass),就是将指定变量或者数据强制转换成你所有需要的类型;创建一个 JavaScript 包装器,给定从 Java.use() 返回的给定类klas的句柄的现有实例。此类包装器还具有用于获取其类的包装器的类属性,以及用于获取其类名的字符串表示的$className属性,通常在拦截so层时会使用此函数将jstring、jarray等等转换之后查看其值。
定义任意数组类型Java.array frida提供了在js代码中定义java数组的api,该数组可以用于传递给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]) } });
我们通过上面定义int数组和byte的例子可以知道其定义格式为Java.array(‘type’,[value1,value2,….]);那它都支持type呢?
|
索引
type
含义
1
Z
boolean
2
B
byte
3
C
char
4
S
short
5
I
int
6
J
long
7
F
float
8
D
double
9
V
void
注册类Java.registerClass(spec) Java.registerClass:创建一个新的Java类并返回一个包装器,其中规范是一个包含:
name:指定类名称的字符串。
superClass:(可选)父类。要从 java.lang.Object 继承的省略。
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对象 Java.vm对象十分常用,比如想要拿到JNI层的JNIEnv对象,可以使用getEnv();
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对象 该对象功能十分强大,函数原型是Interceptor.attach(target, callbacks):
参数target是需要拦截的位置的函数地址,也就是填某个so层函数的地址即可对其拦截
target是一个NativePointer参数,用来指定你想要拦截的函数的地址,NativePointer我们也学过是一个指针。
需要注意的是对于Thumb函数需要对函数地址+1,callbacks则是它的回调函数,分别是以下两个回调函数:
Interceptor.attach onEnter:函数(args):回调函数,给定一个参数args,可用于读取或写入参数作为 NativePointer 对象的数组。
onLeave:函数(retval):回调函数给定一个参数 retval,该参数是包含原始返回值的 NativePointer 派生对象。可以调用 retval.replace(0) 以整数 0 替换返回值,或者调用 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 作用就是让之前所有的Interceptor.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 )); }); }
NativePointer对象 同等与C语言中的指针
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 它也能调用以下运算符
看完API含义之后,我们来使用他们,下面该脚本是readByteArray()示例~
1 2 3 4 5 6 7 8 9 10 11 12 13 function frida_NativePointer ( ) { Java.perform(function ( ) { console .log("" ); var pointer = Process.findModuleByName("libc.so" ).base; console .log(pointer.readByteArray(0x10 )); }); } setImmediate(frida_NativePointer,0 ); 输出如下: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
首先我先来用readByteArray函数来读取libc.so文件在内存中的数据,这样我们方便测试,我们从libc文件读取0x10个字节的长度,肯定会是7F 45 4C 46…因为ELF文件头部信息中的Magic属性。
readPointer() 从此内存位置读取NativePointer,示例代码如下。省略function以及Java.perform~
1 2 3 4 5 var pointer = Process.findModuleByName("libc.so" ).base; console .log(pointer.readByteArray(0x10 )); console .log("readPointer():" +pointer.readPointer()); 输出如下。 readPointer():0x464c457f
也就是将readPointer的前四个字节的内容转成地址产生一个新的NativePointer。
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 0123456789ABCDEF00000000 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 0123456789ABCDEF00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............1179403647 == 0x464c457f 1179403647 == 0x464c457f
writeS32()、writeU32() 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 0123456789ABCDEF00000000 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 0123456789ABCDEF00000000 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指定),其中rereturn 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,其中rereturn 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 )); });
HOOK HOOK java层方法模版 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 import frida, sysdef on_message (message, data ): if message['type' ] == 'send' : print ("[*] {0}" .format (message['payload' ])) else : print (message) jscode = """ Java.perform(function () { var MainActivity = Java.use('类名'); MainActivity.方法.implementation = function (入参) { //打印入参数据 console.log(入参) var result=this.方法(入参) //打印出参 console.log(出参) //数据返回 return result }; }); """ process = frida.get_usb_device().attach("HOOK包名" ) script = process.create_script(jscode) script.on('message' , on_message)print ('[*] Running' ) script.load() sys.stdin.read()
HOOK native层方法模版 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 import frida, sysdef on_message (message, data ): if message['type' ] == 'send' : print ("[*] {0}" .format (message['payload' ])) else : print (message) jscode = """ setImmediate(function() { #Interceptor.attach方法需要两个参数 1.NativePointer指针 2.回调方 Interceptor.attach(Module.findExportByName(".so文件" , "导出方法"), { onEnter: function(args) { send('此消息发送给on_message方法'); }, onLeave:function(retval){ } }); }); """ process = frida.get_usb_device().attach("HOOK包名" ) script = process.create_script(jscode) script.on('message' , on_message)print ('[*] Running' ) script.load() sys.stdin.read()
HOOK重载方法 1. apply arguments 1 2 3 MyClass.MyFunc.overload("java.util.List" ).implementation = function ( ) { this .MyFunc.overload("java.util.List" ).apply(this , arguments ); }
2. argments下标 1 2 3 MyClass.MyFunc.overload("java.util.List" ).implementation = function ( ) { this .MyFunc(arguments [0 ]); };
3. 使用具体参数 1 2 3 MyClass.MyFuncs.overload("int" , "int" ).implementation = function (s1, s2 ) { var ret = this .MyFuncs(s1, s2); }
4. call 1 2 3 4 5 6 7 8 9 10 11 12 13 var Handler = classFactory.use("android.os.Handler" );var Looper = classFactory.use("android.os.Looper" );var looper = Looper.getMainLooper();var handler = Handler.$new.overload("android.os.Looper" ).call(Handler, looper); MyClass.MyFunc.overload("java.lang.String;" ).implementation = { this .MyFunc.overload("java.lang.String" ).call(this , args[1 ]) MyClass.MyFunc.overload("java.lang.String" ).call() }
拦截系统fopen函数 1 FILE *fopen(const char *filename, const char *mode)
使用给定的模式 mode 打开 filename 所指向的文件。文件如果打开成功,会返回一个指针,相当于句柄,如果文件打开失败则返回 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Interceptor.attach(Module.findExportByName(null , "fopen" ), { onEnter : function (args ) { if (!args[0 ].isNull()){ var path = args[0 ].readUtf8String(); console .log("fopen " + path); } }, onLeave : function (retval ) { retval.replace(0 ); }, })
拦截自定义函数 自定义一个 getEncryptKey 函数,返回的参数是一个字符串指针,我们在 onLeave 函数中添加下面的代码,分配内存并填充字符串 123456,最终替换返回值
1 2 3 4 5 6 7 8 9 10 11 Interceptor.attach(Module.findExportByName(null , "getEncryptKey" ), { onLeave : function (retval ) { console .log("getEncryptKey onLeave" ); console .log("[*] retval:" + retval.readUtf8String()); var string = Memory.allocUtf8String("1234567" ); retval.replace(string); }, })
修改C函数执行流程 以修改open方法为例
1 2 3 4 5 6 7 8 9 var openPtr = Module.getExportByName(null , 'open' ); var open = new NativeFunction(openPtr, 'int' , ['pointer' , 'int' ]); Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags ) { var path = pathPtr.readUtf8String(); console .log('[*]path: ' + path); var fd = open(pathPtr, flags); console .log('[*]fd:' + fd); return fd; }, 'int' , ['pointer' , 'int' ]));
以修改fopen方法为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var fopenAddr = Module.findExportByName("libc.so" ,"fopen" );var func_c_fopen = new NativeFunction(fopenAddr,"pointer" ,["pointer" ,"pointer" ]); Interceptor.replace(fopenAddr,new NativeCallback(function (path,mode ) { console .log("替换" ); console .error(path,Memory.readCString(path),"mode:" ,Memory.readCString(mode)); var newPtr = Memory.allocUtf8String("/data/local/tmp/js/app.js" ); return func_c_fopen(newPtr,mode) },"pointer" ,["pointer" ,"pointer" ]));
拦截 sub_xxxx 函数 通过指针去HOOK,首先通过IDA获取到方法的偏移地址,也就是sub后面的十六进制
1 2 3 4 5 Interceptor.attach(Module.findBaseAddress("libc.so" ).add(0x1A45 ), { onEnter : function (args ) { } });
HOOK OC 1. 拦截自定义方法 1 2 3 4 5 6 7 var method = ObjC.classes.类['+/- 方法名' ];var origImp = method.implementation; method.implementation = ObjC.implement(method, function (self,selector ) { return origImp(self,selector); });
2. 拦截NSString 1 frida-trace -U -m "+[NSURL URLWithString:]" xxx
frida会自动在当前文件夹__handlers__
下生成js脚本,编辑并填写以下代码
1 2 3 4 5 6 onEnter: function (log, args, state ) { log("+[NSURL URLWithString:" + args[2 ] + "]" ); var objcNSString = ObjC.Object(args[2 ]); var strObjcNSString = objcNSString.UTF8String(); log("+[NSURL URLWithString] str: " + strObjcNSString); }
3.拦截NSMutableURLRequest 1 frida-trace -U -m "-[NSURLRequest setHTTPBody:]" -m "-[NSMutableURLRequest setHTTPBody:]" xxx
找到 NSMutableURLRequest_setHTTPBody.js 修改成如下代码,可以看到调用 NSData 的 bytes 函数得到内存地址,然后再调用 readUtf8String 读取内存中的数据,即可得到字符串。
1 2 3 4 5 6 onEnter: function (log, args, state ) { log("-[NSMutableURLRequest setHTTPBody:" + args[2 ] + "]" ); var objcData = ObjC.Object(args[2 ]); var strBody = objcData.bytes().readUtf8String(objcData.length()); log("[NSMutableURLRequest setHTTPBody data: " + strBody); },
4. 遍历 NSArray 1 2 3 4 5 var array = new ObjC.Object(args[2 ]);var count = array.count().valueOf();for (var i = 0 ; i !== count; i++) { var element = array.objectAtIndex_(i); }
5. 遍历 NSDictionary 1 2 3 4 5 6 var dict = new ObjC.Object(args[2 ]);var enumerator = dict.keyEnumerator();var key;while ((key = enumerator.nextObject()) !== null ) { var value = dict.objectForKey_(key); }
6. 通配符跟踪方法 1 frida-trace -U -m "-[Home* *]" Twitter
7. 跟踪CCCrypt 1 frida-trace -U -i CCCrypt xxx
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 { onEnter : function (log, args, state ) { if (this .operation == 0 ) { console .log("input:" ) console .log(hexdump(ptr(args[6 ]), { length : args[7 ].toInt32(), header : true , ansi : true })) console .log("Key: " ) console .log(hexdump(ptr(args[3 ]), { length : args[4 ].toInt32(), header : true , ansi : true })) console .log("IV: " ) console .log(hexdump(ptr( args[5 ]), { length : args[4 ].toInt32(), header : true , ansi : true })) } } }
拦截URWithString
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 var className = "NSURL" ;var funcName = "+ URLWithString:" ;var hook = eval ('ObjC.classes.' + className + '["' + funcName + '"]' ); Interceptor.attach(hook.implementation, { onEnter : function (args ) { var className = ObjC.Object(args[0 ]); var methodName = args[1 ]; var urlString = ObjC.Object(args[2 ]); console .log("className: " + className.toString()); console .log("methodName: " + methodName.readUtf8String()); console .log("urlString: " + urlString.toString()); console .log("-----------------------------------------" ); urlString = ObjC.classes.NSString.stringWithString_("http://www.baidu.com" ) console .log("newUrlString: " + urlString.toString()); console .log("-----------------------------------------" ); }, onLeave : function (retval ) { console .log("[*] Class Name: " + className); console .log("[*] Method Name: " + funcName); console .log("\t[-] Type of return value: " + typeof retval); console .log("\t[-] Original Return Value: " + retval); } });
有时候被拦截的函数调用的次数较多,打印的信息也会较多,我们需要保存成文件
1 2 3 4 var file = new File("/var/mobile/log.txt" ,"a+" ); file.write("logInfo" ); file.flush(); file.close();
8. API查找器和拦截器的组合使用实现批量HOOK API查找器支持对 Objective-C 方法和 C 函数的查找,比如我们来写一个找到 NSFileManager 这个类名下的所有方法。新建一个 ApiResolver,如果查找 Objective-C,ApiResolver 参数中填写 objc,如果是查找 C 函数,ApiResolver 参数中填写 module,然后调用 enumerateMatches 枚举函数,每次枚举到一个函数会调用一次 onMatch,回调参数 match 包含 name 和 address 两个属性,分别代表名称和地址。整个枚举过程完成之后会调用 onComplete,代码如下:
1 2 3 4 5 6 7 8 var resolver = new ApiResolver('objc' ); resolver.enumerateMatches('*[NSFileManager *]' , { onMatch : function (match ) { console .log(match['name' ] + ":" + match['address' ]); }, onComplete : function ( ) {} });
附加 Safari 浏览器并加载脚本,会打印出 NSFileManager 所有的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 frida -U -l hook_objc.js "Safari 浏览器" -[NSFileManager dealloc]:0x1e2324034 -[NSFileManager setDelegate:]:0x1e23b2744 -[NSFileManager delegate]:0x1e2333ab8 -[NSFileManager _info]:0x1e2333ae0 -[NSFileManager removeItemAtURL:error:]:0x1e235583c -[NSFileManager copyItemAtURL:toURL:error:]:0x1e23b2ab0 -[NSFileManager fileExistsAtPath:isDirectory:]:0x1e2301d50 -[NSFileManager fileExistsAtPath:]:0x1e22e5398 -[NSFileManager setAttributes:ofItemAtPath:error:]:0x1e23180bc -[NSFileManager removeItemAtPath:error:]:0x1e23336bc -[NSFileManager enumeratorAtPath:]:0x1e2326024 -[NSFileManager isWritableFileAtPath:]:0x1e23240dc -[NSFileManager moveItemAtPath:toPath:uniquePath:error:]:0x1ec6f4ec4 -[NSFileManager copyItemAtPath:toPath:uniquePath:error:]:0x1ec6f4ed4 ......
得到方法名称之后,我们可以把感兴趣的找出来,然后调用拦截器(Interceptor)进行 Hook ,比如我们需要做一个简易的文件监控的功能,监控某个进程操作了哪些文件,文件操作的相关的方法会调用 removeItemAtPath、moveItemAtPath、copyItemAtPath、createFileAtPath、createDirectoryAtPath、enumeratorAtPath、contentsOfDirectoryAtPath,分别代表删除文件、移动文件、复制文件、创建文件、创建目录、枚举目录(包括子目录),枚举目录(不包括子目录),具体代码如下:
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 var resolver = new ApiResolver('objc' ); resolver.enumerateMatches('*[NSFileManager *]' , { onMatch : function (match ) { var method = match['name' ]; var implementation = match['address' ]; if ( (method.indexOf("removeItemAtPath" ) != -1 ) || (method.indexOf("moveItemAtPath" ) != -1 ) || (method.indexOf("copyItemAtPath" ) != -1 ) || (method.indexOf("createFileAtPath" ) != -1 ) || (method.indexOf("createDirectoryAtPath" ) != -1 ) || (method.indexOf("enumeratorAtPath" ) != -1 ) || (method.indexOf("contentsOfDirectoryAtPath" ) != -1 )) { console .log(match['name' ] + ":" + match['address' ]); try { Interceptor.attach(implementation, { onEnter : function (args ) { var className = ObjC.Object(args[0 ]); var methodName = args[1 ]; var filePath = ObjC.Object(args[2 ]); console .log("className: " + className.toString()); console .log("methodName: " + methodName.readUtf8String()); console .log("filePath: " + filePath.toString()); }, onLeave : function (retval ) { } }); } catch (err) { console .log("[!] Exception: " + err.message); } } }, onComplete : function ( ) { } });
附加 SpringBoard 并加载脚本,然后我们操作卸载某个应用,此时会打印出相关的文件操作的路径,从下面打印的信息中我们可以看到卸载某个应用时,SpringBoard 进程对文件的操作过程,会删除 /var/mobile/Library/UserNotifications 和应用的沙盒目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 frida -U -l hook_objc.js SpringBoard [iPhone::SpringBoard]-> className: <NSFileManager: 0x282954250> methodName: removeItemAtPath:error: filePath: /var/mobile/Library/UserNotifications/com.360buy.jdmobile/Attachments className: <NSFileManager: 0x282954250> methodName: filesystemItemRemoveOperation:shouldRemoveItemAtPath: filePath: <NSFilesystemItemRemoveOperation: 0x283674f40> className: <NSFileManager: 0x282954250> methodName: enumeratorAtPath: filePath: /private/var/mobile/Containers/Data/Application/C8A23CD4-6857-4BF4-876E-7FD104B0E799/Library/Caches/Snapshots/com.360buy.jdmobile className: <NSFileManager: 0x282954250> methodName: removeItemAtPath:error: filePath: /private/var/mobile/Containers/Data/Application/C8A23CD4-6857-4BF4-876E-7FD104B0E799/Library/Caches/Snapshots/com.360buy.jdmobile className: <NSFileManager: 0x282954250> methodName: filesystemItemRemoveOperation:shouldRemoveItemAtPath: filePath: <NSFilesystemItemRemoveOperation: 0x283677e80> className: <NSFileManager: 0x282954250> methodName: removeItemAtPath:error: filePath: /private/var/mobile/Containers/Data/Application/C8A23CD4-6857-4BF4-876E-7FD104B0E799/Library/Caches/Snapshots className: <NSFileManager: 0x282954250> methodName: filesystemItemRemoveOperation:shouldRemoveItemAtPath: filePath: <NSFilesystemItemRemoveOperation: 0x283677e80> .......
比如需要拦截 NSFileManager 类的所有方法,但是排除某些方法,代码可以这样写
1 2 3 4 5 6 7 8 9 10 11 12 13 var resolver = new ApiResolver('objc' ); resolver.enumerateMatches('*[NSFileManager *]' , { onMatch : function (match ) { var method = match['name' ]; var implementation = match['address' ]; if (method.indexOf("dealloc" ) == -1 ) { } }, });
接下来我们来学习 API 查找器如何查找 C 函数,有 3 种方式,格式参考如下:
1 format is: exports: *!open*, exports:libc .so!* or imports:notepad .exe!*
比如我们查找 fopen 函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 var resolver = new ApiResolver('module' ); resolver.enumerateMatches('exports:*!fopen*' , { onMatch : function (match ) { var name = match['name' ]; var address = match['address' ]; console .log(name + ":" + address); }, onComplete : function ( ) {} });
附加 SpringBoard 加载脚本之后,打印如下信息,名称包括了动态库的路径,此时得到函数地址,就可以调用拦截器进行 Hook。
1 2 3 4 5 6 7 8 frida -U -l hook_objc.js SpringBoard [iPhone::SpringBoard]-> /usr/ lib/libSystem.B.dylib!fopen:0x1e145d4d0 /usr/lib/libSystem.B.dylib!fopen$DARWIN_EXTSN:0x1e1495d04 /usr/lib/system/libsystem_c.dylib!fopen$DARWIN_EXTSN:0x1e1495d04 /usr/lib/system/libsystem_c.dylib!fopen:0x1e145d4d0 /usr/lib/libSystem.B.dylib!fopen:0x1e145d4d0 /usr/lib/libSystem.B.dylib!fopen$DARWIN_EXTSN:0x1e1495d04
Objective-C 除了调用拦截器进行 Hook,还可以使用替换 implementation 的方式,类似于苹果提供的一套 Method Swizzling Hook 的方式,比如 Hook +[NSURL URLWithString:] 的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var method = ObjC.classes.NSURL['+ URLWithString:' ];var origImp = method.implementation; method.implementation = ObjC.implement(method, function (self, sel, url ) { console .log("+ [NSURL URLWithString:]" ); var urlString = ObjC.Object(url); console .log("url: " + urlString.toString()); return origImp(self, sel, url); });
9. NSData与BASE64互转 NSData转BASE64
1 2 3 4 5 var NSData = new ObjC.Object(args[3 ]);var BASE64NSData = NSData['- base64EncodedDataWithOptions:' ].call(NSData,0 )var NSString = ObjC.classes.NSString.alloc();var ret =NSString['- initWithData:encoding:' ].call(NSString,BASE64NSData,4 );
BASE64转NSData
1 2 3 4 5 var base64String="/9j/4AAQSkZJR" var BASE64NSString = ObjC.classes.NSString.stringWithString_(base64String);var NSData = ObjC.classes.NSData.alloc();var ret = NSData['- initWithBase64EncodedString:options:' ].call(NSData,BASE64NSString,1 );
10.修改NSDictionary数据 假如NSDictionary数据为
1 {"status" :"fail" ,"code" :500 }
修改code字段500为200
1 2 3 4 5 6 var NSMutableDictionaryResult = ObjC.classes.NSMutableDictionary.dictionaryWithDictionary_(result);var code = ObjC.classes.NSString.stringWithString_("code" );var codeNum = ObjC.classes.NSString.stringWithString_("200" ); NSMutableDictionaryResult.setValue_forKey_(codeNum,code); console .log(NSMutableDictionaryResult)
11.UIImage图片与Base64互转 OC实现
1 2 3 4 5 6 7 8 9 //UIImage图片转成Base64字符串: UIImage *originImage = [UIImage imageNamed:@"originImage.png"]; NSData *data = UIImageJPEGRepresentation(originImage, 1.0f); NSString *encodedImageStr = [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; //Base64字符串转UIImage图片: NSData *decodedImageData = [[NSData alloc] initWithBase64EncodedString:encodedImageStr options:NSDataBase64DecodingIgnoreUnknownCharacters]; UIImage *decodedImage = [UIImage imageWithData:decodedImageData];
UIImage转Base64
1 2 3 4 5 6 var UIImageJPEGRepresentation = new NativeFunction(Module.findExportByName(null ,"UIImageJPEGRepresentation" ),"pointer" ,["pointer" ,"float" ]);var uiimage = "" ; var NSData = new ObjC.Object(UIImageJPEGRepresentation(uiimage,1.0 ));var BASE64NSData = NSData['- base64EncodedDataWithOptions:' ].call(NSData,0 )var NSString = ObjC.classes.NSString.alloc();var ret =NSString['- initWithData:encoding:' ].call(NSString,BASE64NSData,4 );
Base64转UIImage
1 2 3 4 5 var base64String = ObjC.classes.NSString.stringWithString_("/9j/4AAQ" );var BASE64NSString = ObjC.classes.NSString.stringWithString_(base64String);var NSData = ObjC.classes.NSData.alloc();var data = NSData['- initWithBase64EncodedString:options:' ].call(NSData,BASE64NSString,1 );var uiimage = ObjC.classes.UIImage.imageWithData_(data);
RPC 远程过程调用(RPC)对于应用逆向起到了很便捷的作用,比如目标应用有一个加解密的函数内部实现非常复杂,想分析整个加解密的实现过程的工作量较大,此时可以使用 frida 的 RPC 功能,只要知道加解密函数的名称或地址,还有相应的参数,即可直接调用得到加解密后的结果。 下面我们来做一个实例,编写一个 CrackMe 程序,定义一个 coreClass 类,里面有 4 个比较重要的方法,分别是 getDeviceId、httpPost、encrypt、decrypt,从它们的名称可以看出大概的意思,具体的定义如下:
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 @interface coreClass : NSObject @end @implementation coreClass - (NSString*)getDeviceId{ return @"e10adc3949ba59abbe56e057f20f883e" ; } +(NSString*)httpPost:(NSString*)url{ NSLog(@"URL: %@" , url); return @"test" ; } - (NSString*)encrypt:(NSData*)data :(NSString*)key{ NSLog(@"data: %@" , data); NSLog(@"key: %@" , key); return @"encrypt successed!" ; } - (NSString*)decrypt:(NSString*)str :(NSString*)key{ NSLog(@"str: %@" , str); NSLog(@"key: %@" , key); return @"decrypt successed!" ; } @end
下面我们要做的事情是在 JS 脚本里去调用这 4 个方法。Objective-C 方法有两种,一种是 - 号开头,称为对象方法,还有一种是 + 号开头,称为类方法,前者需要初始化一个实例才能调用,后者不需要初始化实例,可以直接调用。先来看看对象方法在 JS 脚本里如何调用,比如我们要调用 -[coreClass getDeviceId] 方法,调用的代码如下:
1 2 3 var coreClass = ObjC.classes.coreClass.alloc();var deviceId = coreClass['- getDeviceId' ].call(coreClass);console .log("deviceId: " + deviceId.toString());
再来看看 +[coreClass httpPost:] 方法的调用,从下面的代码可以看出,类方法是不需要初始化实例,在方法名称后面加上一个 _ 号就可以调用了
1 2 3 4 var coreClass = ObjC.classes.coreClass;var url = ObjC.classes.NSURL.URLWithString_("http://www.ioshacker.net" );var retString = coreClass.httpPost_(url);console .log("retString: " + retString);
-[coreClass encrypt::] 的调用代码如下,输入的参数有两个,一个参数是 NSData 类型的,一个是 NSString 类型。
1 2 3 4 5 6 7 8 9 var coreClass = ObjC.classes.coreClass.alloc();var str = ObjC.classes.NSString.stringWithString_("test" );var data = str.dataUsingEncoding_(4 );var key = ObjC.classes.NSString.stringWithString_("key" );var encryptString = coreClass['- encrypt::' ].call(coreClass, data, key);console .log("encryptString: " + encryptString);
-[coreClass decrypt::] 的调用代码如下,两个参数都是 NSString 类型。
1 2 3 4 5 var coreClass = ObjC.classes.coreClass.alloc();var str = ObjC.classes.NSString.stringWithString_("test" );var key = ObjC.classes.NSString.stringWithString_("key" );var decryptString = coreClass['- decrypt::' ].call(coreClass, str, key);console .log("decryptString: " + decryptString);
在上面我们已经学会如何在 JS 脚本里调用目标应用的 Objective-C 方法,接下面要做的就是将功能代码导出提供给 Python 使用。在 JS 代码里定义4个函数,分别是 getDeviceId、httpPost、encrypt、decrypt,这 4 个函数分别代表了调用目标进程的 4 个 Objective-C 方法,然后再使用 rpc.exports 将这 4 个函数导出,代码如下:
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 function getDeviceId ( ) { var coreClass = ObjC.classes.coreClass.alloc(); var deviceId = coreClass['- getDeviceId' ].call(coreClass); console .log("deviceId: " + deviceId.toString()); console .log("--------------------" ); }function httpPost (inputurl ) { var coreClass = ObjC.classes.coreClass; var url = ObjC.classes.NSURL.URLWithString_(inputurl); var retString = coreClass.httpPost_(url); console .log("retString: " + retString); console .log("--------------------" ); }function encrypt (inputstr, inputkey ) { var coreClass = ObjC.classes.coreClass.alloc(); var str = ObjC.classes.NSString.stringWithString_(inputstr); var data = str.dataUsingEncoding_(4 ); var key = ObjC.classes.NSString.stringWithString_(inputkey); var encryptString = coreClass['- encrypt::' ].call(coreClass, data, key); console .log("encryptString: " + encryptString); console .log("--------------------" ); }function decrypt (inputstr, inputkey ) { var coreClass = ObjC.classes.coreClass.alloc(); var str = ObjC.classes.NSString.stringWithString_(inputstr); var key = ObjC.classes.NSString.stringWithString_(inputkey); var decryptString = coreClass['- decrypt::' ].call(coreClass, str, key); console .log("decryptString: " + decryptString); console .log("--------------------" ); } rpc.exports = { deviceid : function ( ) { getDeviceId(); }, httppost : function (urlstr ) { httpPost(urlstr); }, encrypt : function (inputstr, inputkey ) { encrypt(inputstr, inputkey); }, decrypt : function (inputstr, inputkey ) { decrypt(inputstr, inputkey); }, };
接着我们再编写 python 代码,首先调用 get_usb_device().attach 附加到目标进程,附加成功会返回一个 Session 实例,然后读取 call.js 文件里的内容,这里的内容就是上面我们编写的 JS 代码,然后调用 session.create_script 创建脚本,再调用 load 加载脚本,最后调用 script.exports 获取 JS 导出的 RPC 函数,此时就可以调用 RPC 函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import codecsimport fridaif __name__ == '__main__' : session = frida.get_usb_device().attach(u'CrackMe' ) with codecs.open ('./call.js' , 'r' , 'utf-8' ) as f: source = f.read() script = session.create_script(source) script.load() rpc = script.exports rpc.deviceid() rpc.httpPost("http://www.ioshacker.net" ) rpc.encrypt("123" , "456" ) rpc.decrypt("123" , "456" ) session.detach()
上面我们讲到了如何将 Objective-C 方法导出给 Python 使用,那如何导出 C 函数呢?比如调用 NSHomeDirectory 这个系统提供的函数,知道它所在的模块是 Foundation,找到它的地址在 JS 脚本里就可以调用,然后在 rpc.exports 里添加一个导出函数,这样 Python 就可以调用了。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 function getHomeDir ( ) { var NSHomeDirectory = new NativeFunction(ptr(Module.findExportByName("Foundation" , "NSHomeDirectory" )), 'pointer' , []); var path = new ObjC.Object(NSHomeDirectory()); console .log('homeDir: ' + path); } rpc.exports = { homedir : function ( ) { getHomeDir(); } };
b2s and s2b 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function b2s (array ) { var result = "" ; for (var i = 0 ; i < array.length; i++) { result += String .fromCharCode(modulus(array[i], 256 )); } return result; } function modulus (x, n ) { return ((x % n) + n) % n; } var StringClass=Java.use("java.lang.String" );var b2sString = b2s('字节数组' )console .log(b2sString)var jstring = StringClass.$new(b2sString)var jbyte = jstring.getBytes("UTF-8" )
插件化 hook 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 Java.perform(function ( ) { var hook_class_name = "Test" ; Java.enumerateClassLoaders({ onMatch : function (loader ) { console .log(loader) try { if (loader.findClass(hook_class_name)) { var factory = Java.ClassFactory.get(loader) var myClass = factory.use("myClass" ); var test = Java.classFactory.use(hook_class_name); test.DexClass.overload().implementation=function ( ) { var ret = this .DexClass(); console .log(ret); return ret; } } } catch (error){ } }, onComplete : function ( ) { } }); });
创建字节数组 1 2 3 4 5 6 var myArray=new Array (1024 );var i = 0 for (i = 0 ; i < myArray.length; i++) { myArray[i]= 0x0 ; }var buffer = Java.array('byte' ,myArray);
创建String数组 1 Java.array("Ljava.lang.String;" , ["111" ,"222" ,"333" ]);
打印字节数组 1 2 3 4 5 6 7 var size = 100 ; var bytearr= Java.array("byte" ,arg1); var content = "" ;for (var i = 0 ;i<size;i++){ content = content + String .fromCharCode(bytearr[i]); }console .log(content)
load dex打印对象 1 2 3 Java.openClassFile("xxxxx.dex" ).load();const gson = Java.use("com.xxxx.xxxx" );console .log(gson.$new().toJson(xxxx));
打印堆栈
Android
1 console .log(Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Throwable" ).$new()));
IOS
1 console .log('\tBacktrace:\n\t' + Thread.backtrace(this .context,Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n\t' ));
SUB方法主动调用 1 2 3 4 5 6 7 8 9 10 11 function hooksub (content ) { var offset = 0x835 ; var nativeLibModule = Process.getModuleByName("libnative-lib.so" ); var func_sub = nativeLibModule.base.add(offset); var arg0 = Memory.alloc(10 ); ptr(arg0).writeUtf8String(content); var sub_method = new NativeFunction(func_sub,'pointer' ,['pointer' ]); var result = sub_method(arg0) console .log("result" ,hexdump(result)); }
SO导出方法-主动调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function hookMethod (str_data, n_num ) { var soName = "libnative-lib.so" ; var funcName = "encrypt" ; var n_addr_func = Module.findExportByName(soName , funcName); var func_c_enc = new NativeFunction(n_addr_func , 'pointer' , ['pointer' , 'int' ]); var str_data_arg = Memory.allocUtf8String(str_data); var p_str_ret = func_c_enc(str_data_arg, n_num); var str_ret = Memory.readCString(p_str_ret); return str_ret; }
NativeFunction中frida支持的数据类型和abi
SUPPORTED TYPES
void
pointer
int
uint
long
ulong
char
uchar
size_t
ssize_t
float
double
int8
uint8
int16
uint16
int32
uint32
int64
uint64
bool
SUPPORTED ABIS
default
Windows 32-bit:
sysv
stdcall
thiscall
fastcall
mscdecl
Windows 64-bit:
UNIX x86:
UNIX ARM:
keypatch 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 function dis (address,number ) { console .log("\n" ); for (var i = 0 ; i < number; i++) { var ins = Instruction.parse(address); console .error("address:" + address + " dis:" + ins.toString()); address = ins.next; } }function nopKill ( ) { var libnativemodule = Process.getModuleByName("libnative-lib.so" ); var base = libnativemodule.base; Memory.patchCode(base.add(0x1564 ),4 ,patchaddr => { var arm64Writer = new Arm64Writer(patchaddr); arm64Writer.putNop(); arm64Writer.flush(); }) }function editTbnz ( ) { var libnativemodule = Process.getModuleByName("libnative-lib.so" ); var base = libnativemodule.base; Memory.protect(base.add(0x150C ), 4 , 'rwx' ); base.add(0x150C ).writeByteArray([0xD1 , 0x01 , 0x00 , 0x36 ]); }function main ( ) { var libnativemodule = Process.getModuleByName("libnative-lib.so" ); var base = libnativemodule.base; dis(base.add(0x150C ),30 ); } setImmediate(main);
ssl_pinning 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 Java.perform(function ( ) { var androidVersion = parseInt (Java.androidVersion, 10 ) if (androidVersion > 6 ){ console .log(111 ) try { var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl' ); TrustManagerImpl.checkTrustedRecursive.implementation = function (certs, host, clientAuth, untrustedChain, trustedChain, used ) { send('[SSL Pinning Bypass] checkTrustedRecursive() bypassed' ); return Java.use("java.util.ArrayList" ).$new(); } }catch (err) { send('[SSL Pinning Bypass] TrustManagerImpl.checkTrustedRecursive() not found' ); } try { var TrustManagerImpl2 = Java.use('com.android.org.conscrypt.TrustManagerImpl' ); TrustManagerImpl2.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData ) { send('[SSL Pinning Bypass] verifyChain() bypassed for: ' + host); return untrustedChain; } } catch (err) { send('[SSL Pinning Bypass] TrustManagerImpl.verifyChain() not found' ); } try { var ConscryptFileDescriptorSocket = Java.use('com.android.org.conscrypt.ConscryptFileDescriptorSocket' ); ConscryptFileDescriptorSocket.verifyCertificateChain.implementation = function (certChain, authMethod ) { send('[SSL Pinning Bypass] verifyCertificateChain() bypassed' ); return ; } } catch (err) { send('[SSL Pinning Bypass] ConscryptFileDescriptorSocket.verifyCertificateChain() not found' ); } } else if (androidVersion > 4 && androidVersion < 7 ) { console .log(111 ) try { var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl' ); OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, authMethod ) { send('[SSL Pinning Bypass] OpenSSLSocketImpl.verifyCertificateChain() bypassed' ); return ; } } catch (err) { send('[SSL Pinning Bypass] OpenSSLSocketImpl.verifyCertificateChain() not found' ); } } try { var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient" ); OkHttpClient.setCertificatePinner.implementation = function (certificatePinner ) { send('[SSL Pinning Bypass] OkHttpClient.setCertificatePinner() bypassed' ); return this ; }; var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner" ); CertificatePinner.check.overload('java.lang.String' , '[Ljava.security.cert.Certificate;' ).implementation = function (p0, p1 ) { send('[SSL Pinning Bypass] CertificatePinner.check() 1 bypassed' ); return ; }; CertificatePinner.check.overload('java.lang.String' , 'java.util.List' ).implementation = function (p0, p1 ) { send('[SSL Pinning Bypass] CertificatePinner.check() 2 bypassed' ); return ; }; } catch (err) { send('[SSL Pinning Bypass] okhttp CertificatePinner not found' ); } try { var CertificatePinner2 = Java.use('okhttp3.CertificatePinner' ); CertificatePinner2.check.overload('java.lang.String' , 'java.util.List' ).implementation = function (str ) { send('[SSL Pinning Bypass] okhttp3.CertificatePinner.check() bypassed for ' + str); return ; }; } catch (err) { send('[SSL Pinning Bypass] okhttp3 CertificatePinner not found' ); } try { var dataTheorem = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier" ); dataTheorem.verify.overload('java.lang.String' , 'javax.net.ssl.SSLSession' ).implementation = function (str ) { send('[SSL Pinning Bypass] DataTheorem trustkit.pinning.OkHostnameVerifier.verify() 1 bypassed for ' + str); return true ; }; dataTheorem.verify.overload('java.lang.String' , 'java.security.cert.X509Certificate' ).implementation = function (str ) { send('[SSL Pinning Bypass] DataTheorem trustkit.pinning.OkHostnameVerifier.verify() 2 bypassed for ' + str); return true ; }; } catch (err) { send('[SSL Pinning Bypass] DataTheorem trustkit not found' ); } try { var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager' ); PinningTrustManager.checkServerTrusted.implementation = function ( ) { send('[SSL Pinning Bypass] Appcelerator appcelerator.https.PinningTrustManager.checkServerTrusted() bypassed' ); } } catch (err) { send('[SSL Pinning Bypass] Appcelerator PinningTrustManager not found' ); } try { var SSLCertificateChecker = Java.use('nl.xservices.plugins.SSLCertificateChecker' ); SSLCertificateChecker.execute.overload('java.lang.String' , 'org.json.JSONArray' , 'org.apache.cordova.CallbackContext' ).implementation = function (action, args, callbackContext ) { send('[SSL Pinning Bypass] Apache Cordova - SSLCertificateChecker.execute() bypassed' ); callbackContext.success("CONNECTION_SECURE" ); return ; }; } catch (err) { send('[SSL Pinning Bypass] Apache Cordova SSLCertificateChecker not found' ); } try { var wultra = Java.use('com.wultra.android.sslpinning.CertStore' ); wultra.validateFingerprint.overload('java.lang.String' , '[B' ).implementation = function (commonName, fingerprint ) { send('[SSL Pinning Bypass] Wultra com.wultra.android.sslpinning.CertStore.validateFingerprint() bypassed' ); var ValidationResult = Java.use('com.wultra.android.sslpinning.ValidationResult' ); return ValidationResult.TRUSTED; }; } catch (err) { send('[SSL Pinning Bypass] Wultra CertStore.validateFingerprint not found' ); } }, 0 );
android加密 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 Java.perform(function ( ) { var complete_bytes = new Array (); var index = 0 ; var secretKeySpecDef = Java.use('javax.crypto.spec.SecretKeySpec' ); var ivParameterSpecDef = Java.use('javax.crypto.spec.IvParameterSpec' ); var cipherDef = Java.use('javax.crypto.Cipher' ); var Arrays = Java.use('java.util.Arrays' ); var jAndroidLog = Java.use("android.util.Log" ); var jException = Java.use("java.lang.Exception" ); var threadef = Java.use('java.lang.Thread' ); var threadinstance = threadef.$new(); function Where (stack ) { for (var i = 0 ; i < stack.length; ++i){ console .log(stack[i].toString()); } } var cipherDoFinal_1 = cipherDef.doFinal.overload(); var cipherDoFinal_2 = cipherDef.doFinal.overload('[B' ); var cipherDoFinal_3 = cipherDef.doFinal.overload('[B' , 'int' ); var cipherDoFinal_4 = cipherDef.doFinal.overload('[B' , 'int' , 'int' ); var cipherDoFinal_5 = cipherDef.doFinal.overload('[B' , 'int' , 'int' , '[B' ); var cipherDoFinal_6 = cipherDef.doFinal.overload('[B' , 'int' , 'int' , '[B' , 'int' ); var cipherUpdate_1 = cipherDef.update.overload('[B' ); var cipherUpdate_2 = cipherDef.update.overload('[B' , 'int' , 'int' ); var cipherUpdate_3 = cipherDef.update.overload('[B' , 'int' , 'int' , '[B' ); var cipherUpdate_4 = cipherDef.update.overload('[B' , 'int' , 'int' , '[B' , 'int' ); var secretKeySpecDef_init_1 = secretKeySpecDef.$init.overload('[B' , 'java.lang.String' ); var secretKeySpecDef_init_2 = secretKeySpecDef.$init.overload('[B' , 'int' , 'int' , 'java.lang.String' ); var ivParameterSpecDef_init_1 = ivParameterSpecDef.$init.overload('[B' ); var ivParameterSpecDef_init_2 = ivParameterSpecDef.$init.overload('[B' , 'int' , 'int' ); secretKeySpecDef_init_1.implementation = function (arr, alg ) { var key = b2s(arr); var byteArr = Arrays.toString(arr); console .log("\n[*] " +alg + " secret key, plaintext: " + key+" byte[]: " +byteArr); return secretKeySpecDef_init_1.call(this , arr, alg); } secretKeySpecDef_init_2.implementation = function (arr, off, len, alg ) { var key = b2s(arr); var byteArr = Arrays.toString(arr); console .log("\n[*] " +alg + " secret key, plaintext: " + key+" byte[]: " +byteArr); return secretKeySpecDef_init_2.call(this , arr, off, len, alg); } cipherDoFinal_1.implementation = function ( ) { var ret = cipherDoFinal_1.call(this ); info(this .getIV(), this .getAlgorithm(), complete_bytes, ret); return ret; } cipherDoFinal_2.implementation = function (arr ) { addtoarray(arr); var ret = cipherDoFinal_2.call(this , arr); info(this .getIV(), this .getAlgorithm(), complete_bytes, ret); return ret; } cipherDoFinal_3.implementation = function (arr, a ) { addtoarray(arr); var ret = cipherDoFinal_3.call(this , arr, a); info(this .getIV(), this .getAlgorithm(), complete_bytes, ret); return ret; } cipherDoFinal_4.implementation = function (arr, a, b ) { addtoarray(arr); var ret = cipherDoFinal_4.call(this , arr, a, b); info(this .getIV(), this .getAlgorithm(), complete_bytes, ret); return ret; } cipherDoFinal_5.implementation = function (arr, a, b, c ) { addtoarray(arr); var ret = cipherDoFinal_5.call(this , arr, a, b, c); info(this .getIV(), this .getAlgorithm(), complete_bytes, ret); return ret; } cipherDoFinal_6.implementation = function (arr, a, b, c, d ) { addtoarray(arr); var ret = cipherDoFinal_6.call(this , arr, a, b, c, d); info(this .getIV(), this .getAlgorithm(), complete_bytes, c); return ret; } cipherUpdate_1.implementation = function (arr ) { addtoarray(arr); return cipherUpdate_1.call(this , arr); } cipherUpdate_2.implementation = function (arr, a, b ) { addtoarray(arr); return cipherUpdate_2.call(this , arr, a, b); } cipherUpdate_3.implementation = function (arr, a, b, c ) { addtoarray(arr); return cipherUpdate_3.call(this , arr, a, b, c); } cipherUpdate_4.implementation = function (arr, a, b, c, d ) { addtoarray(arr); return cipherUpdate_4.call(this , arr, a, b, c, d); } function info (iv, alg, plain, encoded ) { console .log("\n[*] ------------------------------------------" +alg+"---------------------------------------------" ) var stack = threadinstance.currentThread().getStackTrace(); Where(stack) if (iv) { var byteArr = Arrays.toString(iv); console .log("[*] Initialization Vector: " + b2s(iv) + " byte[]: " +byteArr); } else { console .log("[*] Initialization Vector: " + iv); } console .log("[*] Algorithm: " + alg); console .log("[*] In: " + b2s(plain)); console .log("[*] Out: " + b2s(encoded)); complete_bytes = []; index = 0 ; console .log("[*] -----------------------------------------------------------------------------------------" ) } function hexdump (buffer, blockSize ) { blockSize = blockSize || 16 ; var lines = []; var hex = "0123456789ABCDEF" ; for (var b = 0 ; b < buffer.length; b += blockSize) { var block = buffer.slice(b, Math .min(b + blockSize, buffer.length)); var addr = ("0000" + b.toString(16 )).slice(-4 ); var codes = block.split('' ).map(function (ch ) { var code = ch.charCodeAt(0 ); return " " + hex[(0xF0 & code) >> 4 ] + hex[0x0F & code]; }).join("" ); codes += " " .repeat(blockSize - block.length); var chars = block.replace(/[\\x00-\\x1F\\x20]/g , '.' ); chars += " " .repeat(blockSize - block.length); lines.push(addr + " " + codes + " " + chars); } return lines.join("\\n" ); } function b2s (array ) { var result = "" ; for (var i = 0 ; i < array.length; i++) { result += String .fromCharCode(modulus(array[i], 256 )); } return result; } function modulus (x, n ) { return ((x % n) + n) % n; } function addtoarray (arr ) { for (var i = 0 ; i < arr.length; i++) { complete_bytes[index] = arr[i]; index = index + 1 ; } } });
android哈希 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 Java.perform(function ( ) { var hash_complete_bytes = new Array (); var hash_index = 0 ; var MessageDigestDef = Java.use('java.security.MessageDigest' ); var MacDef = Java.use('javax.crypto.Mac' ); var MessageDigestDef_init_1 = MessageDigestDef.$init.overload('java.lang.String' ); var MessageDigestUpdate_1 = MessageDigestDef.update.overload('[B' ); var MessageDigestUpdate_2 = MessageDigestDef.update.overload('[B' , 'int' , 'int' ); var MessageDigestUpdate_3 = MessageDigestDef.update.overload('java.nio.ByteBuffer' ); var MessageDigestdigest_1 = MessageDigestDef.digest.overload(); var MessageDigestdigest_2 = MessageDigestDef.digest.overload('[B' ); var MessageDigestdigest_3 = MessageDigestDef.digest.overload('[B' ,'int' ,'int' ); var MacdoFinal_1 = MacDef.doFinal.overload(); var MacdoFinal_2 = MacDef.doFinal.overload('[B' ); var MacdoFinal_3 = MacDef.doFinal.overload('[B' ,'int' ); MessageDigestDef_init_1.implementation = function (alg ) { return MessageDigestDef_init_1.call(this ,alg); } MessageDigestUpdate_1.implementation = function (arr ) { hashAddtoarray(arr); return MessageDigestUpdate_1.call(this , arr); } MessageDigestUpdate_2.implementation = function (arr,offset,len ) { hashAddtoarray(arr); return MessageDigestUpdate_2.call(this , arr,offset,len); } MessageDigestUpdate_3.implementation = function (arr ) { hashAddtoarray(arr); return MessageDigestUpdate_3.call(this , arr); } MacdoFinal_1.implementation = function ( ) { var ret = MacdoFinal_1.call(this ); hashInfo(this .getAlgorithm(),hash_complete_bytes,ret,'MacdoFinal_1' ) return ret } MacdoFinal_2.implementation = function (arr ) { hashAddtoarray(arr) var ret = MacdoFinal_2.call(this ,arr); hashInfo(this .getAlgorithm(),hash_complete_bytes,ret,'MacdoFinal_2' ) return ret } MacdoFinal_3.implementation = function (arr,outOffset ) { hashAddtoarray(arr) var ret = MacdoFinal_3.call(this ,arr,outOffset); hashInfo(this .getAlgorithm(),hash_complete_bytes,ret,'MacdoFinal_3' ) return ret } function hashInfo (alg, plain, encoded,method ) { console .log("\n[*] -----------------------------------------" +alg+"--------------------------------------------" ) console .log("[*] method : " +method) console .log("[*] Hash Algorithm : " + alg); console .log("[*] Hash In : " + b2s(plain)); console .log("[*] Hash Out : " + toHexString(encoded)); hash_complete_bytes = []; hash_index = 0 ; console .log(Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Throwable" ).$new())); console .log("[*] --------------------------------------------------------------------------------------------" ) } MessageDigestdigest_1.implementation = function ( ) { var ret = MessageDigestdigest_1.call(this ); hashInfo(this .getAlgorithm(),hash_complete_bytes,ret,'MessageDigestdigest_1' ) return ret } MessageDigestdigest_2.implementation = function (arr ) { var ret = MessageDigestdigest_2.call(this ,arr); var hash = b2s(arr); hashInfo(this .getAlgorithm(),hash_complete_bytes,ret,'MessageDigestdigest_2' ) return ret; } MessageDigestdigest_3.implementation = function (arr,offset,len ) { var ret = MessageDigestdigest_3.call(this ,offset,len); var hash = b2s(arr); hashInfo(this .getAlgorithm(),hash_complete_bytes,ret,'MessageDigestdigest_3' ) return ret; } function hashAddtoarray (arr ) { for (var i = 0 ; i < arr.length; i++) { hash_complete_bytes[hash_index] = arr[i]; hash_index = hash_index + 1 ; } } function toHexString (byteArray ) { var HEX_DIGITS = "0123456789abcdef" ; var HEX_DIGIT_BITS = 4 var HEX_DIGIT_MASK = 0xF var sb='' ; for (var i = 0 ; i < byteArray.length; i++) { var b = byteArray[i] & 0xFF ; sb += HEX_DIGITS.charAt(b >>> HEX_DIGIT_BITS)+HEX_DIGITS.charAt(b & HEX_DIGIT_MASK); } return sb; } function b2s (array ) { var result = "" ; for (var i = 0 ; i < array.length; i++) { result += String .fromCharCode(modulus(array[i], 256 )); } return result; } function modulus (x, n ) { return ((x % n) + n) % n; } });
内存搜索字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function scan ( ) { var result = []; Process.enumerateRanges('r--' ).forEach(function (range ) { if (range.file && range.file.path) { try { Memory.scanSync(range.base, range.size, "31 38 38 34 36" ).forEach(function (match ) { console .log("[*] search : " +range.file.path) console .log(hexdump(match.address)) }); } catch (e) { } } }); console .log("No search" ) }
rootbypass 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 Java.perform(function ( ) { var RootPackages = [ "com.noshufou.android.su" , "com.noshufou.android.su.elite" , "eu.chainfire.supersu" , "com.koushikdutta.superuser" , "com.thirdparty.superuser" , "com.yellowes.su" , "com.koushikdutta.rommanager" , "com.koushikdutta.rommanager.license" , "com.dimonvideo.luckypatcher" , "com.chelpus.lackypatch" , "com.ramdroid.appquarantine" , "com.ramdroid.appquarantinepro" , "com.devadvance.rootcloak" , "com.devadvance.rootcloakplus" , "de.robv.android.xposed.installer" , "com.saurik.substrate" , "com.zachspong.temprootremovejb" , "com.amphoras.hidemyroot" , "com.amphoras.hidemyrootadfree" , "com.formyhm.hiderootPremium" , "com.formyhm.hideroot" , "me.phh.superuser" , "eu.chainfire.supersu.pro" , "com.kingouser.com" ]; var RootBinaries = ["mu" , ".su" , "su" , "busybox" , "supersu" , "Superuser.apk" , "KingoUser.apk" , "SuperSu.apk" ,"mounts" ]; var RootProperties = { "ro.build.selinux" : "1" , "ro.debuggable" : "0" , "service.adb.root" : "0" , "ro.secure" : "1" }; var RootPropertiesKeys = []; for (var k in RootProperties) RootPropertiesKeys.push(k); var NativeFile = Java.use('java.io.File' ); NativeFile.exists.implementation = function ( ) { var name = NativeFile.getName.call(this ); console .log("HOOK exists: " +name) if (RootBinaries.indexOf(name) > -1 ) { console .log("[*] hook:exists() parameter: " +name); return false ; } else { return this .exists.call(this ); } }; var String = Java.use('java.lang.String' ); String .contains.implementation = function (name ) { if (name == "test-keys" ) { console .log("[*] test-keys check Bypass" ); return false ; } return this .contains.call(this , name); }; function isRootCheck (cmd ) { var fakeCmd; console .log("cmd isRootCheck: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" || cmd == "sh" || cmd.indexOf("which" ) != -1 ) { fakeCmd = "grep" ; console .log("[*] cmd Bypass parameter:" +cmd); return fakeCmd; } if (cmd == "su" ) { fakeCmd = "justalled" ; console .log("[*] cmd Bypass parameter:" +cmd); return fakeCmd; } return false ; } function get_implementations (toHook ) { var imp_args = [] toHook.overloads.forEach(function (impl, _ ) { if (impl.hasOwnProperty('argumentTypes' )) { var args = []; var argTypes = impl.argumentTypes argTypes.forEach(function (arg_type, __ ) { args.push(arg_type.className) }); imp_args.push(args); } }); return imp_args; } var Runtime = Java.use('java.lang.Runtime' ); var execImplementations = get_implementations(Runtime.exec) var exec = Runtime.exec.overload('java.lang.String' ) execImplementations.forEach(function (args, _ ) { Runtime.exec.overload.apply(null , args).implementation = function ( ) { var fakeCmd; var argz = [].slice.call(arguments ); var cmd = argz[0 ] if (typeof cmd === 'string' ) { fakeCmd = isRootCheck(cmd); if (fakeCmd) { console .log("[*] hook Runtime.exec string cmd Bypass parameter:" +cmd); return exec.call(this , fakeCmd); } } else if (typeof cmd === 'object' ) { for (var i = 0 ; i < cmd.length; i = i + 1 ) { var tmp_cmd = cmd[i]; fakeCmd = isRootCheck(tmp_cmd); if (fakeCmd) { console .log("[*] hook Runtime.exec object cmd Bypass parameter:" +cmd); return exec.call(this , 'aba' ); } } } return this ['exec' ].apply(this , argz); }; }); var ProcessBuilder = Java.use('java.lang.ProcessBuilder' ); ProcessBuilder.start.implementation = function ( ) { var cmd = this .command.call(this ); var shouldModifyCommand = false ; for (var i = 0 ; i < cmd.size(); i = i + 1 ) { var tmp_cmd = cmd.get(i).toString(); if (tmp_cmd.indexOf("getprop" ) != -1 || tmp_cmd.indexOf("mount" ) != -1 || tmp_cmd.indexOf("build.prop" ) != -1 || tmp_cmd.indexOf("id" ) != -1 ) { shouldModifyCommand = true ; } } if (shouldModifyCommand) { console .log("[*] hook ProcessBuilder cmd Bypass parameter:" +cmd); this .command.call(this , ["grep" ]); return this .start.call(this ); } if (cmd.indexOf("su" ) != -1 ) { console .log("[*] hook ProcessBuilder cmd Bypass parameter:" +cmd); this .command.call(this , ["juslled" ]); return this .start.call(this ); } return this .start.call(this ); } var RootBypass = [{ class : 'android.security.keystore.KeyInfo' , method : 'isInsideSecureHardware' , func : function ( ) { send("[RootDetection Bypass] isInsideSecureHardware" ); console .log("isInsideSecureHardware Bypass" ); return true ; }, target : 6 }, { class : 'android.app.ApplicationPackageManager' , method : 'getPackageInfo' , arguments : ['java.lang.String' , 'int' ], func : function (pname, flags ) { var shouldFakePackage = (RootPackages.indexOf(pname) > -1 ); if (shouldFakePackage) { console .log("[*] hook ApplicationPackageManager check for package:" +pname); pname = "com.cola.zzzz" ; } return this .getPackageInfo.call(this , pname, flags); } }, { class : 'android.os.SystemProperties' , method : 'get' , arguments : ['java.lang.String' ], func : function (name ) { if (RootPropertiesKeys.indexOf(name) != -1 ) { console .log("[*] hook SystemProperties parameter:" +name); return RootProperties[name]; } return this .get.call(this , name); } } ] RootBypass.forEach(function (bypass, _ ) { var toHook; try { if (bypass.target && parseInt (Java.androidVersion, 10 ) < bypass.target) { console .log("[*] unavailable class/method - " +bypass.class + '.' + bypass.method); return } toHook = Java.use(bypass.class)[bypass.method]; if (!toHook) { console .log("[*] Cannot find- " +bypass.class + '.' + bypass.method); return } } catch (err) { console .log("[*] Error- " +bypass.class + '.' + bypass.method+err); return } if (bypass.arguments) { toHook.overload.apply(null , bypass.arguments).implementation = bypass.func; } else { toHook.overload.implementation = bypass.func; } }) Interceptor.attach(Module.findExportByName("libc.so" , "fopen" ), { onEnter : function (args ) { var path = Memory.readCString(args[0 ]); path = path.split("/" ); var executable = path[path.length - 1 ]; var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1 ) if (shouldFakeReturn) { console .log("[*] hook:fopen() parameter: " +path) args[0 ] = Memory.alloc(1 ) } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "system" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:system() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execl" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execl() parameter: " +cmd) console .log("[*] hook:execl() parameter1: " + Memory.readCString(args[1 ])) console .log("[*] hook:execl() parameter2: " + Memory.readCString(args[2 ])) console .log("[*] hook:execl() parameter3: " + Memory.readCString(args[3 ])) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" || cmd.indexOf("netstat" ) != -1 ) { Memory.writeUtf8String(args[0 ], "grepx" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execlp" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execlp() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execve" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execve() parameter: " +cmd) console .log("[*] hook:execl() parameter: " + Memory.readCString(args[1 ])) console .log("[*] hook:execl() parameter: " + Memory.readCString(args[2 ])) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" || cmd.indexOf("netstat" ) != -1 ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execle" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execle() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execlpe" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execlpe() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execv" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execv() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execvp" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execvp() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); Interceptor.attach(Module.findExportByName("libc.so" , "execvpe" ), { onEnter : function (args ) { var cmd = Memory.readCString(args[0 ]); console .log("[*] hook:execvpe() parameter: " +cmd) if (cmd.indexOf("getprop" ) != -1 || cmd == "mount" || cmd.indexOf("build.prop" ) != -1 || cmd == "id" ) { Memory.writeUtf8String(args[0 ], "grep" ); } if (cmd == "su" ) { Memory.writeUtf8String(args[0 ], "justafaled" ); } }, onLeave : function (retval ) { } }); });
SSL双向认证 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 function hook_KeyStore_load ( ) { Java.perform(function ( ) { var ByteString = Java.use("com.android.okhttp.okio.ByteString" ); var myArray=new Array (1024 ); var i = 0 for (i = 0 ; i < myArray.length; i++) { myArray[i]= 0x0 ; } var buffer = Java.array('byte' ,myArray); var StringClass = Java.use("java.lang.String" ); var KeyStore = Java.use("java.security.KeyStore" ); KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter' ).implementation = function (arg0 ) { console .log(Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Throwable" ).$new())); console .log("KeyStore.load1:" , arg0); this .load(arg0); }; KeyStore.load.overload('java.io.InputStream' , '[C' ).implementation = function (arg0, arg1 ) { console .log(Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Throwable" ).$new())); console .log("KeyStore.load2:" , arg0, arg1 ? StringClass.$new(arg1) : null ); if (arg0){ var file = Java.use("java.io.File" ).$new("/sdcard/Download/" + String (arg0)+".p12" ); var out = Java.use("java.io.FileOutputStream" ).$new(file); var r; while ( (r = arg0.read(buffer)) > 0 ){ out.write(buffer,0 ,r) } console .log("save success!" ) out.close() } this .load(arg0, arg1); }; console .log("hook_KeyStore_load..." ); }); }function hook_ssl ( ) { Java.perform(function ( ) { var ClassName = "com.android.org.conscrypt.Platform" ; var Platform = Java.use(ClassName); var targetMethod = "checkServerTrusted" ; var len = Platform[targetMethod].overloads.length; console .log(len); for (var i = 0 ; i < len; ++i) { Platform[targetMethod].overloads[i].implementation = function ( ) { console .log("class:" , ClassName, "target:" , targetMethod, " i:" , i, arguments ); } } }); }function main ( ) { hook_KeyStore_load() } setImmediate(main);
参考文章