转载:[翻译] 使用 Frida 来 hack 安卓APP(一)

 

转载:[翻译] 使用 Frida 来 hack 安卓APP(一):https://www.cnblogs.com/pt007/p/11900231.html

 

使用 Frida 来 hack 安卓APP (1):https://www.codemetrix.io/hacking-android-apps-with-frida-1/

使用 Frida 来 hack 安卓APP (2) --- Crackme:https://www.codemetrix.io/hacking-android-apps-with-frida-2/

 

当我去年看 RadareCon 的时候,我学习了一个动态二进制插桩框架 Frida 。而且,最初看起来仅仅是有意思的东西,其实是很有趣的。还记得在游戏里的上帝模式么?在原生应用中使用Frida就是这样的一种感觉。这是一篇介绍如何使用Frida来操作Android apps的博客。 此外,由于我们从事这一方面的工作,我们也打算在在这个博客的第二部分来解决一个简单的Android Crackme。

 

 

什么是动态二进制插桩?

 

动态二进制插桩 ( Dynamic Binary Instrumentation DBI ) , 意味着我们要在已经存在 (或者运行) 的二进制文件中 注入外部代码,来使的它们去做一些它们之前没有做的事情。它并不需要我们写 exp,因为代码注入的发生并不依赖于你是否已经找到了漏洞。它也并不是在 debugging,因为我们并不需要使用 debugger 调试 binary,但是我们仍然可以做一些类似的事情。那么我们可以使用 DBI 做什么呢?有以下一些炫酷的事情:

  • Access process memory。                                              访问进程内存
  • Overwrite functions while the application is running。    在应用运行的时候覆盖函数
  • Call functions from imported classes。                           调用导入的类中的函数
  • Find object instances on the heap and use them。        寻找堆上的对象实例,并且利用它们
  • Hook, trace and intercept functions etc。                        Hook,记录以及拦截函数等等

当然你也可以利用 debugger 来做上面的这些事情,但是却会遇到一系列的问题。例如,在 Android 中,你需要反汇编并且重新编译一个应用之后,它才可以被 debug。一些 app 还会检测,并且会试图阻止 debugger,因此,你还要去想办法跳出对应的逻辑。当然,这是很有可能的,但是确实比较麻烦的。使用 Frida 的 DBI 使得我们不需要知道其中的细节就可以快速开始。

 

 

Frida

 

转载:[翻译] 使用 Frida 来 hack 安卓APP(一)_第1张图片

Frida 允许你将 JavaScript 的部分代码或者你自己的库注入到 windows,macos,linux,IOS,Android,以及 QNX 的原生应用中。它最初是基于 Google 的 V8 Javascript runtime,到了版本9之后,Frida 就开始使用内部的 Duktape 了。但在你需要的情况下,你仍然可以切换到 V8。Frida 有很多模式的操作来和二进制文件进行交互(也有可能在没有 roo t的设备上的 app 中插桩),但是在这里我们就是举一些常用的例子,并且不关心内部的情况。

首先,你需要如下软件

 

  • Frida,在这个教程中我是用的是9.1.16版本
  • 一个 release page 上的 Frida-server 可执行文件( 在写这篇文章的时候,我使用的是frida-server-9.1.16-android-arm.xz,这个可执行文件的版本应该和你的 Frida 版本匹配)
  • 一个安卓模拟器或者被 root 的设备。Frida 是在 Android4.4 上开发的,但它应该也会支持之后的版本。我在这篇教程中成功地在 Android7.1.1 中使用了它。对于第二部分的 crackme,我们无论如何也需要 Android4.4 之后的版本。

同样,我假设你已经有了一个 linux 为主机的操作系统。当然,如果你正在使用 windows 或者 mac,你需要调整一下命令。

如果你想要跟着我在第二部分的讲解中解决 OWASP中的 Unbreakable Crackme Level 1,你应该也需要下载

  • OWASP Uncrackable Crackme Level 1(APK) :https://github.com/OWASP/owasp-mstg/blob/master/OMTG-Files/02_Crackmes/List_of_Crackmes.md
  • BytecodeViewer:http://bytecodeviewer.com/
  • dex2jar:https://github.com/pxb1988/dex2jar

Frida 提供了一系列的 API 以及方法来开始你的旅程。你可以使用命令行窗口或者像 frida-trace 的记录 low-level 函数(例如 libc.so 中的 'open' 调用 ) 的工具来快速运行。你可以使用 C,NodeJs 或者 Python 绑定来完成更加复杂的工作。Frida 在内部使用了很多 JavaScript 语言,因此很多时候你可能都需要和这个语言打交道。因此,如果你也像我一样总是有点不喜欢JavaScript (除了它的 XSS capabilities ),Frida 就是一个你需要熟悉它的理由。

如果你还没有安装 Frida,那就使用下面的方法来安装(当然你也可以根据 README 采用其他的方法来安装):

pip install frida
npm install frida

或者 

pip install frida-tools

frida  命令 帮助:frida -h

frida -h

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
  -F, --attach-frontmost
                        attach to frontmost application
  -n NAME, --attach-name=NAME
                        attach to NAME
  -p PID, --attach-pid=PID
                        attach to PID
  --stdio=inherit|pipe  stdio behavior when spawning (defaults to “inherit”)
  --aux=option          set aux option when spawning, such as “uid=(int)42”
                        (supported types are: string, bool, int)
  --runtime=duk|v8      script runtime to use
  --debug               enable the Node.js compatible script debugger
  --squelch-crash       if enabled, will not dump crash report to console
  -O FILE, --options-file=FILE
                        text file containing additional command line options
  -l SCRIPT, --load=SCRIPT
                        load SCRIPT
  -P PARAMETERS_JSON, --parameters=PARAMETERS_JSON
                        parameters as JSON, same as Gadget
  -C CMODULE, --cmodule=CMODULE
                        load CMODULE
  -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
  --exit-on-error       exit with code 1 after encountering any exception in
                        the SCRIPT

然后启动你的模拟器或者连接到你的设备,并且确保adb可以运行,之后列出你的设备:

michael@sixtyseven:~$ adb devices
List of devices attached 
emulator-5556 device

然后安装 frida-server。从压缩包中提取对应的文件,并把它 push 到设备上:

adb push /home/michael/Downloads/frida-server-9.1.16-android-arm /data/local/tmp/frida-server

使用设备的 adb 来打开一个 shell,并且切换到 root 状态,然后启动 frida:

adb shell
su cd /data/local/tmp
chmod 755 frida-server 
./frida-server

note1:

  • 如果 frida-server 没有启动的话,请确保你处于 root 状态,并且之前传输的那个二进制文件被正确传输了。我曾经由于传输文件不成功而导致了一些奇怪的错误。
  • 如果你想要将 frida-server 作为一个后台程序启动,请使用 ./frifa-server &

在另外一个终端中,一个正常的 OS 的 shell,检查 frida 是否正在运行,并且列出在 Android 上的进程:

frida-ps -U

-U 代表着 USB,并且让 Frida检查 USB-Device,但是使用模拟器也会有这样的效果,你会得到类似于下面的结果:

michael@sixtyseven:~$ frida-ps -U
  PID  Name ----  --------------------------------------------------  
  696  adbd 
  5828 android.ext.services 
  6188 android.process.acore 
  5210 audioserver 
  5211 cameraserver 
  8334 com.android.calendar 
  6685 com.android.chrome 
  6245 com.android.deskclock 
  5528 com.android.inputmethod.latin 
  6120 com.android.phone 
  6485 com.android.printspooler 
  8355 com.android.providers.calendar 
  5844 com.android.systemui 
  7944 com.google.android.apps.nexuslauncher 
  6416 com.google.android.gms 
  [...]

你可以看到进程号id(PID),以及正在运行的程序的名字。利用Frida,你现在就可以hook其中任意一个进程,并且开始进行修改。

e.g. 你可以追踪 Chrome 的某些调用(如果它还没有运行的话,请记得先启动 chrome)。

frida-trace -i "open" -U com.android.chrome

然后你会得到如下的结果:

michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
 Instrumenting functions...                                               
  
 open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js" 
 Started tracing 1 function. Press Ctrl+C to stop.                                   
  
 /* TID 0x2740 */    
 282 ms  open(pathname=0xa843ffc9, flags=0x80002)            
 /* TID 0x2755 */    
 299 ms  open(pathname=0xa80d0c44, flags=0x2)            
 /* TID 0x2756 */    
 309 ms  open(pathname=0xa80d0c44, flags=0x2)            
 /* TID 0x2740 */    
 341 ms  open(pathname=0xa80d06f7, flags=0x2)    
 592 ms  open(pathname=0xa77dd3bc, flags=0x0)    
 596 ms  open(pathname=0xa80d06f7, flags=0x2)    
 699 ms  open(pathname=0xa80d105e, flags=0x80000)    
 717 ms  open(pathname=0x9aff0d70, flags=0x42)    
 742 ms  open(pathname=0x9ceffda0, flags=0x0)    
 758 ms  open(pathname=0xa63b04c0, flags=0x0)

frida-trace 命令会产生一些 javascript 文件,这些文件就是 Frida 注入到进程中用来记录某些调用的。我们可以简单看看生成的open.js,它的目录为 "./__handlers__/libc.so/open.js"。它 hook 了 libc.so 中的 open 函数,然后输出了对应参数。在 Frida 这样做是非常简单的。( 执行命令时,所在的当前目录下生成  __handlers__/libc.so/open.js )

[...]
 onEnter: function (log, args, state) {     
    log("open(" + "pathname=" + args[0] + ", flags=" + args[1] + ")"); 
 }, 
[...]

请注意 Frida 如何提供访问到被 chrome 在内部调用的 open 函数的参数的能力。我们来简单改一下这个脚本。如果我们将对应输出文件的路径名修改为可以阅读的文本格式,而不是这些路径被存储的内存地址,那岂不是更好?幸运的是,我们可以直接利用Frida 来访问内存。简单看一下 Frida 的 API 以及 Memory 对象。我们可以修改我们的脚本,并将内存地址对应的内容以 UTF8 的格式输出,这样我们就可以看到一个更加直观的效果了。修改之后,脚本大概是这个样子的:

onEnter: function (log, args, state) {
     log("open(" + "pathname=" + Memory.readUtf8String(args[0])+ ", flags=" + args[1] + ")"); 
},

我们仅仅添加了 Memory.readUtf8String 函数,然后我们得到了:

michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
 Instrumenting functions...                                               
 open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js" 
 Started tracing 1 function. Press Ctrl+C to stop.                                   
 /* TID 0x29bf */    
 240 ms  open(pathname=/dev/binder, flags=0x80002)            
 /* TID 0x29d3 */    
 259 ms  open(pathname=/dev/ashmem, flags=0x2)            
 /* TID 0x29d4 */    
 269 ms open(pathname=/dev/ashmem, flags=0x2)            
 /* TID 0x29bf */    
 291 ms  open(pathname=/sys/qemu_trace/process_name, flags=0x2)    
 453 ms  open(pathname=/dev/alarm, flags=0x0)    
 456 ms  open(pathname=/sys/qemu_trace/process_name, flags=0x2)    
 562 ms  open(pathname=/proc/self/cmdline, flags=0x80000)    
 576 ms  open(pathname=/data/dalvik-cache/arm/system@app@[email protected]@classes.dex.flock, flags=0x42)

Frida 输出了对应的路径名,对吧?

另一个需要注意的事情是,你不仅可以在利用 Frida 注入到某个进程之前启动它,你还可以利用 -f 参数来使得 Frida 自己产生对应的进程。

现在我们来看一下 Frida 的命令行接口 frida-cli :

frida -U com.android.chrome

这将会启动 Frida 和 Chrome app。但是它并不会启动 chrome 的主进程。这也就意味着,这样就给了你机会使得你可以在主线程启动之前注入Frida 代码。不幸的是,在我这里,这样做总会使得 app 在两秒之后被自动杀死。这并不是我们想要的。正如 cli 输出所建议的,你可以利用这两秒去输入 %resume,使得 app 启动其主线程。或者你也可以直接使用 --no-pause 参数来使得无论如何也不要中断 app 的启动,同时,将生成进程的工作交给 Frida。

转载:[翻译] 使用 Frida 来 hack 安卓APP(一)_第2张图片

无论你是用哪一种方法,你都会得到一个不会被 kill 的 shell,利用这个你就可以使用 Frida 的 JavaScript 的 API 向其中写命令。你还可以利用 TAB 键来看一下一些可以使用的命令。这个 shell 是支持补全命令。

转载:[翻译] 使用 Frida 来 hack 安卓APP(一)_第3张图片

大部分你想要做的事情都会有对应的文档。对于 Android,你可以看一下 Javascript API 部分的 Java section( 尽管从技术角度出发,应该说是访问 java 对象的 Javascript 外壳,但我这里就直接说是 " Java API " )。我们将在下面关注 Java API,因为这是一个相对来说比较容易与 app 进行交互的方法了。我们并不需要直接 hook libc 函数,我们可以直接与 java 函数以及对象进行交互。(注意,如果你对使用 Frida 做其它的事情感兴趣的话,你可以 hook 更低一级的 C 代码,也就是我们使用的 frida-trace,你可以看一看文档中的 functions 。这里我不打算将这一部分。)

为了熟悉 Java API 的访问,简单利用 Frida 的命令行来看一下 Android 的版本吧

[USB::Android Emulator 5556::['com.android.chrome']]-> Java.androidVersion
 "7.1.1"

或者列出所有被加载的类(警告:这将会输出一大堆东西,我下面就会对这些代码进行介绍):

[USB::Android Emulator 5556::['com.android.chrome']]-> Java.perform(function()
{Java.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) 
},"onComplete":function(){}})})
  org.apache.http.HttpEntityEnclosingRequest 
  org.apache.http.ProtocolVersion 
  org.apache.http.HttpResponse 
  org.apache.http.impl.cookie.DateParseException 
  org.apache.http.HeaderIterator

这里我们输入了一个很长的命令,我们需要明确一下其中嵌入的代码。首先,我们输入的代码的最外层包装是Java.perform(function(){ ... }),这是 Fridas Java API 的需求。

下面是我们在 Java.perform 中插入的函数:

Java.enumerateLoadedClasses
(
   {   "onMatch": function(className){          
           console.log(className)      
       },   
      "onComplete":function(){}   
   } 
)

这一步相当简单,我们利用 Java.enumerateLoadedClasses 来枚举所有被装载的类,然后利用 console.log 来输出所有的匹配。你将会在 Frida 中遇到很多这样的回调函数。你会提供下面样子的回调对象:

{
   "onMatch":function(arg1, ...){ ... },   
   "onComplete":function(){ ... }, 
}

一旦 Frida 在你的请求中发现了一个匹配,onMatch 会被一个有 一个或者多个参数 的函数所调用。然后当 Frida 枚举完所有的可能的匹配后,就会调用这个函数。

现在,让我们更加深入地了解一下 Frida 的魔法吧,然后利用 Frida 来覆盖一个函数,除此之外,我们也同样加载了一个外部分脚本,而不是把它出入到 cli 中,这样会更加方便一点。把下面的代码保存到一个叫脚本文件中,例如 chrome.js :

Java.perform(function () {
     var Activity = Java.use("android.app.Activity");     
     Activity.onResume.implementation = function () {         
          console.log("[*] onResume() got called!");         
          this.onResume();     
         }; 
});

这个函数会重写 android.app.Activity 类的 onResume 函数。他利用 Java.use 来接受这个类的一个封装对象,然后访问他的onResume 函数的 implementation 的属性来提供一个新的接口。在函数的内部,他会通过 this.onResume() 来直接调用原始的onResume 接口,因此这个 app 可以正常地运行。

打开你的模拟器,然后打开chrome,并利用 -l 来注入对应的脚本:

frida -U -l chrome.js com.android.chrome

一旦你触发了 onResume   -e.g.  通过切换到其它的应用,然后返回到模拟器中的 chrome,你将会得到

[*] onResume() got called!

漂亮,难道不是么?我们确实重写了一个 app 中的函数。因此,我们就很有可能控制目标 app 的行为了。但是我们可以做的更多:我们可以利用 Java.choose 来寻找堆上实例化的对象。

在我们继续之前,需要注意一点:当你的模拟器有点慢的时候,Frida 可能会超时。 为了防止这个发生,要么在函数 setImmediate 中给你的脚本添加一层包装,要么 export them as rpc 。Frida 中的 RPC 默认不会超时 ( 这里要感谢@oleavr 给出这些建议 )。一旦你修改了脚本,setImmediate 就会自动返回它,因此这相当方便。它同时在后台运行你的程序。这也就意味着,你会立即得到一个 cli,即使 Frida 还在执行你的脚本。我们需要做的就是等待,然后不要离开 cli,直到 Frida 已经把你脚本里所有的输出都打印出来了。再次修改一下你的 chrome.js 脚本:

setImmediate(function() {
     console.log("[*] Starting script");     
     Java.perform(function () {          
      
         Java.choose("android.view.View", {                            
             "onMatch":function(instance){                   
                    console.log("[*] Instance found");              
              },               
              "onComplete":function() {                   
                    console.log("[*] Finished heap search")              
              }         
         });      
      }); 
});

然后通过下面的指令运行:

frida =U -l chrome.js com.android.chrome

应该会输出如下的结果:

[*] Starting script
[*] Instance found 
[*] Instance found 
[*] Instance found 
[*] Instance found 
[*] Finished heap search

可以看出,我们在堆上找到了 android.view.View 的四个对象。让我们继续来看看我们可以利用这些东西再做些什么。或许我们可以调用这些实例的方法。我们只需要简单地添加 instance.toString() 到我们的 console.log 中 ( 因为我们使用了setImmediate,我们可以直接修改我们的代码,Frida 会自动重新加载它):

setImmediate(function() {
     console.log("[*] Starting script");     
     Java.perform(function () {          
          Java.choose("android.view.View", {                            
              "onMatch":function(instance){
                    console.log("[*] Instance found: " + instance.toString());              
               },               
               "onComplete":function() {                 
                    console.log("[*] Finished heap search")              
               }         
          });      
    }); 
});

这会返回:

[*] Starting script
[*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background} 
[*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub} 
[*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator} 
[*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground} 
[*] Finished heap search

Frida 确实调用了 android.view.View 的 toString 方法。相当酷。因此,在 Frida 的帮助下,我们可以读取进程的内存,修改函数,然后找到存在的实例化对象,然后使用简单的几行代码来使用它。

现在你应该对 Frida 有了一个基本的了解,而且应该能够自己去更进一步地挖掘它的文档与 API 了。在结束这篇文章之前,我想要添加两个主题,Frida' binding 以及 r2frida,但是首先,我们需要注意如下的事情。

 

注意事项

 

当你在用 Frida 的时候,你可能会发现不稳定性。首先,将外部代码注入到其它的进程中后,由于 app 将会以不被期望的方式运行,所以可能会导致崩溃。其次,Frida 本身也仍处于试验期阶段。虽然它也是有效的,但通常你需要尝试很多种方法才能够得到结果。例如,当我尝试在命令行中使用一条命令加载一个脚本并且生成一个进程的时候,Frida 经常会崩溃。取而代之的,我需要首先启动进程,然后再利用 Frida 注入代码。这就是我为什么给你展示了使用 Frida 的不同的方法以及防止延迟。 你需要自己去判断哪一个更加适合自己。

 

 

Python bindings

 

如果你想要利用 Frida 自动化工作,你可能需要 python、C 或者 NodeJS 了。例如,使用 python 注入 chrome.js 脚本,你可能需要使用 Frida 的 Python bindings,然后创建一个脚本。

#!/usr/bin/python
import frida  
 
# put your javascript-code here 
jscode= """ 
 
console.log("[*] Starting script");  
 
Java.perform(function() {        
 
   var Activity = Java.use("android.app.Activity");     
   Activity.onResume.implementation = function () {         
      console.log("[*] onResume() got called!");         
      this.onResume();     
   };  
}); 
"""  
 
# startup frida and attach to com.android.chrome process on a usb device 
session = frida.get_usb_device().attach("com.android.chrome")  
 
# create a script for frida of jsccode 
script = session.create_script(jscode)  
 
# and load the script 
script.load()

如果你想要结束你的 Frida会话并且销毁在这个会话中插入的脚本,你可以调用 session.detach()。

关于更多的例如,请参考 Frida 的 documentation。

 

 

Frida 和 Radare2 :r2frida

 

如果可以使用一个类似于 Radare2 的反汇编框架来检查我们的 app 内存,那岂不是更好。因此有了 r2frida。你可以利用 r2frida 来将 Radare2 连接到 Firda。然后使用静态分析以及反汇编进程内存。由于它需要 Radare2(如果你没有看的话,非常值得看一看)作为铺垫,我在这里并不打算详细介绍 r2frida。但是,我还是打算告诉你如何简单地使用它。

你可以利用 Radare2 的包管理器(假设你已经装了Radare2)来装 r2frida。

r2pm install r2frida

我们再次回到 frida-trace 的例子,删除或者重命名我们已经修改的脚本,然后让 frida-trace 自动生成默认的脚本,然后我们再来简单看一下日志:

michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
Instrumenting functions...                                               
open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js" 
Started tracing 1 function. Press Ctrl+C to stop.                                   
 
/* TID 0x2740 */    
282 ms  open(pathname=0xa843ffc9, flags=0x80002)            
/* TID 0x2755 */    
[...]

通过 r2frida,你可以非常容易地检查内存地址中的内容,并且读取对应的文件名(在这个例子中是/dev/binder):

root@sixtyseven:~# r2 frida://emulator-5556/com.android.chrome
  -- Enhance your graphs by increasing the size of the block and graph.depth eval variable. 
  [0x00000000]> s 0xa843ffc9 
  [0xa843ffc9]> px 
  - offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF 
  0xa843ffc9  2f64 6576 2f62 696e 6465 7200 4269 6e64  /dev/binder.Bind 
  0xa843ffd9  6572 2069 6f63 746c 2074 6f20 6f62 7461  er ioctl to obta 
  0xa843ffe9  696e 2076 6572 7369 6f6e 2066 6169 6c65  in version faile 
  0xa843fff9  643a 2025 7300 4269 6e64 6572 2064 7269  d: %s.Binder dri 
  [...]

使用 r2frida 来访问对应的内存并且使用它来注入代码的语法如下:

r2 frida://DEVICE-ID/PROCESS

你同样可以使用 =! 来检查你可以使用那些命令。你还可以快速查找内存中的特定内容或者将内容写到任意地址。

[0x00000000]> =!?
 r2frida commands available via =! 
 ?                         Show this help 
 ?V                         Show target Frida version 
 /[x][j]   Search hex/string pattern in memory ranges (see search.in=?) 
 /w[j] string               Search wide string 
 [...]

 

 

后续

 

如果这个使你感兴趣的话,你可以看看下面的内容

• Frida 的 project page

• youtube上的@oleavr在r2con上的讲话,以及David Weinstein的 Frida Intro talk

• Frida的Twitter @fridadotre

• Frida的Telegram channel

•  AppMon, 基于Frida的app监视以及注入的可视化工具(by @dpnishant)

 

在这个教程的第二部分,我们将会利用Frida来解决一个简单的crackme。

如果有任何评论,问题或者批评等等,请在 Twitter 上面联系我。

原文链接:https://www.codemetrix.net/hacking-android-apps-with-frida-1/

本文由 看雪翻译小组成员 iromise 翻译

 

 

 

 

你可能感兴趣的:(Hook,框架,之,Frida)