初识Frida--Android逆向之Java层hook (一)

 

 

目录

 


 

博客同步:访问

0x00 文中用到的工具

  • Frida
  • jadx-gui 一个强大的android反编译工具
  • genymotion模拟器
  • Python2.7以及frida-python库
  • radare2 反汇编器
  • pycharm

0x01 hook示例的安装与分析

Frida官网给我们了一个ctf的示例,就以此为例子,开始学习frida在android逆向的使用。
rps.apk 下载地址

安装

使用genymotion等类似android模拟器安装好打开,发现这是一个石头剪刀布的游戏应用,简单的玩了一下,没什么特别的,直接分析代码吧,看看到底想干什么。

源代码分析

使用jadx-gui反编译,发现app没有加壳和混淆,当然一来就加壳和混淆的话对我们就太不友好了,接下分析就简单了,直接看java代码。当然也可以使用androidkiller,jeb等其他强大的反编译工具。

 

在MainActivity中找到OnCreate()方法,可以看到只是简单的声明了button控件以及对应的监听器。

1

2

3

4

5

6

7

8

9

10

11

protected void onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      setContentView(R.layout.activity_main);

      this.P = (Button) findViewById(R.id.button);

      this.S = (Button) findViewById(R.id.button3);

      this.r = (Button) findViewById(R.id.buttonR);

      this.P.setOnClickListener(this);

      this.r.setOnClickListener(this);

      this.S.setOnClickListener(this);

      this.flag = 0;

  }

继续查看button的onclick方法,可以看出cpu是通过随机数组出的,其判断输赢的方法在this.showMessageTask中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

public void onClick(View v) {

      if (this.flag != 1) {

          this.flag = 1;

          ((TextView) findViewById(R.id.textView3)).setText("");

          TextView tv = (TextView) findViewById(R.id.textView);

          TextView tv2 = (TextView) findViewById(R.id.textView2);

          this.m = 0;

          this.n = new Random().nextInt(3);  //随机数0,1,2

          tv2.setText(new String[]{"CPU: Paper""CPU: Rock""CPU: Scissors"}[this.n]); //随机出石头,剪刀,布

          if (v == this.P) {

              tv.setText("YOU: Paper");

              this.m = 0;

          }

          if (v == this.r) {

              tv.setText("YOU: Rock");

              this.m = 1;

          }

          if (v == this.S) {

              tv.setText("YOU: Scissors");

              this.m = 2;

          }

          this.handler.postDelayed(this.showMessageTask, 1000);//输赢判断方法

      }

  }

跟进分析showMessageTask,可以看到如果赢了mainActivity.cnt会+1,但是一旦输了cnt就会置0,而获取flag的要求是我们得获胜1000次,...... :(

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

private final Runnable showMessageTask = new Runnable() {

        public void run() {

            TextView tv3 = (TextView) MainActivity.this.findViewById(R.id.textView3);

            MainActivity mainActivity;

            //我方:布 CPU:石头 or 我方:石头 CUP:剪刀 ,则为赢

            if (MainActivity.this.n - MainActivity.this.m == 1) {

                mainActivity = MainActivity.this;

                mainActivity.cnt++;

                tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));

             //反过来当然是输咯

            else if (MainActivity.this.m - MainActivity.this.n == 1) {

                MainActivity.this.cnt = 0;

                tv3.setText("LOSE +0");

             //一样则打平

            else if (MainActivity.this.m == MainActivity.this.n) {

                tv3.setText("DRAW +" + String.valueOf(MainActivity.this.cnt));

             //我布  cup:剪刀

            else if (MainActivity.this.m < MainActivity.this.n) {

                MainActivity.this.cnt = 0;

                tv3.setText("LOSE +0");

            else {

                mainActivity = MainActivity.this;

                mainActivity.cnt++;

                tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));

            }

            //获胜1000次则能够获取flag

            if (1000 == MainActivity.this.cnt) {

                tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107+ "}");

            }

            MainActivity.this.flag = 0;

        }

    };

简单分析一下获取flag需要的条件,总结有3个办法:

  • 分析calc()方法能算出答案,但这个方法在so中,得分析汇编代码才行,当然可以尝试使用ida pro,F5查看C代码分析,前提是算法不难。

  • 获取calc函数的返回值,从而计算答案。

  • 还有一个方法就是,直接将MainActivity.this.cnt的值构造成1000。

接下来就用frida,使用后两种思路来解这个简单的示例。但在这之前得先了解Frida自带的Messages机制,了解frida怎么从通过一个python脚本发送和接收message消息是一个提升理解frida的好方法。

0x02 frida自带的Messages机制与进程交互

先来看看一个Messages的模板,这里用到的语言分别是python和javascript,他们之间的关系是python作为载体,javascript作为在android中真正执行代码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

import frida, sys

 

//hook代码,采用javascript编写

jscode = """

//javascript代码,重点

"""

 

//自定义回调函数

def on_message(message, data):

    if message['type'== 'send':

        print("[*] {0}".format(message['payload']))

    else:

        print(message)

 

#重点的4行代码

process = frida.get_usb_device().attach('应用完整包名')

script = process.create_script(jscode)

script.on('message', on_message)

script.load()

sys.stdin.read()

当然如果是对此简单的使用,只需要编写jscode,以及填写你要hook的应用完整包名就行了,不过如果单纯只会用可能在以后会被模板限制,所以一探究竟还是很有必要。
可以在cmd中,使用python终端的help()函数找到frida库的源代码的绝对路径。

接下来就来具体看看这几句代码做了什么事情。

1

2

3

4

5

process = frida.get_usb_device().attach('应用完整包名')

script = process.create_script(jscode)

script.on('message', on_message)

script.load()

sys.stdin.read()

首先使用了frida.get_usb_device(),返回了一个_get_device函数,跟进_get_device方法。

1

2

def get_usb_device(timeout = 0):

    return _get_device(lambda device: device.type == 'tether', timeout)

在_get_device中,通过get_device_manager()实例化DeviceManager类,并调用该类中的enumerate_devices()方法。

1

2

3

4

5

6

7

8

9

10

def _get_device(predicate, timeout):

    mgr = get_device_manager()                //获取设备管理

    def find_matching_device():               //寻找匹配设备

        usb_devices = [device for device in mgr.enumerate_devices() if predicate(device)]

        if len(usb_devices) > 0:

            return usb_devices[0]

        else:

            return None

    device = find_matching_device()

   ...省略

get_device_manager()代码

1

2

3

4

5

6

def get_device_manager():

    global _device_manager

    if _device_manager is None:

        from import core

        _device_manager = core.DeviceManager(_frida.DeviceManager())

    return _device_manager

DeviceManager中enumerate_devices()方法,可以看到enumerate_devices()方法实际上是返回了一个Device()类的实例化对象List。

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

class DeviceManager(object):

    def __init__(self, impl):

        self._impl = impl

 

    def __repr__(self):

        return repr(self._impl)

 

    //返回了一个Device()类的实例化。

    def enumerate_devices(self):

        return [Device(device) for device in self._impl.enumerate_devices()]

 

    def add_remote_device(self, host):

        return Device(self._impl.add_remote_device(host))

 

    def remove_remote_device(self, host):

        self._impl.remove_remote_device(host)

 

    def get_device(self, device_id):

        devices = self._impl.enumerate_devices()

        if device_id is None:

            return Device(devices[0])

        for device in devices:

            if device.id == device_id:

                return Device(device)

        raise _frida.InvalidArgumentError("unable to find device with id %s" % device_id)

 

    def on(self, signal, callback):

        self._impl.on(signal, callback)

 

    def off(self, signal, callback):

        self._impl.off(signal, callback)

继续跟进Device类中的,就找到了attach()方法。在attach方法这是设置断点,看看传入的数据。

 

 

接下来提供的“应用完整名”是通过self._pid_of()函数去找到对应的进程号pid,然后将pid后通过Session类初始化。到此第一句代码过程就算是明白了,最终得到的是一个对应进程号pid的Session实例化对象process。

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

class Device(object):

    def __init__(self, device):

        self.id = device.id

        self.name = device.name

        self.icon = device.icon

        self.type = device.type

        self._impl = device

 

    def __repr__(self):

        return repr(self._impl)

 

    ...节省空间删除部分方法,详细内容可自行查看源码

 

    def kill(self, target):

        self._impl.kill(self._pid_of(target))

 

    //返回了一个Session的实例化对象

    def attach(self, target):

        return Session(self._impl.attach(self._pid_of(target)))

 

    def inject_library_file(self, target, path, entrypoint, data):

        return self._impl.inject_library_file(self._pid_of(target), path, entrypoint, data)

 

    def inject_library_blob(self, target, blob, entrypoint, data):

        return self._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data)

 

    def on(self, signal, callback):

        self._impl.on(signal, callback)

 

    def off(self, signal, callback):

        self._impl.off(signal, callback)

 

    def _pid_of(self, target):

        if isinstance(target, numbers.Number):

            return target

        else:

            return self.get_process(target).pid

第二句,紧接着process.create_script(jscode),可以看到它返回一个Script类的实例化,参数不确定。

1

2

def create_script(self*args, **kwargs):

        return Script(self._impl.create_script(*args, **kwargs))

跟进Script类,可以找到on()方法,在on方法中可以设置自定义回调函数。

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

class Script(object):

    def __init__(self, impl):

        self.exports = ScriptExports(self)

 

        self._impl = impl

        self._on_message_callbacks = []

        self._log_handler = self._on_log

 

        self._pending = {}

        self._next_request_id = 1

        self._cond = threading.Condition()

 

        impl.on('destroyed'self._on_destroyed)

        impl.on('message'self._on_message)

 

   ...节省空间删除部分类方法,详细内容可自行查看源码

 

    def load(self):

        self._impl.load()

 

   //设置自定义回调函数

    def on(self, signal, callback):

        if signal == 'message':

            self._on_message_callbacks.append(callback)

        else:

            self._impl.on(signal, callback)

 

在IDE中可以看到_on_message_callbacks中存放的on_message函数地址。


接下来调用load()方法,在服务端就启动javascript脚本了,至于在frida-server服务端怎么执行的,可逆向研究一下frida-server,它才是真正的核心。

0x03 Javascript代码构造与执行

现在就来使用frida实现刚刚试想的方法。

方法一:获取calc()返回值

第一种思路就是直接获取calc的返回值,从native函数定义上知道它的返回值是int类型,当然直接获取calc函数的返回值是解出问题最简单的方法。

1

public native int calc();

那怎么获取calc()函数的返回值呢,这个函数在MainActivity类中,直接引用该类下的calc()方法,不就ok了吗,原理是这样,下面就来构造一下Javascript代码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//Java.Perform 开始执行JavaScript脚本。

Java.perform(function () {

//定义变量MainActivity,Java.use指定要使用的类

    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

    //hook该类下的onCreate方法,重新实现它

    MainActivity.onCreate.implementation = function () {

        send("Hook Start...");

        //调用calc()方法,获取返回值

        var returnValue = this.calc();

        send("Return:"+returnValue);

        var result = (1000+returnValue)*107;

        //解出答案

        send("Flag:"+"SECCON{"+result.toString()+"}");

    }

});

JavaScript代码就是这样,如果不是很理解,学习一下JavaScript基础即可,下面看看完整的python脚本。

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

import frida, sys

 

def on_message(message, data):

    if message['type'== 'send':

        print("[*] {0}".format(message['payload']))

    else:

        print(message)

 

jscode = """

Java.perform(function () {

    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

    MainActivity.onCreate.implementation = function () {

        send("Hook Start...");

        var returnValue = this.calc();

        send("Return:"+returnValue);

        var result = (1000+returnValue)*107;

        send("Flag:"+"SECCON{"+result.toString()+"}");

    }

});

"""

 

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')

script = process.create_script(jscode)

script.on('message', on_message)

script.load()

sys.stdin.read()

接下来运行一下,看看能否成功。

 

步骤如下:

  1. 启动模拟器,使用adb push将对应架构的frida-server文件push到模拟器中
    /data/local/tmp目录下。
  2. adb shell 进入/data/local/tmp目录,启动frida-server。
  3. 开启端口转发
    adb forward tcp:27043 tcp:27043
    adb forward tcp:27042 tcp:27042
  4. 启动应用后,在命令行等执行python脚本。

因为hook的是应用的onCreate方法,执行python脚本的前提是应用首先启动,这样才能attach到该应用,所以还得返回模拟器桌面重新启动应用,这样它才会执行hook的onCreate()方法,结果如下。

方法二:修改cnt的值为1000

第二种思路也比较简单,我们需要修改cnt的值,但如果直接修改cnt的初始值为1000的话,在游戏中可能存在不确定因素,比如输了会置0,赢了cnt值就变成1001了,所以还得控制一下输赢,而输赢的条件是电脑出什么,所以最终hook的方法就在onClick中。
从onClick()中可以知道,控制输赢的在于修改this.n 和 this.m的值,再来看看源代码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

public void onClick(View v) {

       if (this.flag != 1) {

           this.flag = 1;

           ((TextView) findViewById(R.id.textView3)).setText("");

           TextView tv = (TextView) findViewById(R.id.textView);

           TextView tv2 = (TextView) findViewById(R.id.textView2);

           this.m = 0;

           //控制电脑出拳

           this.n = new Random().nextInt(3);

           tv2.setText(new String[]{"CPU: Paper""CPU: Rock""CPU: Scissors"}[this.n]);

           if (v == this.P) {

               tv.setText("YOU: Paper");

               this.m = 0;

           }

           if (v == this.r) {

               tv.setText("YOU: Rock");

               this.m = 1;

           }

           if (v == this.S) {

               tv.setText("YOU: Scissors");

               this.m = 2;

           }

           this.handler.postDelayed(this.showMessageTask, 1000);

       }

来看JavaScript代码怎么写吧

1

2

3

4

5

6

7

8

9

10

11

12

13

14

Java.perform(function () {

    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

    //hook onClick方法,此处要注意的是onClick方法是传递了一个View参数v

    MainActivity.onClick.implementation = function (v) {

        send("Hook Start...");

        //调用onClick,模拟点击事件

        this.onClick(v);

        //修改参数

        this.n.value = 0;

        this.m.value = 2;

        this.cnt.value = 999;

        send("Success!")

    }

});

完整python代码

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

import frida, sys

 

def on_message(message, data):

    if message['type'== 'send':

        print("[*] {0}".format(message['payload']))

    else:

        print(message)

 

jscode = """

Java.perform(function () {

    var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

    MainActivity.onClick.implementation = function (v) {

        send("Hook Start...");

        this.onClick(v);

        this.n.value = 0;

        this.m.value = 2;

        this.cnt.value = 999;

        send("Success!")

    }

});

"""

 

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')

script = process.create_script(jscode)

script.on('message', on_message)

script.load()

sys.stdin.read()

执行python脚本,任意点击按钮,答案就出来了。

 

当然,如果so中的calc()函数算法不难的前提,直接使用ida pro或者radare2分析汇编代码也是可以的。这里给出用radare2反汇编出来的代码。可以看到,calc()函数就单纯的返回了int值7。

0x04 总结

  • 一般分析流程
    1.反编译apk,分析代码寻找hook点。
    2.编写js代码,调用类的方法或者替换。
    3.在python中执行即可。
    下面一篇会更详细介绍frida的使用。

https://bbs.pediy.com/thread-227232.htm

### 回答1: Frida-server-15.2.2-android-x86是一款适用于安卓x86架构的Frida服务器的版本。Frida是一种开源的动态插桩框架,可用于安卓设备上的应用程序逆向工程和安全性评估。 Frida-server是Frida框架中的一部分,用于在安卓设备上与Frida客户端通信。Frida-server是在设备上运行的服务,可以与Frida客户端(通常是运行在PC上的Frida工具)进行通信,并接收来自客户端的指令。 Frida-server-15.2.2-android-x86适用于安卓x86架构的设备。x86是一种PC上常见的处理器架构,而不是常用于移动设备的ARM架构。因此,如果你的安卓设备是基于x86架构的,那么你可以使用这个版本的Frida-server在设备上运行Frida服务。 通过在设备上运行Frida-server,你可以利用Frida的强大功能来分析、修改和控制应用程序。Frida允许你动态地插入JavaScript代码到应用程序中,以实时地跟踪和修改应用程序的行为。你可以使用Frida进行代码注入、函数钩子、网络流量捕获、数据修改等操作,以便进行应用程序逆向工程、漏洞挖掘、安全性评估等任务。 总而言之,Frida-server-15.2.2-android-x86是一种适用于安卓x86架构设备的Frida服务器版本,通过在设备上运行Frida-server,你可以利用Frida框架的功能对应用程序进行逆向工程和安全性评估。 ### 回答2: frida-server-15.2.2-android-x86是一款用于Android x86架构的Frida服务器。Frida是一种强大的开源工具,用于分析、修改和调试软件。它可以通过脚本语言来进行动态注入和操作,支持多种平台和架构。 通过使用frida-server-15.2.2-android-x86,我们可以在Android x86设备上安装和运行Frida服务器。安装frida-server时我们需要将其推送到设备上,并在设备上运行它。运行成功后,我们可以通过Frida客户端连接到该设备上的Frida服务器,并使用Frida的功能进行应用程序的分析、修改和调试。 Frida-server-15.2.2-android-x86版本适用于Android x86架构的设备。在使用时,我们首先需要确保设备已经以root权限运行,并且具备adb工具的连接。然后,我们可以通过命令行将frida-server-15.2.2-android-x86安装到设备上。在设备上运行frida-server时,它会监听指定的端口,并等待Frida客户端的连接。 通过与Frida服务器建立连接,我们可以使用JavaScript或Python等脚本语言来进行动态插桩、API Hook、函数跟踪等操作。Frida提供了强大的API,使得应用程序的分析和修改变得更加简单高效。使用Frida,我们可以实时监测应用程序的行为,获取关键信息,进行漏洞分析,甚至可以修改应用程序的逻辑和数据。 总之,frida-server-15.2.2-android-x86是一款用于Android x86平台的Frida服务器,它提供了强大的功能和API,用于动态分析、修改和调试应用程序。通过与Frida客户端的连接,我们可以通过脚本语言来操作和控制应用程序,使得应用程序的分析和修改变得更加高效和可靠。 ### 回答3: Frida-Server是一款功能强大的开源工具,用于在Android设备上进行动态代码注入和调试。frida-server-15.2.2-android-x86指的是适用于Android x86架构的Frida-Server版本15.2.2。 Frida-Server能够以服务的形式运行在目标Android设备上,通过与Frida桌面端或其他脚本进行通信,来实现对目标应用程序的动态分析和操作。它提供了一套JavaScript API,允许我们在运行时通过修改和执行目标应用程序中的代码来实现功能扩展,如函数拦截、数据修改等。 在具体使用时,首先需要在目标Android设备上安装Frida-Server,这个版本适用于x86架构的设备。其次,需要将Frida-Server与Frida桌面端或其他脚本工具配合使用,以实现与目标应用程序的通信。我们可以通过Frida提供的命令行工具或编写脚本的方式来进行代码注入和调试。 Frida-Server-15.2.2-android-x86版本对于使用x86架构的Android设备来说是必需的,因为它能够确保Frida-Server能在这样的设备上正确运行。使用适合设备架构的版本,可以保证性能和稳定性,并且避免兼容性问题。 总而言之,Frida-Server-15.2.2-android-x86是一款用于在Android x86架构设备上进行动态代码注入和调试的工具,通过与Frida桌面端或其他脚本进行通信来实现相关功能。它可以帮助安全研究人员、开发人员等对Android应用程序进行潜在威胁分析、性能优化以及功能增强等工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值