Frida是一款基于python + javascript 的hook框架,由于是基于脚本的交互,因此相比xposed和substrace cydia更加便捷。
Frida的官网为:http://www.frida.re/
pip换源 pip国内的一些镜像
阿里云 http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣(douban) http://pypi.douban.com/simple/ 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/ 中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/
修改源方法:
临时使用: 可以在使用pip的时候在后面加上-i参数,指定pip源 pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple
安装 安装frida 首先要保证你的android手机已经root。通过pip安装frida:
pip install frida
下载frida-server:
frida_server的下载地址:https://github.com/frida/frida/releases
到android手机上并且运行
adb push frida-server /data/local/tmp/ adb shell su cd /data/local/tmp/ chmod 777 frida-server ./frida-server
转发android TCP端口到本地:
adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27043
测试frida环境,如果出现android手机的进程列表说明搭建成功:
frida-ps -U frida-ps -R PID Name
安装objection 1 2 pip install objection objection -h
如果报错Running setup.py install for objection … error
更新工具即可
1 2 pip install --upgrade pip pip install --upgrade setuptools
Frida基础 frida 下面是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 Usage: frida [options] target Options: --version show program's version number and exit -h, --help show this help message and exit -D ID, --device=ID connect to device with the given ID -U, --usb connect to USB device -R, --remote connect to remote frida-server -H HOST, --host=HOST connect to remote frida-server on HOST -f FILE, --file=FILE spawn FILE -n NAME, --attach-name=NAME attach to NAME -p PID, --attach-pid=PID attach to PID --debug enable the Node.js compatible script debugger --enable-jit enable JIT -l SCRIPT, --load=SCRIPT load SCRIPT -c CODESHARE_URI, --codeshare=CODESHARE_URI load CODESHARE_URI -e CODE, --eval=CODE evaluate CODE -q quiet mode (no prompt) and quit after -l and -e --no-pause automatically start main thread after startup -o LOGFILE, --output=LOGFILE output to log file
1 2 3 frida-ps -Ua #查看正在运行的 frida-ps -Uai #查看所有
脚本注入 1 frida -U -l myhook.js com.xxx.xxxx
参数解释:
-U 指定对USB设备操作
-l 指定加载一个Javascript脚本
最后指定一个进程名,如果想指定进程pid,用-p
选项。正在运行的进程可以用frida-ps -U
命令查看
重启进程并注入 1 frida -U -l myhook.js -f com.xxx.xxxx --no-pause
参数解释:
-f 指定一个进程,重启它并注入脚本
–no-pause 自动运行程序
这种注入脚本的方法,常用于hook在App就启动期就执行的函数。
frida运行过程中,执行%resume
重新注入,执行%reload
来重新加载脚本;执行exit
结束脚本注入
Hook Java 载入类 Java.use方法用于加载一个Java类,相当于Java中的Class.forName()
。比如要加载一个String类:
1 var StringClass = Java.use("java.lang.String");
加载内部类:
1 var MyClass_InnerClass = Java.use("com.luoyesiqiu.MyClass$InnerClass");
其中InnerClass是MyClass的内部类
修改函数 修改一个函数的实现是逆向调试中相当有用的。修改一个函数的实现后,如果这个函数被调用,我们的Javascript代码里的函数实现也会被调用。
函数类型 不同的参数类型都有自己的表示方法
对于基本类型,直接用它在Java中的表示方法就可以了,不用改变,例如:
int
short
char
byte
boolean
float
double
long
基本类型数组,用左中括号接上基本类型的缩写
基本类型缩写表示表:
基本类型
缩写
boolean
Z
byte
B
char
C
double
D
float
F
int
I
long
J
short
S
例如:int[]
类型,在重载时要写成[I
任意类,直接写完整类名即可
例如:java.lang.String
对象数组,用左中括号接上完整类名再接上分号
例如:[java.lang.String;
构造函数
修改参数为byte[]类型的构造函数的实现
1 2 3 ClassName.$init.overload('[B').implementation=function(param){ //do something }
注:ClassName是使用Java.use定义的类;param是可以在函数体中访问的参数
修改多参数的构造函数的实现
1 2 3 ClassName.$init.overload('[B','int','int').implementation=function(param1,param2,param3){ //do something }
无参
1 2 3 ClassName.$init.overload().implementation=function(){ //do something }
调用原构造函数
1 2 3 4 5 ClassName.$init.overload().implementation=function(){ //do something this.$init(); //do something }
注意:当构造函数(函数)有多种重载形式,比如一个类中有两个形式的func:void func()
和void func(int)
,要加上overload来对函数进行重载,否则可以省略overload
普通函数 修改函数名为func,参数为byte[]类型的函数的实现
1 2 3 4 ClassName.func.overload('[B').implementation=function(param){ //do something //return ... }
无参
1 2 3 ClassName.func.overload().implementation=function(){ //do something }
注: 在修改函数实现时,如果原函数有返回值,那么我们在实现时也要返回合适的值
1 2 3 4 ClassName.func.overload().implementation=function(){ //do something return this.func(); }
调用函数 和Java一样,创建类实例就是调用构造函数,而在这里用$new
表示一个构造函数。
1 2 var ClassName=Java.use("com.luoye.test.ClassName"); var instance = ClassName.$new();
实例化以后调用其他函数
1 2 3 var ClassName=Java.use("com.luoye.test.ClassName"); var instance = ClassName.$new(); instance.func();
字段操作 字段赋值和读取要在字段名后加.value
,假设有这样的一个类:
1 2 3 4 5 package com.luoyesiqiu.app; public class Person{ private String name; private int age; }
写个脚本操作Person类的name字段和age字段:
1 2 3 4 5 6 7 8 9 var person_class = Java.use("com.luoyesiqiu.app.Person"); //实例化Person类 var person_class_instance = person_class.$new(); //给name字段赋值 person_class_instance.name.value = "luoyesiqiu"; //给age字段赋值 person_class_instance.age.value = 18; //输出name字段和age字段的值 console.log("name = ",person_class_instance.name.value, "," ,"age = " ,person_class_instance.age.value);
输出:
1 name = luoyesiqiu , age = 18
类型转换 用Java.cast
方法来对一个对象进行类型转换,如将variable
转换成java.lang.String
:
1 2 var StringClass=Java.use("java.lang.String"); var NewTypeClass=Java.cast(variable,StringClass);
实例 修改返回值 demo 带message
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 package com.example.mytest;import android.os.Bundle;import com.google.android.material.snackbar.Snackbar;import androidx.appcompat.app.AppCompatActivity;import android.view.View;import androidx.navigation.NavController;import androidx.navigation.Navigation;import androidx.navigation.ui.AppBarConfiguration;import androidx.navigation.ui.NavigationUI;import com.example.mytest.databinding.ActivityMainBinding;import android.view.Menu;import android.view.MenuItem;import android.widget.TextView;public class MainActivity extends AppCompatActivity { private AppBarConfiguration appBarConfiguration; private ActivityMainBinding binding; private TextView td; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); NavController navController = Navigation.findNavController(this , R.id.nav_host_fragment_content_main); appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build(); NavigationUI.setupActionBarWithNavController(this , navController, appBarConfiguration); binding.fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick (View view) { Snackbar.make(view, "Replace with your own action:" + isExcellent(), Snackbar.LENGTH_LONG) .setAction("Action" , null ).show(); } }); } public String isExcellent () { return "222str" ; } @Override public boolean onCreateOptionsMenu (Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true ; } @Override public boolean onOptionsItemSelected (MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true ; } return super .onOptionsItemSelected(item); } @Override public boolean onSupportNavigateUp () { NavController navController = Navigation.findNavController(this , R.id.nav_host_fragment_content_main); return NavigationUI.navigateUp(navController, appBarConfiguration) || super .onSupportNavigateUp(); } }
脚本 1 2 3 4 5 6 7 8 9 10 11 if (Java.available){ Java.perform(function ( ) { var MainActivity = Java.use("com.example.mytest.MainActivity" ); MainActivity.isExcellent.implementation=function ( ) { console .log(4556 ) return "124876543" ; } }); }
效果 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 PS D:\WorkingTool\frida\frida-agent -example \agent> frida -U -l .\exp1.js -f com.example.mytest --no -pause ____ / _ | Frida 15.1 .1 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit /quit -> Exit . . . . . . . . More info at https://frida.re/docs/home/ Spawned `com.example.mytest`. Resuming main thread! [M2007J17C ::com.example.mytest ]-> %resume [M2007J17C ::com.example.mytest ]-> message: {'type' : 'send' , 'payload' : '123' } data : None message: {'type' : 'send' , 'payload' : '123' } data : None message: {'type' : 'send' , 'payload' : '123' } data : None message: {'type' : 'send' , 'payload' : '123' } data : None message: {'type' : 'send' , 'payload' : '123' } data : None message: {'type' : 'send' , 'payload' : '123' } data : None [M2007J17C ::com.example.mytest ]-> [M2007J17C ::com.example.mytest ]-> 456 456 456 456 456 456 456
显示身份信息 新建poject->login Activity
使用jeb
使用脚本
1 2 3 4 5 6 7 8 9 if (Java.available){ Java.perform(function ( ) { var LoginActivity = Java.use("com.example.login.ui.login.LoginViewModel" ).login.overload('java.lang.String' , 'java.lang.String' ).implementation = function (str, str2 ) { send("username:" +str+" password:" +str2) } }); }
输入账户密码,点击按钮
效果
1 2 3 4 5 6 7 8 9 10 11 12 PS D:\WorkingTool\frida\frida-agent-example\agent> frida -U -l .\exp2.js -f com.example.login --no-pause ____ / _ | Frida 15.1 .1 - A world-class dynamic instrumentation toolkit | (_ | | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at https: Spawned `com.example.login` . Resuming main thread! [M2007J17C::com.example.login]-> message: {'type' : 'send' , 'payload' : 'username:12345@qq.com password:12345678' } data: None
HooK Native API 讲解
Interceptor.attach(target, callbacks[, data])
: 拦截呼叫以在 .这是一个本地指点,
指定您要拦截呼叫的功能的地址。请注意,在 32 位 ARM 上,该地址必须将其最不重要的位设置为 0 用于 ARM 功能,1 用于拇指功能。弗里达照顾这个细节为你,如果你从弗里达API(例如模块.获取出口比名()的
地址。target
参数是包含以下一个或多个对象:callbacks
onEnter(args)
: 回调函数给出一个参数,可用于将参数作为一系列原点点
对象读取或写入。[: #interceptor参赛者]args
onLeave(retval)
: 回调函数给出一个参数,即包含原始返回值的原点
源性对象。您可以调用用整数替换返回值,或者用指点器替换。请注意,此对象在离开呼叫时 被回收,因此不要将其存储并在回调之外使用。如果您需要存储包含的价值,请制作一份深拷贝,例如: .retval``retval.replace(1337)``1337``retval.replace(ptr("0x1234"))``ptr(retval.toString())
如果挂钩函数非常热,并且可能是自位指
值指向使用**CModule **编译的原生 C 函数。他们的签名是:onEnter``onLeave
void onEnter (GumInvocationContext * ic)
void onLeave (GumInvocationContext * ic)
在这种情况下,第三个可选参数可能是通过访问本地指点。
data``gum_invocation_context_get_listener_function_data()
您也可以通过传递一个函数而不是对象来拦截任意指令。此函数具有相同的签名,但传递给它的参数只会在截获的指令处于函数的开头或寄存器/堆栈尚未偏离该点时给出合理的值。callbacks``onEnter``args
与上图一样,此功能也可以在 C 中通过指定"本地指点"
而不是”功能”来实现。
返回可以调用的听众对象。detach()
请注意,这些功能将被调用与每调用(线程本地)对象,在那里你可以存储任意数据,这是有用的,如果你想阅读一个参数,并采取行动。this``onEnter``onLeave
1 2 3 4 5 6 7 8 9 10 Interceptor.attach(Module.getExportByName('libc.so' , 'read' ), { onEnter (args ) { this .fileDescriptor = args[0 ].toInt32(); }, onLeave (retval ) { if (retval.toInt32() > 0 ) { } } });
此外,对象还包含一些有用的属性:
returnAddress
: 返回地址作为**本地点 **
context
: 具有键的对象,以及分别指定 ia32/x64/臂的 EIP/RIP/PC 和 ESP/RSP/SP 的**原生指 **点对象。其他处理器特定密钥也可用,例如,等。您也可以通过分配到这些密钥来更新注册值。pc``sp``eax``rax``r0``x0
errno
: (UNIX) 当前额值(您可以替换它)
lastError
: (Windows) 当前操作系统错误值(您可以替换它)
threadId
: 操作系统线程 ID
depth
: 相对于其他调用的呼叫深度
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Interceptor.attach(Module.getExportByName(null , 'read' ), { onEnter (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); this .fd = args[0 ].toInt32(); this .buf = args[1 ]; this .count = args[2 ].toInt32(); }, onLeave (result ) { console .log('----------' ) const numBytes = result.toInt32(); if (numBytes > 0 ) { console .log(hexdump(this .buf, { length : numBytes, ansi : true })); } console .log('Result : ' + numBytes); } })
常用代码 免写参数 1 2 3 4 5 6 7 var CodecWarpper = Java.use("xx.CodecWarpper" ); CodecWarpper.encodeRequest.implementation = function ( ) { var ret = this .encodeRequest.apply(this , arguments ); return ret; }
枚举模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Java.perform(function(){ //枚举当前加载的模块 var process_Obj_Module_Arr = Process.enumerateModules(); for(var i = 0; i < process_Obj_Module_Arr.length; i++) { //包含"lib"字符串的 if(process_Obj_Module_Arr[i].path.indexOf("lib")!=-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); } } });
枚举导出函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function frida_Module_import ( ) { Java.perform(function ( ) { const hooks = Module.load('libc.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_import,0 );
bytes2Hex java中 byte范围 -128~127 16进制范围 0 ~ 255
1 2 3 4 5 6 7 8 9 10 11 12 13 function bytes2Hex (arr ) { var str = "[" ; for (var i = 0 ; i < arr.length; i++) { var z = parseInt (arr[i]); if (z < 0 ) z = 255 + z; var tmp = z.toString(16 ); if (tmp.length == 1 ) { tmp = "0" + tmp; } str = str + " " + tmp; } return (str + " ]" ).toUpperCase(); }
获取方法名 1 2 3 4 5 6 7 8 9 10 function getMethodName ( ) { var ret; Java.perform(function ( ) { var Thread = Java.use("java.lang.Thread" ) ret = Thread.currentThread().getStackTrace()[2 ].getMethodName(); }); return ret; }
打印堆栈 1 2 3 4 5 function showStacks ( ) { Java.perform(function ( ) { console .log(Java.use("android.util.Log" ).getStackTraceString(Java.use("java.lang.Exception" ).$new())); }); }
输出类所有方法名 1 2 3 4 5 6 7 8 9 10 11 function enumMethods (targetClass ) { var ret; Java.perform(function ( ) { var hook = Java.use(targetClass); var ret = hook.class.getDeclaredMethods(); ret.forEach(function (s ) { console .log(s); }) }) return ret; }
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 Java.perform( function x() { var targetClass = "com.anchorfree.eliteapi.EliteApi"; var hookCls = Java.use(targetClass); var methods = hookCls.class.getDeclaredMethods(); for (var i in methods) { console.log(methods[i].toString()); console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1"))); console.log(decodeURIComponent(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")))); } hookCls[decodeURIComponent("vpnConfig%24lambda-36%24lambda-34")] .implementation = function (str1,str2) { console.log("-------------------------start Hook-----------------------------"); console.log("参数1:",str1); console.log("参数2:",str2); } } )
hook 所有重载函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function hookAllOverloads (targetClass, targetMethod ) { Java.perform(function ( ) { var targetClassMethod = targetClass + '.' + targetMethod; var hook = Java.use(targetClass); var overloadCount = hook[targetMethod].overloads.length; for (var i = 0 ; i < overloadCount; i++) { hook[targetMethod].overloads[i].implementation = function ( ) { var retval = this [targetMethod].apply(this , arguments ); return retval; } } }); }
输出 byte[] 等 java 对象 1 2 3 4 function jobj2Str (jobject ) { var ret = JSON .stringify(jobject); return ret; }
dump 地址 1 2 3 4 5 6 7 8 9 function dumpAddr (address, length ) { length = length || 1024 ; console .log(hexdump(address, { offset: 0 , length: length, header: true , ansi: false })); }
ArrayBuffer 转换 1 2 3 4 5 6 7 8 function ab2Hex (buffer ) { var arr = Array .prototype.map.call(new Uint8Array (buffer), function (x ) {return ('00' + x.toString(16 )).slice(-2 )}).join(" " ).toUpperCase(); return "[" + arr + "]" ; } function ab2Str (buffer ) { return String .fromCharCode.apply(null , new Uint8Array (buffer)); }
基于python HooK native函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import fridaimport sysrdev = frida.get_remote_device() session = rdev.attach("com.tencent.mm" ) scr = """ Interceptor.attach(Module.findExportByName("libc.so" , "open"), { onEnter: function(args) { send("open("+Memory.readCString(args[0])+","+args[1]+")"); }, onLeave:function(retval){ } }); """ script = session.create_script(scr) def on_message (message ,data ): print message script.on("message" , on_message) script.load() sys.stdin.read()
HooK Java层函数 如下代码为hook微信(测试版本为6.3.13,不同版本由于混淆名字的随机生成的原因或者代码改动导致类名不一样) com.tencent.mm.sdk.platformtools.ay 类的随机数生成函数,让微信猜拳随机(type=2),而摇色子总是为6点(type=5)
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 import fridaimport sysrdev = frida.get_remote_device() session = rdev.attach("com.tencent.mm" ) scr = """ Java.perform(function () { var ay = Java.use("com.tencent.mm.sdk.platformtools.ay"); ay.pu.implementation = function(){ var type = arguments[0]; send("type="+type); if (type == 2) { return this.pu(type); } else { return 5; } }; }); """ script = session.create_script(scr) def on_message (message ,data ): print message script.on("message" , on_message) script.load() sys.stdin.read()
注入dex 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 import frida, sys, optparse, redef on_message (message, data ): if message['type' ] == 'send' : print("[*] {0}" .format (message['payload' ])) else : print(message) jscode = """ Java.perform(function () { var currentApplication = Java.use("android.app.ActivityThread").currentApplication(); var context = currentApplication.getApplicationContext(); var pkgName = context.getPackageName(); var dexPath = "%s"; var entryClass = "%s"; Java.openClassFile(dexPath).load(); console.log("inject " + dexPath +" to " + pkgName + " successfully!") Java.use(entryClass).%s("%s"); console.log("call entry successfully!") }); """ def checkRequiredArguments (opts, parser ): missing_options = [] for option in parser.option_list: if re.match(r'^\[REQUIRED\]' , option.help ) and eval ('opts.' + option.dest) == None : missing_options.extend(option._long_opts) if len (missing_options) > 0 : parser.error('Missing REQUIRED parameters: ' + str (missing_options)) if __name__ == "__main__" : usage = "usage: python %prog [options] arg\n\n" \ "example: python %prog -p com.android.launcher " \ "-f /data/local/tmp/test.apk " \ "-e com.parker.test.DexMain/main " \ "\"hello fridex!\"" parser = optparse.OptionParser(usage) parser.add_option("-p" , "--package" , dest="pkg" , type ="string" , help ="[REQUIRED]package name of the app to be injected." ) parser.add_option("-f" , "--file" , dest="dexPath" , type ="string" , help ="[REQUIRED]path of the dex" ) parser.add_option("-e" , "--entry" , dest="entry" , type ="string" , help ="[REQUIRED]the entry function Name." ) (options, args) = parser.parse_args() checkRequiredArguments(options, parser) if len (args) == 0 : arg = "" else : arg = args[0 ] pkgName = options.pkg dexPath = options.dexPath entry = options.entry.split("/" ) if len (entry) > 1 : entryClass = entry[0 ] entryFunction = entry[1 ] else : entryClass = entry[0 ] entryFunction = "main" process = frida.get_usb_device(1 ).attach(pkgName) jscode = jscode%(dexPath, entryClass, entryFunction, arg) script = process.create_script(jscode) script.on('message' , on_message) print('[*] Running fridex' ) script.load() sys.stdin.read()
Objection基础 基础指令 objection启动并注入内存 1 objection -d -g package_name explore
查看内存中加载的module
查看库的导出函数 1 memory list exports libssl.so
dump内存空间 memory dump all 文件名
1 memory dump from_base 起始地址 字节数 文件名
搜索内存空间 1 Usage: memory search "<pattern eg: 41 41 41 ?? 41>" (--string) (--offsets-only)
内存堆搜索实例 1 android heap search instances 类名
在堆上搜索类的实例 1 2 3 4 5 6 7 8 9 10 sakura@sakuradeMacBook-Pro:~$ objection -g myapplication.example.com.frida_demo explore Using USB device `Google Pixel` Agent injected and responds ok! [usb] # android heap search instances myapplication.example.com.frida_demo .MainActivity Class instance enumeration complete for myapplication.example.com.frida_demo.MainActivity Handle Class toString() -------- ------------------------------------------------- --------------------------------------------------------- 0 x2102 myapplication.example.com.frida_demo.MainActivity myapplication.example.com.frida_demo.MainActivity@5 b1b0af
调用实例的方法 android heap execute 实例ID 实例方法
查看当前可用的activity或者service android hooking list activities/services
直接启动activity或者服务 android intent launch_activity/launch_service activity/服务
android intent launch_activity com.android.settings.DisplaySettings
这个命令比较有趣的是用在如果有些设计的不好,可能就直接绕过了密码锁屏等直接进去。
1 2 3 4 com.android.settings on (google: 8 .1 .0 ) [usb] # android hooking list services com.android.settings.SettingsDumpService com.android.settings.TetherService com.android.settings.bluetooth.BluetoothPairingService
列出内存中所有的类 1 android hooking list classes
在内存中所有已加载的类中搜索包含特定关键词的类。 android hooking search classes display
1 2 3 4 5 6 7 com.android.settings on (google: 8 .1 .0 ) [usb] # android hooking search classes display [Landroid.icu.text.DisplayContext$Type ; [Landroid.icu.text.DisplayContext; [Landroid.view.Display$Mode ; android.hardware.display.DisplayManager android.hardware.display.DisplayManager$DisplayListener android.hardware.display.DisplayManagerGlobal
内存中搜索指定类的所有方法 android hooking list class_methods 类名
1 2 3 4 com.android.settings on (google: 8.1.0) [usb] # android hooking list class_methods java.nio.charset.Charset private static java.nio.charset.Charset java.nio.charset.Charset.lookup(java.lang.String) private static java.nio.charset.Charset java.nio.charset.Charset.lookup2(java.lang.String) private static java.nio.charset.Charset java.nio.charset.Charset.lookupViaProviders(java.lang.String)
在内存中所有已加载的类的方法中搜索包含特定关键词的方法 android hooking search methods display
知道名字开始在内存里搜就很有用
1 2 3 4 5 6 7 com.android.settings on (google: 8.1.0) [usb] # android hooking search methods display Warning, searching all classes may take some time and in some cases, crash the target application. Continue? [y/N]: y Found 5529 classes, searching methods (this may take some time)... android.app.ActionBar.getDisplayOptions android.app.ActionBar.setDefaultDisplayHomeAsUpEnabled android.app.ActionBar.setDisplayHomeAsUpEnabled
hook类的方法 (hook类里的所有方法/具体某个方法)
android hooking watch class 类名
这样就可以hook这个类里面的所有方法,每次调用都会被log出来。
1 android hooking watch class 类名 --dump-args --dump-backtrace --dump-return
在上面的基础上,额外dump参数,栈回溯,返回值
1 android hooking watch class xxx.MainActivity --dump-args --dump-backtrace --dump-return
1 android hooking watch class_method 方法名
1 2 //可以直接hook到所有重载 android hooking watch class_method xxx.MainActivity.fun --dump-args --dump-backtrace --dump-return
grep trick和文件保存 objection log默认是不能用grep过滤的,但是可以通过objection run xxx | grep yyy的
方式,从终端通过管道来过滤。 用法如下
1 2 3 4 5 6 sakura@sakuradeMacBook-Pro:~$ objection -g com.android.settings run memory list modules | grep libc Warning: Output is not to a terminal (fd=1). libcutils.so 0x7a94a1c000 81920 (80.0 KiB) /system/lib64/libcutils.so libc++.so 0x7a9114e000 983040 (960.0 KiB) /system/lib64/libc++.so libc.so 0x7a9249d000 892928 (872.0 KiB) /system/lib64/libc.so libcrypto.so 0x7a92283000 1155072 (1.1 MiB) /system/lib64/libcrypto.so
有的命令后面可以通过--json logfile
来直接保存结果到文件里。 有的可以通过查看.objection
文件里的输出log来查看结果。
1 2 3 4 sakura@sakuradeMacBook-Pro:~/.objection$ cat *log | grep -i display android.hardware.display.DisplayManager android.hardware.display.DisplayManager$DisplayListener android.hardware.display.DisplayManagerGlobal
Xposed基础 框架加载
Xposed Installer | Xposed Module Repository
XposedAPI相关介绍 1、在Zygote启动时调用,用于系统服务的Hook
IXposedHookZygoteInit
2、在资源布局初始化时会回被执行(inflate方法)
IXposedHookInitPackageResources
3、回调方法
handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParamresparam)
InitPackageResourcesParam包含两个参数,包名和XResource(资源相关)
有了这个XResource对象,就可以拿到布局资源树了,通过重写hookLayout方法,
4、LayoutInflatedParam
里面这个view就是布局资源树了,你可以拿到遍历,拿到某个特定控件,然后做一些骚操作。
5、XposeHelpers
提供了一些辅助方法
6、在APP中调用特定方法
callMethod(Objectobj,StringmethodName,Object…args)
参数依次是:调用方法的所在类,调用方法名,方法参数
7、findClass(StringclassName,ClassLoaderclassLoader)
获取class类实例参数依次是类名,类加载器
8、findMethodExact
通过反射查找类的成员方法(可setAccessible(true)设置非私有)
9、findConstructorExact
通过反射查找构造函数(同样可设置可访问下性)
10、findAndHookMethod
查找并Hook
11、XposedBridge.log(“日志内容”):输入日志和写入到/data/xposed/debug.log
XposedInstaller日志那里可以看到!
Xposed模块编写 1、在新创建的android工程Project中创建一个lib目录,然后放入对应版本的XposedBridgeApi-xx.jar,这里用的模拟器是安卓5.1版本,所以放入54的版本进行测试,最后还需要修改gradle的compile为provided(编译的时候用到依赖相应的jar包 但是打包为apk的时候不会进行参与)
jar包链接:https://pan.baidu.com/s/1aMqFmPv1Ya4EOFBW9wE_qA 提取码:kikl
2、然后再AndroidManifest文件中的application标签中加入如下内容
1 2 3 <meta-data android:name ="xposedmodule" android:value ="true" /> <meta-data android:name ="xposeddescription" android:value ="HookTest" /> <meta-data android:name ="xposedminversion" android:value ="54" />
3、在build.grade添加jar包路径compileOnly files(‘libs/XposedBridgeApi-54.jar’)
1 2 3 4 5 6 7 8 9 10 11 dependencies { compileOnly files('libs/XposedBridgeApi-54.jar') implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.0' implementation 'androidx.navigation:navigation-fragment:2.3.5' implementation 'androidx.navigation:navigation-ui:2.3.5' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
然后再弹出来的窗口中点击sync now(这个很重要)
4、回到app/src/main/java/xxx.xxx.xxx(包名)/包名下创建一个类,类名随意(这里创建的类名为Test),代码为如下:自己写的登录框
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 import android.content.Context;import de.robv.android.xposed.IXposedHookLoadPackage;import de.robv.android.xposed.XC_MethodHook;import de.robv.android.xposed.callbacks.XC_LoadPackage;import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;public class Test implements IXposedHookLoadPackage { @Override public void handleLoadPackage (XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { try { if (loadPackageParam.packageName.equals("com.example.loginhooktest" )){ findAndHookMethod("com.example.loginhooktest.MainActivity" ,loadPackageParam.classLoader,"logintest" , new XC_MethodHook(){ protected void beforeHookedMethod (XC_MethodHook.MethodHookParam param) throws Throwable { } protected void afterHookedMethod (XC_MethodHook.MethodHookParam param) throws Throwable { Class aClass = param.thisObject.getClass(); XposedBridge.log("要hook的方法所在的类:" + aClass.getName()); Field field= aClass.getDeclaredField("et2" ); field.setAccessible(true ); EditText et2 = (EditText) field.get(param.thisObject); String str = et2.getText().toString(); XposedBridge.log("劫持到的密码:" + str); } }); } } catch (Exception e){ e.printStackTrace(); } } }
5、在AS项目的app文件夹上点击右键,选择 NEW
-> Folder
-> Assets Folder
,选择 main
,点击ok
6、来到app/src/main/Assets
,右键Assets选择NEW,创建一个File,命名为xposed_init
,里面的内容是hook模块的入口点,本例中的包名以及入口点是com.example.xposedtest.Test 所以这个文件里就写了这个,具体你们的包名是什么,类名是什么,你们自己更改
7、然后打开Xposed选中自己刚才安装的Xposed编写的模块,还需要进行重启模块才会生效
8、查看Xposed的日志,发现HOOK成功
注意:看过很多网上的方法,其中有一个就是把第三步替换成以下内容,
1 2 compileOnly 'de.robv.android.xposed:api:82' compileOnly 'de.robv.android.xposed:api:82:sources'
安装包提取 1、查看所有包名
adb shell pm list package
2、找到对应的包名
adb shell pm path com.istone.xdf
3、提取apk
adb pull /data/app/xxxxx/base.apk
如果有root权限 1、切换权限
su
2、进入app目录
cd /data/app
3、找到对应的包目录进行提取
adb pull /data/app/xxxxx/base.apk
参考 frida的用法–Hook Java代码篇 - luoyesiqiu - 博客园 (cnblogs.com)
Frida入门学习笔记-hook native中的函数(1) - 简书 (jianshu.com)
Android逆向之hook框架frida篇 - 简书 (jianshu.com)
Frida Android hook | Sakuraのblog (eternalsakura13.com)