关于大数据:抖音数据采集教程一篇文章带你领悟Frida的精髓

39次阅读

共计 15525 个字符,预计需要花费 39 分钟才能阅读完成。

抖音数据采集教程,一篇文章带你领悟 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 -U
Waiting for USB device to appear...
 PID  Name
----  -----------------------------------------------
 431  ATFWD-daemon
3148  adbd
 391  adspd
2448  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 Sum
11-26 21:26:23.234  3245  3245 D Sum     : 80
11-26 21:26:24.234  3245  3245 D Sum     : 80
11-26 21:26:25.235  3245  3245 D Sum     : 80
11-26 21:26:26.235  3245  3245 D Sum     : 80
11-26 21:26:27.236  3245  3245 D Sum     : 80
11-26 21:26:28.237  3245  3245 D Sum     : 80
11-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 time
import frida
# 连贯安卓机上的 frida-server
device = frida.get_usb_device()
# 启动 `demo02` 这个 app
pid = 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     : 80
11-26 21:44:48.375  2420  2420 D Sum     : 80
11-26 21:44:48.875  2420  2420 D Sum     : 80
11-26 21:44:49.375  2420  2420 D Sum     : 80
11-26 21:44:49.878  2420  2420 D Sum     : 7
11-26 21:44:50.390  2420  2420 D Sum     : 7
11-26 21:44:50.904  2420  2420 D Sum     : 7
11-26 21:44:51.408  2420  2420 D Sum     : 7
11-26 21:44:51.921  2420  2420 D Sum     : 7
11-26 21:44:52.435  2420  2420 D Sum     : 7
11-26 21:44:52.945  2420  2420 D Sum     : 7
11-26 21:44:53.459  2420  2420 D Sum     : 7
11-26 21:44:53.970  2420  2420 D Sum     : 7
11-26 21:44:54.480  2420  2420 D Sum     : 7

kali 主机上能够察看到:

$ python loader.py
Script loaded successfully
Inside java perform function
Java.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 ROYSUE
11-26 22:22:35.689  3051  3051 D ROYSUE.Sum: 80
11-26 22:22:35.689  3051  3051 D ROYSUE.string: lowercase me!!!!!!!!!
11-26 22:22:36.695  3051  3051 D ROYSUE.Sum: 80
11-26 22:22:36.696  3051  3051 D ROYSUE.string: lowercase me!!!!!!!!!
11-26 22:22:37.696  3051  3051 D ROYSUE.Sum: 80
11-26 22:22:37.696  3051  3051 D ROYSUE.string: lowercase me!!!!!!!!!
11-26 22:22:38.697  3051  3051 D ROYSUE.Sum: 80
11-26 22:22:38.697  3051  3051 D ROYSUE.string: lowercase me!!!!!!!!!
11-26 22:22:39.697  3051  3051 D ROYSUE.Sum: 80
11-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.py
Script loaded successfully
Inside java perform function
Java.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.py
Script loaded successfully
Inside java perform function
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)
*************************************
Original arg: LoWeRcAsE Me!!!!!!!!!
Return value: my test string#####
*************************************

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

$ adb logcat |grep ROYSUE
11-26 22:23:29.597  3244  3244 D ROYSUE.Sum: 7
11-26 22:23:29.673  3244  3244 D ROYSUE.string: my test string#####
11-26 22:23:30.689  3244  3244 D ROYSUE.Sum: 7
11-26 22:23:30.730  3244  3244 D ROYSUE.string: my test string#####
11-26 22:23:31.740  3244  3244 D ROYSUE.Sum: 7
11-26 22:23:31.789  3244  3244 D ROYSUE.string: my test string#####
11-26 22:23:32.797  3244  3244 D ROYSUE.Sum: 7
11-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.py
Script loaded successfully
Inside java perform function
Found instance: com.roysue.demo02.MainActivity@92d5deb
Result 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 time
import frida
def my_message_handler(message, payload):
    print message
    print payload
device = 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.py
Script loaded successfully
Enter command:
1: Exit
2: Call secret function
choice:2
Found instance: com.roysue.demo02.MainActivity@2eacd80
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!
Enter command:
1: Exit
2: Call secret function
choice:2
Found instance: com.roysue.demo02.MainActivity@2eacd80
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!
Enter command:
1: Exit
2: Call secret function
choice:2
Found instance: com.roysue.demo02.MainActivity@2eacd80
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!
Enter command:
1: Exit
2: Call secret function
choice: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 time
import frida
def 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.py
Script loaded successfully
{u'type': u'send', u'payload': u'Sending to the server :YWFhYTpiYmJi\n'}
None
Sending to the server :YWFhYTpiYmJi
message: {u'type': u'send', u'payload': u'Sending to the server :YWFhYTpiYmJi\n'}
data: aaaa:bbbb
pw: bbbb
encoded data: YWRtaW46YmJiYg==
Modified data sent
string_to_recv: YWRtaW46YmJiYg==

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

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

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

正文完
 0