抖音数据采集教程,一篇文章带你领悟Frida的精华

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是平台原生appGreasemonkey,说的业余一点,就是一种动静插桩工具,能够插入一些代码到原生app的内存空间去,(动静地监督和批改其行为),这些原生平台能够是WinMacLinuxAndroid或者iOS。而且frida还是开源的。
Greasemonkey可能大家不明确,它其实就是firefox的一套插件体系,应用它编写的脚本能够间接扭转firefox对网页的编排形式,实现想要的任何性能。而且这套插件还是外挂的,十分灵活机动。
frida也是一样的情理。

frida为什么这么火?

动动态批改内存实现舞弊始终是刚需,比方金山游侠,实质上frida做的跟它是一件事件。原则上是能够用frida把金山游侠,包含CheatEngine等“外挂”做进去的。
当然,当初曾经不是间接批改内存就能够居安思危的年代了。大家也不要这样做,做外挂可是违法行为。
在逆向的工作上也是一样的情理,应用frida能够“看到”平时看不到的货色。出于编译型语言的个性,机器码在CPU和内存上执行的过程中,其外部数据的交互和跳转,对用户来讲是看不见的。当然如果手上有源码,甚至哪怕有带调试符号的可执行文件包,也能够应用gbdlldb等调试器连上去看。
那如果没有呢?如果是纯黑盒呢?又要对app进行逆向和动静调试、甚至自动化剖析以及规模化收集信息的话,咱们须要的是细粒度的流程管制和代码级的可定制体系,以及一直对调试进行动静纠正和可编程调试的框架,这就是frida
frida应用的是pythonJavaScript等“胶水语言”也是它火爆的一个起因,能够迅速将逆向过程自动化,以及整合到现有的架构和体系中去,为你们公布“威逼情报”、“数据平台”甚至“AI风控”等产品打好根底。



官宣屁屁踢甚至将其麻利开发迅速适配到现有架构的能力作为其外围卖点。

frida实操环境

主机:

Host:Macbook Air CPU: i5 Memory:8GSystem:Kali Linux 2018.4 (Native,非虚拟机)

客户端:

client:Nexus 6 shamu CPU:Snapdragon 805 Mem:3GSystem:lineage-15.1-20181123-NIGHTLY-shamu,android 8.1

kali linux的起因是工具很全面,权限很繁多,只有一个root,作为原型开发很好用,否则pythonnode的各种权限、环境和依赖切实是烦。用lineage因为它有便当的网络ADB调试,能够省掉一个usb数据线连贯的过程。(尽管实在的起因是没钱买新设施,Nexus 6官网只反对到7.1.1,想上8.1只有lineage一个抉择。)记得须要刷进去一个lineagesu包,获取root权限,frida是须要在root权限下运行的。
首先到官网下载一个platform-tools的linux版本——SDK Platform-Tools for Linux,下载解压之后能够间接运行外面的二进制文件,当然也能够把门路加到环境里去。这样adbfastboot命令就有了。
而后再将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)]([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.Successfully!{u'columnNumber': 1, u'description': u"Error: fun(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload('java.lang.String')\n\t.overload('int', 'int')", u'fileName': u'frida/node_modules/frida-java/lib/class-factory.js', u'lineNumber': 2233, u'type': u'error', u'stack': u"Error: fun(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload('java.lang.String')\n\t.overload('int', 'int')\n    at throwOverloadError (frida/node_modules/frida-java/lib/class-factory.js:2233)\n    at frida/node_modules/frida-java/lib/class-factory.js:1468\n    at x (/script1.js:14)\n    at frida/node_modules/frida-java/lib/vm.js:43\n    at M (frida/node_modules/frida-java/index.js:347)\n    at frida/node_modules/frida-java/index.js:299\n    at frida/node_modules/frida-java/lib/vm.js:43\n    at frida/node_modules/frida-java/index.js:279\n    at /script1.js:15"}None

能够看出是一个throwOverloadError,这时候就是因为咱们没有解决重载,造成的重载处理错误。这个时候就须要咱们来解决重载了,在js脚本中解决重载是这样写的:

my_class.fun.overload("int" , "int").implementation = function(x,y){...my_class.fun.overload("java.lang.String").implementation = function(x){

其中参数均为两个int的状况下,上一节曾经讲过了。参数为String类的时候,因为String类不是Java根本数据类型,而是java.lang.String类型,所以在替换参数的结构上,须要花点心理。

var string_class = Java.use("java.lang.String"); //获取String类型my_class.fun.overload("java.lang.String").implementation = function(x){  console.log("*************************************");  var my_string = string_class.$new("My TeSt String#####"); //new一个新字符串  console.log("Original arg: " +x );  var ret =  this.fun(my_string); // 用新的参数替换旧的参数,而后调用原函数获取后果  console.log("Return value: "+ret);  console.log("*************************************");  return ret;};

这样咱们对于重载函数的解决就算是ok了。咱们到试验里来看下:

$ python loader.pyScript loaded successfullyInside java perform functionoriginal call: fun(50, 30)*************************************Original arg: LoWeRcAsE Me!!!!!!!!!Return value: my test string#####*************************************original call: fun(50, 30)*************************************Original arg: LoWeRcAsE Me!!!!!!!!!Return value: my test string#####*************************************original call: fun(50, 30)*************************************Original arg: LoWeRcAsE Me!!!!!!!!!Return value: my test string#####*************************************

而后logcat打进去的后果也变了。

$ adb logcat |grep ROYSUE11-26 22:23:29.597  3244  3244 D ROYSUE.Sum: 711-26 22:23:29.673  3244  3244 D ROYSUE.string: my test string#####11-26 22:23:30.689  3244  3244 D ROYSUE.Sum: 711-26 22:23:30.730  3244  3244 D ROYSUE.string: my test string#####11-26 22:23:31.740  3244  3244 D ROYSUE.Sum: 711-26 22:23:31.789  3244  3244 D ROYSUE.string: my test string#####11-26 22:23:32.797  3244  3244 D ROYSUE.Sum: 711-26 22:23:32.833  3244  3244 D ROYSUE.string: my test string#####

最初再说一下暗藏办法的调用,frida对其的解决方法跟Xposed是十分像的,Xposed应用的是XposedHelpers.findClass("com.example.inner_class_demo.demo",lpparam.classLoader);办法,间接findClass,其实frida也十分相似,也是应用的间接到内存里去寻找的办法,也就是Java.choose(className, callbacks)函数,通过类名触发回掉函数。

Java.choose("com.roysue.demo02.MainActivity" , {  onMatch : function(instance){ //该类有多少个实例,该回调就会被触发多少次    console.log("Found instance: "+instance);    console.log("Result of secret func: " + instance.secret());  },  onComplete:function(){}});

最终运行成果如下:

$ python loader.pyScript loaded successfullyInside java perform functionFound instance: com.roysue.demo02.MainActivity@92d5debResult of secret func: @@@###@@@original call: fun(50, 30)*************************************Original arg: LoWeRcAsE Me!!!!!!!!!Return value: my test string#####*************************************original call: fun(50, 30)*************************************Original arg: LoWeRcAsE Me!!!!!!!!!Return value: my test string#####*************************************original call: fun(50, 30)

这样暗藏办法也被调用起来了。

中级能力:近程调用

上一大节中咱们在安卓机器上应用js脚本调用了暗藏函数secret(),它在app内尽管没有被任何中央调用,然而依然被咱们的脚本“找到”并且“调用”了起来
这一大节咱们要实现的是,不仅要在跑在安卓机上的js脚本里调用这个函数,还要能够在kali主机上的py脚本里,间接调用这个函数。
也就是应用frida提供的RPC性能(Remote Procedure Call)。
安卓app不须要有任何批改,这次咱们要批改的是js脚本和py脚本。

$ nano s3.js
console.log("Script loaded successfully ");function callSecretFun() { //定义导出函数    Java.perform(function () { //找到暗藏函数并且调用        Java.choose("com.roysue.demo02.MainActivity", {            onMatch: function (instance) {                console.log("Found instance: " + instance);                console.log("Result of secret func: " + instance.secret());            },            onComplete: function () { }        });    });}rpc.exports = {    callsecretfunction: callSecretFun //把callSecretFun函数导出为callsecretfunction符号,导出名不能够有大写字母或者下划线};

而后咱们能够在kali主机的py脚本里间接调用该函数:

$ nano loader3.py
import timeimport fridadef my_message_handler(message, payload):    print message    print payloaddevice = frida.get_usb_device()pid = device.spawn(["com.roysue.demo02"])device.resume(pid)time.sleep(1)session = device.attach(pid)with open("s3.js") as f:    script = session.create_script(f.read())script.on("message", my_message_handler)script.load()command = ""while 1 == 1:    command = raw_input("Enter command:\n1: Exit\n2: Call secret function\nchoice:")    if command == "1":        break    elif command == "2": #在这里调用        script.exports.callsecretfunction()

而后在kali主机上咱们就能够看到以下的输入:

$ python loader3.pyScript loaded successfullyEnter command:1: Exit2: Call secret functionchoice:2Found instance: com.roysue.demo02.MainActivity@2eacd80Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!Enter command:1: Exit2: Call secret functionchoice:2Found instance: com.roysue.demo02.MainActivity@2eacd80Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!Enter command:1: Exit2: Call secret functionchoice:2Found instance: com.roysue.demo02.MainActivity@2eacd80Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!Enter command:1: Exit2: Call secret functionchoice:1

这样咱们就实现了在kali主机上间接调用安卓app外部的函数的能力。

高级能力:互联互通、动静批改

最初咱们要实现的性能是,咱们不仅仅能够在kali主机上调用安卓app里的函数。咱们还能够把数据从安卓app里传递到kali主机上,在主机上进行批改,再传递回安卓app外面去。
咱们编写这样一个app,其中最外围的中央在于判断用户是否为admin,如果是,则间接返回谬误,禁止登陆。如果不是,则把用户和明码上传到服务器上进行验证。

package com.roysue.demo04;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Base64;import android.view.View;import android.widget.EditText;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    EditText username_et;    EditText password_et;    TextView message_tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        password_et = (EditText) this.findViewById(R.id.editText2);        username_et = (EditText) this.findViewById(R.id.editText);        message_tv = ((TextView) findViewById(R.id.textView));        this.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (username_et.getText().toString().compareTo("admin") == 0) {                    message_tv.setText("You cannot login as admin");                    return;                }                //hook target                message_tv.setText("Sending to the server :" + Base64.encodeToString((username_et.getText().toString() + ":" + password_et.getText().toString()).getBytes(), Base64.DEFAULT));            }        });    }}

最终跑起来之后,成果就是这样。

咱们的指标就是在kali主机上“失去”输入框输出的内容,并且批改其输出的内容,并且“传输”给安卓机器,使其通过验证。也就是说,咱们哪怕输出admin的账户和明码,也能够绕过本地校验,进行登陆的操作。
所以最终安卓端的js代码的逻辑就是,截取输出,传输给kali主机,暂停执行,失去kali主机传回的数据之后,继续执行。造成代码如下:

Java.perform(function () {    var tv_class = Java.use("android.widget.TextView");    tv_class.setText.overload("java.lang.CharSequence").implementation = function (x) {        var string_to_send = x.toString();        var string_to_recv;        send(string_to_send); // 将数据发送给kali主机的python代码        recv(function (received_json_object) {            string_to_recv = received_json_object.my_data            console.log("string_to_recv: " + string_to_recv);        }).wait(); //收到数据之后,再执行上来        return this.setText(string_to_recv);    }});

kali主机端的流程就是,将承受到的JSON数据解析,提取出其中的明码局部,而后将用户名替换成admin,这样就实现了将adminpw发送给“服务器”的后果。

import timeimport fridadef my_message_handler(message, payload):    print message    print payload    if message["type"] == "send":        print message["payload"]        data = message["payload"].split(":")[1].strip()        print 'message:', message        data = data.decode("base64")        user, pw = data.split(":")        data = ("admin" + ":" + pw).encode("base64")        print "encoded data:", data        script.post({"my_data": data})  # 将JSON对象发送回去        print "Modified data sent"device = frida.get_usb_device()pid = device.spawn(["com.roysue.demo04"])device.resume(pid)time.sleep(1)session = device.attach(pid)with open("s4.js") as f:    script = session.create_script(f.read())script.on("message", my_message_handler)  # 注册音讯处理函数script.load()raw_input()

咱们只有输出任意用户名(非admin)+明码,非admin的用户名能够绕过compareTo校验,而后frida会帮忙咱们将用户名改成admin,最终就是admin:pw的组合发送到服务器。

$ python loader4.pyScript loaded successfully{u'type': u'send', u'payload': u'Sending to the server :YWFhYTpiYmJi\n'}NoneSending to the server :YWFhYTpiYmJimessage: {u'type': u'send', u'payload': u'Sending to the server :YWFhYTpiYmJi\n'}data: aaaa:bbbbpw: bbbbencoded data: YWRtaW46YmJiYg==Modified data sentstring_to_recv: YWRtaW46YmJiYg==

动静批改输出内容就这样实现了。

短视频、直播数据实时采集接口,请查看文档: TiToData


免责申明:本文档仅供学习与参考,请勿用于非法用处!否则所有后果自负。