抖音数据采集教程,逆向神器 frida 介绍
短视频、直播数据实时采集接口,请查看文档: TiToData
免责声明:本文档仅供学习与参考,请勿用于非法用途!否则一切后果自负。<br>
frida是啥?
首先,frida是啥,github目录Awesome Frida这样介绍frida的:
Frida is Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript into native apps that run on Windows, Mac, Linux, iOS and Android. Frida is an open source software.
frida是平台原生app的Greasemonkey,说的专业一点,就是一种动态插桩工具,可以插入一些代码到原生app的内存空间去,(动态地监视和修改其行为),这些原生平台可以是Win、Mac、Linux、Android或者iOS。而且frida还是开源的。Greasemonkey可能大家不明白,它其实就是firefox的一套插件体系,使用它编写的脚本可以直接改变firefox对网页的编排方式,实现想要的任何功能。而且这套插件还是外挂的,非常灵活机动。frida也是一样的道理。
frida为什么这么火?
动静态修改内存实现作弊一直是刚需,比如金山游侠,本质上frida做的跟它是一件事情。原则上是可以用frida把金山游侠,包括CheatEngine等“外挂”做出来的。
当然,现在已经不是直接修改内存就可以高枕无忧的年代了。大家也不要这样做,做外挂可是违法行为。
在逆向的工作上也是一样的道理,使用frida可以“看到”平时看不到的东西。出于编译型语言的特性,机器码在CPU和内存上执行的过程中,其内部数据的交互和跳转,对用户来讲是看不见的。当然如果手上有源码,甚至哪怕有带调试符号的可执行文件包,也可以使用gdb、lldb等调试器连上去看。
那如果没有呢?如果是纯黑盒呢?又要对app进行逆向和动态调试、甚至自动化分析以及规模化收集信息的话,我们需要的是细粒度的流程控制和代码级的可定制体系,以及不断对调试进行动态纠正和可编程调试的框架,这就是frida。frida使用的是python、JavaScript等“胶水语言”也是它火爆的一个原因,可以迅速将逆向过程自动化,以及整合到现有的架构和体系中去,为你们发布“威胁情报”、“数据平台”甚至“AI风控”等产品打好基础。
官宣屁屁踢甚至将其敏捷开发和迅速适配到现有架构的能力作为其核心卖点。
frida实操环境
主机:
Host:Macbook Air CPU: i5 Memory:8G System:Kali Linux 2018.4 (Native,非虚拟机)
客户端:
client:Nexus 6 shamu CPU:Snapdragon 805 Mem:3G System:lineage-15.1-20181123-NIGHTLY-shamu,android 8.1
用kali linux的原因是工具很全面,权限很单一,只有一个root,作为原型开发很好用,否则python和node的各种权限、环境和依赖实在是烦。用lineage因为它有便利的网络ADB调试,可以省掉一个usb数据线连接的过程。(虽然真实的原因是没钱买新设备,Nexus 6官方只支持到7.1.1,想上8.1只有lineage一个选择。)记得需要刷进去一个lineage的su包,获取root权限,frida是需要在root权限下运行的。
首先到官网下载一个platform-tools的linux版本——SDK Platform-Tools for Linux,下载解压之后可以直接运行里面的二进制文件,当然也可以把路径加到环境里去。这样adb和fastboot命令就有了。
然后再将frida-server下载下来,拷贝到安卓机器里去,使用root用户跑起来,保持adb的连接不要断开。
$ ./adb root # might be required$ ./adb push frida-server /data/local/tmp/$ ./adb shell "chmod 755 /data/local/tmp/frida-server"$ ./adb shell "/data/local/tmp/frida-server &"
最后在kali linux里安装好frida即可,在kali里安装frida真是太简单了,一句话命令即可,保证不出错。(可能会需要先安装pip,也是一句话命令:curl [https://bootstrap.pypa.io/get-pip.py](https://bootstrap.pypa.io/get-pip.py) -o get-pip.py)
pip install frida-tools
然后用frida-ps -U命令连上去,就可以看到正在运行的进程了。
root@kali:~# frida-ps -UWaiting for USB device to appear... PID Name---- ----------------------------------------------- 431 ATFWD-daemon3148 adbd 391 adspd2448 android.ext.services 358 android.hardware.cas@1.0-service 265 android.hardware.configstore@1.0-service 359 android.hardware.drm@1.0-service 360 android.hardware.dumpstate@1.0-service.shamu 361 android.hardware.gnss@1.0-service 266 android.hardware.graphics.allocator@2.0-service 357 android.hidl.allocator@1.0-service ... ...
基本能力Ⅰ:hook参数、修改结果
先自己写个app:
package com.roysue.demo02;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fun(50,30); } } void fun(int x , int y ){ Log.d("Sum" , String.valueOf(x+y)); }}
原理上很简单,就是间隔一秒在控制台输出一下fun(50,30)函数的结果,fun()这个函数的作用是求和。那最终结果在控制台如下所示。
$ adb logcat |grep Sum11-26 21:26:23.234 3245 3245 D Sum : 8011-26 21:26:24.234 3245 3245 D Sum : 8011-26 21:26:25.235 3245 3245 D Sum : 8011-26 21:26:26.235 3245 3245 D Sum : 8011-26 21:26:27.236 3245 3245 D Sum : 8011-26 21:26:28.237 3245 3245 D Sum : 8011-26 21:26:29.237 3245 3245 D Sum : 80
现在我们来写一段js代码,并用frida-server将这段代码加载到com.roysue.demo02中去,执行其中的hook函数。
$ nano s1.js
console.log("Script loaded successfully ");Java.perform(function x() { console.log("Inside java perform function"); //定位类 var my_class = Java.use("com.roysue.demo02.MainActivity"); console.log("Java.Use.Successfully!");//定位类成功! //在这里更改类的方法的实现(implementation) my_class.fun.implementation = function(x,y){ //打印替换前的参数 console.log( "original call: fun("+ x + ", " + y + ")"); //把参数替换成2和5,依旧调用原函数 var ret_value = this.fun(2, 5); return ret_value; }});
然后我们在kali主机上使用一段python脚本,将这段js脚本“传递”给安卓系统里正在运行的frida-server。
$ nano loader.py
import timeimport frida# 连接安卓机上的frida-serverdevice = frida.get_usb_device()# 启动`demo02`这个apppid = device.spawn(["com.roysue.demo02"])device.resume(pid)time.sleep(1)session = device.attach(pid)# 加载s1.js脚本with open("s1.js") as f: script = session.create_script(f.read())script.load()# 脚本会持续运行等待输入raw_input()
然后得保证frida-server正在运行,方法可以是在kali主机输入frida-ps -U命令,如果安卓机上的进程出现了,则frida-server运行良好。
还需要保证selinux是关闭的状态,可以在adb shell里,su -获得root权限之后,输入setenforce 0命令来获得,在Settings→About Phone→SELinux status里看到Permissive,说明selinux关闭成功。
然后在kali主机上输入python loader.js,可以观察到安卓机上com.roysue.demo02这个app马上重启了。然后$ adb logcat|grep Sum里的内容也变了。
11-26 21:44:47.875 2420 2420 D Sum : 8011-26 21:44:48.375 2420 2420 D Sum : 8011-26 21:44:48.875 2420 2420 D Sum : 8011-26 21:44:49.375 2420 2420 D Sum : 8011-26 21:44:49.878 2420 2420 D Sum : 711-26 21:44:50.390 2420 2420 D Sum : 711-26 21:44:50.904 2420 2420 D Sum : 711-26 21:44:51.408 2420 2420 D Sum : 711-26 21:44:51.921 2420 2420 D Sum : 711-26 21:44:52.435 2420 2420 D Sum : 711-26 21:44:52.945 2420 2420 D Sum : 711-26 21:44:53.459 2420 2420 D Sum : 711-26 21:44:53.970 2420 2420 D Sum : 711-26 21:44:54.480 2420 2420 D Sum : 7
在kali主机上可以观察到:
$ python loader.pyScript loaded successfullyInside java perform functionJava.Use.Successfully!original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)original call: fun(50, 30)
说明脚本执行成功了,代码也插到com.roysue.demo02这个包里去,并且成功执行了,s1.js里的代码成功执行了,并且把交互结果传回了kali主机上。
基本能力Ⅱ:参数构造、方法重载、隐藏函数的处理
我们现在把app的代码稍微写复杂一点点:
package com.roysue.demo02;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity { private String total = "@@@###@@@"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fun(50,30); Log.d("ROYSUE.string" , fun("LoWeRcAsE Me!!!!!!!!!")); } } void fun(int x , int y ){ Log.d("ROYSUE.Sum" , String.valueOf(x+y)); } String fun(String x){ total +=x; return x.toLowerCase(); } String secret(){ return total; }}
app运行起来后在使用logcat打印出来的日志如下:
$ adb logcat |grep ROYSUE11-26 22:22:35.689 3051 3051 D ROYSUE.Sum: 8011-26 22:22:35.689 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!!11-26 22:22:36.695 3051 3051 D ROYSUE.Sum: 8011-26 22:22:36.696 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!!11-26 22:22:37.696 3051 3051 D ROYSUE.Sum: 8011-26 22:22:37.696 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!!11-26 22:22:38.697 3051 3051 D ROYSUE.Sum: 8011-26 22:22:38.697 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!!11-26 22:22:39.697 3051 3051 D ROYSUE.Sum: 8011-26 22:22:39.698 3051 3051 D ROYSUE.string: lowercase me!!!!!!!!!
可以看到fun()方法有了重载,在参数是两个int的情况下,返回两个int之和。在参数为String类型之下,则返回字符串的小写形式。
另外,secret()函数为隐藏方法,在app里没有被直接调用。
这时候如果我们直接使用上一节里面的js脚本和loader.js来加载的话,肯定会崩溃。为了看到崩溃的信息,我们对loader.js做一些处理。
def my_message_handler(message , payload): #定义错误处理 print message print payload...script.on("message" , my_message_handler) #调用错误处理script.load()
再运行$ python loader.py的话,就会看到如下的错误信息返回:
$ python loader.pyScript loaded successfullyInside java perform functionJava.Use.Succes.........