欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Frida用法之函数操作

程序员文章站 2022-03-21 17:25:08
Frida接口功能介绍 Frida是个so级别的hook框架,它可以帮助开发、安全人员对指定的进程的so模块进行分析。它主要提供了功能简单的Python接口和功能丰富的JS接口,使得hook函数和修改so可以编程化,接口中包含了主控端与目标进程的交互接口。 目标进程的交互接口分为: JS接口 功能包 ......

frida接口功能介绍

frida是个so级别的hook框架,它可以帮助开发、安全人员对指定的进程的so模块进行分析。它主要提供了功能简单的python接口和功能丰富的js接口,使得hook函数和修改so可以编程化,接口中包含了主控端与目标进程的交互接口。

目标进程的交互接口分为:

  • js接口
    功能包括但不限于进程操作、模块操作、内存操作、函数操作、线程操作、网络通信、数据流操作、文件操作、数据库操作、寄存器操作。
  • python接口
    提供的功能较少,基本都是用来获取进程、模块、函数操作。

frida功能较多,暂时没有需求要每个都掌握,我现在的需求就是在程序运行的时候修改函数传参值、得到函数的返回值这种简单操作,下面通过js配合python脚本方式对这两个功能进行探讨。


注入android系统的使用流程

  1. 打开一个app应用,并跳转到有你想注入的页面
  2. 通过adb shell dumpsys activity top,获取当前 android 系统中与用户交互(顶层) activity 的详细信息
  3. 反编译apk文件,根据上一步提供的信息,进行代码查看,然后定位到想hook的函数,查看的该函数的传参和返回值
  4. 编写js注入代码,运行脚本注入到函数中

步骤1演示 - 打开app页面

自己写的一个android demo,下面有代码。
Frida用法之函数操作

步骤2演示 - 获取顶层activity信息

adb shell dumpsys activity top

task com.example.myapplication id=190
  activity com.example.myapplication/.mainactivity 6b2b7a5 pid=31745
    local activity e71a071 state:
      mresumed=false mstopped=true mfinished=false
      mchangingconfigurations=false
      mcurrentconfig={1.0 ?mcc?mnc zh_cn ldltr sw411dp w411dp h659dp 420dpi nrml port finger -keyb/v/h -nav/h s.4}
      mloadersstarted=true
      active fragments in 6e3c2de:
        #0: reportfragment{a0dccbf #0 androidx.lifecycle.lifecycledispatcher.report_fragment_tag}
          mfragmentid=#0 mcontainerid=#0 mtag=androidx.lifecycle.lifecycledispatcher.report_fragment_tag
          mstate=3 mindex=0 mwho=android:fragment:0 mbackstacknesting=0
          madded=true mremoving=false mresumed=false mfromlayout=false minlayout=false
          mhidden=false mdetached=false mmenuvisible=true mhasmenu=false
          mretaininstance=false mretaining=false muservisiblehint=true
          mfragmentmanager=fragmentmanager{6e3c2de in hostcallbacks{6fc8f8c}}
          mhost=android.app.activity$hostcallbacks@6fc8f8c
          child fragmentmanager{f8df6d5 in reportfragment{a0dccbf}}:
            fragmentmanager misc state:
              mhost=android.app.activity$hostcallbacks@6fc8f8c
              mcontainer=android.app.fragment$1@e652eea
              mparent=reportfragment{a0dccbf #0 androidx.lifecycle.lifecycledispatcher.report_fragment_tag}
              mcurstate=3 mstatesaved=true mdestroyed=false
      added fragments:
        #0: reportfragment{a0dccbf #0 androidx.lifecycle.lifecycledispatcher.report_fragment_tag}
      fragmentmanager misc state:
        mhost=android.app.activity$hostcallbacks@6fc8f8c
        mcontainer=android.app.activity$hostcallbacks@6fc8f8c
        mcurstate=3 mstatesaved=true mdestroyed=false
    viewroot:
      madded=true mremoved=false
      mconsumebatchedinputscheduled=false
      mconsumebatchedinputimmediatelyscheduled=false
      mpendinginputeventcount=0
      mprocessinputeventsscheduled=false
      mtraversalscheduled=false      misambientmode=false
      android.view.viewrootimpl$nativepreimeinputstage: mqueuelength=0
      android.view.viewrootimpl$imeinputstage: mqueuelength=0
      android.view.viewrootimpl$nativepostimeinputstage: mqueuelength=0
    choreographer:
      mframescheduled=false
      mlastframetime=64637557 (5732266 ms ago)
    view hierarchy:
      com.android.internal.policy.phonewindow$decorview{85c75db v.e...... r....... 0,0-1080,1920}
        android.widget.linearlayout{da91878 v.e...... ........ 0,0-1080,1794}
          android.view.viewstub{b442b51 g.e...... ......i. 0,0-0,0 #10203b0 android:id/action_mode_bar_stub}
          android.widget.framelayout{2df4fb6 v.e...... ........ 0,63-1080,1794}
            androidx.appcompat.widget.actionbaroverlaylayout{2294b7 v.e...... ........ 0,0-1080,1731 #7f070030 app:id/decor_content_parent}
              androidx.appcompat.widget.contentframelayout{4934424 v.e...... ........ 0,147-1080,1731 #1020002 android:id/content}
                androidx.constraintlayout.widget.constraintlayout{94f2b8d v.e...... ........ 0,0-1080,1584}
                  androidx.appcompat.widget.appcompattextview{208b142 v.ed..... ........ 191,724-890,861 #7f07008d app:id/tv}
              androidx.appcompat.widget.actionbarcontainer{ec1c553 v.ed..... ........ 0,0-1080,147 #7f070008 app:id/action_bar_container}
                androidx.appcompat.widget.toolbar{3d27e90 v.e...... ........ 0,0-1080,147 #7f070006 app:id/action_bar}
                  androidx.appcompat.widget.appcompattextview{68ff389 v.ed..... ........ 42,38-196,109}
                  androidx.appcompat.widget.actionmenuview{c749f8e v.e...... ......id 1080,0-1080,147}
                androidx.appcompat.widget.actionbarcontextview{c1963af g.e...... ......i. 0,0-0,0 #7f07000e app:id/action_context_bar}
        android.view.view{b88f3bc v.ed..... ........ 0,1794-1080,1920 #1020030 android:id/navigationbarbackground}
        android.view.view{2fb3f45 v.ed..... ........ 0,0-1080,63 #102002f android:id/statusbarbackground}
    looper (main, tid 1) {69f269a}
      (total messages: 0, polling=false, quitting=false)
    local fragmentactivity e71a071 state:
      mcreated=true mresumed=false mstopped=true    fragmentmanager misc state:
      mhost=androidx.fragment.app.fragmentactivity$hostcallbacks@4a28bcb
      mcontainer=androidx.fragment.app.fragmentactivity$hostcallbacks@4a28bcb
      mcurstate=2 mstatesaved=true mstopped=true mdestroyed=false

步骤3演示 - 查看代码

  这个是自己写的android代码,没有混淆。如果你用反编译的方式打开别人的代码,大概率是混淆过的,不过也一样用,无非是将类名、函数名、变量名变成a、b、c...,只是增加看代码的难度而已,但是调用流程还是一样的。

public class mainactivity extends appcompatactivity {

    private textview testview;
    private string returnvalule;

    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_main);
        testview = findviewbyid(r.id.tv);

        testview.settext("得分计次1:60 \n");
        returnvalule = addnumber("60");
        testview.append("addnumber()的返回值:" + returnvalule);
    }

    private string addnumber(string nubs){
        testview.append("得分计次2:" + nubs + "\n");
        return "我是默认的返回值⊙_⊙";
    }
}

步骤4演示 - js配合python脚本注入

import frida
import sys

jscode = """
/* 这个字段标记java虚拟机(例如: dalvik 或者 art)是否已加载, 操作java任何东西的之前,要确认这个值是否为true */
if(java.available){
    /* 111、222、333 打这些log的目的是看流程走到哪里 */
    console.log("111");
    
    /* java.perform(function(){ ... javascript代码成功被附加到目标进程时调用,我们核心的代码要在里面写。是个固定格式 */
    java.perform(function(){
        
        /* java.use方法用于声明一个java类,在用一个java类之前首先得声明。比如声明一个string类,要指定完整的类名var stringclass=java.use("java.lang.string"); */
        var mainactivity = java.use("com.example.myapplication.mainactivity");
        console.log("222");
        
        /* 类.函数.overload(参数类型).implementation = function(形参名称){ */
        mainactivity.addnumber.overload("java.lang.string").implementation = function(nubs){
            console.log("333");
            
            /* 给addnumber函数传参、得到addnumber函数的返回值 */
            console.log(this.addnumber("77"));
            
            /* 修改addnumber函数的返回值 */
            return "i am mysticbinary!";
        }
    });

}
"""

def on_message(message, data):
    if message['type'] == 'send':
        print(" {0}".format(message['payload']))
    else:
        print(message)

# 查找usb设备并附加到目标进程
session = frida.get_usb_device().attach('com.example.myapplication')

# 在目标进程里创建脚本
script = session.create_script(jscode)

# 注册消息回调
script.on('message', on_message)

# 加载创建好的javascript脚本
script.load()

# 读取系统输入
sys.stdin.read()

调用说明:
Frida用法之函数操作

脚本注入说明:

  1. 打开你想注入的界面
  2. 运行上面步骤4的代码
  3. android回退到系统主界面,再次进入一次,主要是为了触发函数
  4. 查看运行结果

代码运行结果:
Frida用法之函数操作

app注入结果:
Frida用法之函数操作


参考文章

https://www.cnblogs.com/mysticbinary/p/12012935.html
https://frida.re/docs/javascript-api/
https://bbs.pediy.com/thread-226846.htm
https://www.52pojie.cn/forum.php?mod=viewthread&tid=931872