安卓逆向——Frida的基础用法(上)
1484321987948696 发表于 浙江 移动安全 3234浏览 · 2024-08-13 03:03

一..js脚本hook一个方法
1.工具:1.jadx----用于进行静态分析,也可以进行动态调试(JEB似乎更好)。
雷神模拟器 9(Android 9)----原本自带的adb.exe好像有问题,建议另行自找。(需要root)
Frida Frida server

例题:Frida 0x1

2.拿到附件,一个.apk文件,我们可以先用MT管理器看看APP入口

找到了之后,就可以用jadx打开,先静态分析看看逻辑

package com.ad2001.frida0x1;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Random;

/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
    TextView t1;

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        final EditText editText = (EditText) findViewById(R.id.editTextTextPassword);
        this.t1 = (TextView) findViewById(R.id.textview1);
        final int i = get_random();
        ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x1.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                String obj = editText.getText().toString();
                if (TextUtils.isDigitsOnly(obj)) {
                    MainActivity.this.check(i, Integer.parseInt(obj));
                } else {
                    Toast.makeText(MainActivity.this.getApplicationContext(), "Enter a valid number !!", 1).show();
                }
            }
        });
    }

    int get_random() {
        return new Random().nextInt(100);
    }

    void check(int i, int i2) {
        if ((i * 2) + 4 == i2) {
            Toast.makeText(getApplicationContext(), "Yey you guessed it right", 1).show();
            StringBuilder sb = new StringBuilder();
            for (int i3 = 0; i3 < 20; i3++) {
                char charAt = "AMDYV{WVWT_CJJF_0s1}".charAt(i3);
                if (charAt < 'a' || charAt > 'z') {
                    if (charAt >= 'A') {
                        if (charAt <= 'Z') {
                            charAt = (char) (charAt - 21);
                            if (charAt >= 'A') {
                            }
                            charAt = (char) (charAt + 26);
                        }
                    }
                    sb.append(charAt);
                } else {
                    charAt = (char) (charAt - 21);
                    if (charAt >= 'a') {
                        sb.append(charAt);
                    }
                    charAt = (char) (charAt + 26);
                    sb.append(charAt);
                }
            }
            this.t1.setText(sb.toString());
            return;
        }
        Toast.makeText(getApplicationContext(), "Try again", 1).show();
    }
}

可以看到,APP中位于com.ad2001.frida0x1MainActivity类的onCreate方法在程序初始化时会调用get_random()方法生成一个介于0到100之间的随机数并赋值给变量i。同时,监听button的点击,当按下按钮后,程序首先判断输入的内容是不是数字,如果是数字就将其从string转化为int,并将该整数与生成的随机数一起传递给check方法。接下来,继续分析check**方法实现了哪些功能。

check方法被传入了两个参数,一个是我们在APP页面输入后提交的的数字i2,一个是程序启动时调用get_random方法生成的随机数i。该方法会检查表达式(i * 2) + 4 == i2)是否成立,如果成立则将正确的flag输出到绑定的textView控件上,反之则会提示“Try again”。

​ 当然,这个题其实是可以直接逆的,写脚本爆破,但是为了学习frida的用法,我们使用frida来hook出每次程序运行时所生成的随机数i是多少,然后我们再将正确答案输入,便可得到正确flag。

​ PS:因为使用frida进行hook,也是属于动态调.apk文件,所以我们得在aok文件的AndroidMainfestxml清单里查看application中是否有可调式的属性,如果没有得自行加入。

然后就可以开始使用frida进行hook了。

3.具体操作
首先先查看adb的连接是否有问题,在终端输入adb devices

然后输入adb shell进入手机,接着输入su进入超级管理,再输入cd /data/local/tmp进入相应目录(网上大部分都是将frida server存入此文件夹。(:frida和frida server版本需一致,同时frida server的类型需与虚拟机(手机)的架构一直,可用指令adb shell getprop ro.product.cpu.abi查看具体信息)。进入目录之后,可以看看文件夹里是否有frida server这个文件,

然后修改文件权限chmod 755 frida-server-16.2\ .1-android-x86_64,紧接着就可以运行了。然后,我另外再打开一个终端窗口,分别输入

adb forward tcp:27042 tcp:27042**adb forward tcp:27043 tcp:27043** 进行端口转发监听

最后输入frida-ps -U或者frida-ps -R看看能否成功输出进程列表,检查frida能否正常使用,

我们还需编写脚本

// 使用Java.perform函数创建一个特殊的Java上下文,用于执行挂钩和Java类操作
Java.perform(function() {
    // 获取应用程序中com.ad2001.frida0x1.MainActivity类的引用
    var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");

    // 重写MainActivity类的get_random方法的实现
    MainActivity.get_random.implementation = function() {
        // 当get_random方法被调用时,输出一条日志信息
        console.log("MainActivity.get_random is called");

        // 调用原始的get_random方法并获取其返回值
        var ret_val = this.get_random();

        // 输出get_random方法的返回结果
        console.log("MainActivity.get_random result is " + ret_val);

        // 返回原始方法的返回值
        return ret_val;
    };
});

保存好,然后我们使用frida -U -f com.ad2001.frida0x1 -l test.js(test.js是脚本的绝对路径)

再在jadx中点击运行,就可以看到输出了此次app运行时生成的随机数是多少,再计算 (74 * 2) + 4 = 152,输入,就可得到flag


这就是利用frida hook一个方法的完整过程。
总结:Frida Hook方法的脚本模板:

Java.perform(function() {

var <class_reference> = Java.use(<package_name>.<class>);

<class_reference>.<method_to_hook>.implementation = function(<args>) {

     /*

       我们自己的方法实现

     */

   }
}

二..js脚本hook一个静态方法
1.工具:还是那几样东西。
2.例题:Frida 0x2
3.分析:
拿到附件apk文件,先安装到雷电9里运行一下,
没有输入,就是很直白,hook。那么接下来我们用jadx反编译为java看看静态逻辑。

package com.ad2001.frida0x2;

import android.os.Bundle;
import android.util.Base64;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
    static TextView t1;

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        t1 = (TextView) findViewById(R.id.textview);
    }

    public static void get_flag(int a) {
        if (a == 4919) {
            try {
                SecretKeySpec secretKeySpec = new SecretKeySpec("HILLBILLWILLBINN".getBytes(), "AES");
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                IvParameterSpec iv = new IvParameterSpec(new byte[16]);
                cipher.init(2, secretKeySpec, iv);
                byte[] decryptedBytes = cipher.doFinal(Base64.decode("q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4=", 0));
                String decryptedText = new String(decryptedBytes);
                t1.setText(decryptedText);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

逻辑很简单,有一个静态方法get_flag,比较一个参数a是不是4919,是的话就会解密输出flag,一个AES加密,但是,这里没有任何方法会调用这个静态方法,那么我们就可以利用frida编写脚本调用这个静态方法,并且将参数a的值改为正确的,那么就可以输出flag。使用frida的前期工作和例题一一样,但是因为这是静态方法,如果使用指令frida -U -f 包名 -l 脚本会出现hook不上的情况,所以我们可以先运行程序,然后使用指令frida -U 'Frida 0x2' -l .\Hook.js,就可以输出flag了。

脚本:

Java.perform(function() {

    // 声明了一个变量a来表示目标Android应用程序中的Java类。
    // Java.use函数指定要使用com.ad2001.frida0x2包中的MainActivity类。
    var a = Java.use("com.ad2001.frida0x2.MainActivity");

    // 调用get_flag方法并传入参数4919
    a.get_flag(4919);

});

运行脚本:

得到flag

总结:Frida 调用静态方法的脚本模板:

Java.perform(function (){

     var <class_reference> = Java.use(<package_name>.<class>);

     a.function(val); // 需要调用的方法名称

})

三. .js脚本更改变量的值
1.拿到附件apk文件,依旧先安装到雷电模拟器上运行一下看看回显,

框框一顿点没什么用,那么就开始用jadx静态分析一波吧,看看函数逻辑,

package com.ad2001.frida0x3;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
    TextView t1;

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = (Button) findViewById(R.id.button);
        this.t1 = (TextView) findViewById(R.id.textView);
        btn.setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x3.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                if (Checker.code == 512) {
                    byte[] bArr = new byte[0];
                    Toast.makeText(MainActivity.this.getApplicationContext(), "YOU WON!!!", 1).show();
                    byte[] KeyData = "glass123".getBytes();
                    SecretKeySpec KS = new SecretKeySpec(KeyData, "Blowfish");
                    byte[] ecryptedtexttobytes = Base64.getDecoder().decode("MKxsZsY9Usw3ozXKKzTF0ymIaC8rs0AY74GnaKqkUrk=");
                    try {
                        Cipher cipher = Cipher.getInstance("Blowfish");
                        cipher.init(2, KS);
                        byte[] decrypted = cipher.doFinal(ecryptedtexttobytes);
                        String decryptedString = new String(decrypted, Charset.forName("UTF-8"));
                        MainActivity.this.t1.setText(decryptedString);
                        return;
                    } catch (InvalidKeyException e) {
                        throw new RuntimeException(e);
                    } catch (NoSuchAlgorithmException e2) {
                        throw new RuntimeException(e2);
                    } catch (BadPaddingException e3) {
                        throw new RuntimeException(e3);
                    } catch (IllegalBlockSizeException e4) {
                        throw new RuntimeException(e4);
                    } catch (NoSuchPaddingException e5) {
                        throw new RuntimeException(e5);
                    }
                }
                Toast.makeText(MainActivity.this.getApplicationContext(), "TRY AGAIN", 1).show();
            }
        });
    }
}

逻辑还是很清晰的,就是com.ad2001.frida0x3包的Checker类中检查参数code的值是否是512,是的话就会输出flag,再来看看Checker类的逻辑,

有个increase方法,主函数中有个检测按钮的点击次数,点一次就加2,那么这个题要解出来的话,要么点256次,要么直接解密,但似乎加密的逻辑不是很清晰,再一个方法就是用frida修改code的值,直接改为512,这样一来就可以直接输出flag了。

脚本

/*** 
 * Java.perform 是Frida中的一个函数,用于为脚本创建特殊上下文,
 * 进入此上下文后,可以执行挂钩方法或访问Java类等操作来控制或观察应用程序的行为。
 **/ 

Java.perform(function () {

    // 声明了一个变量a来表示目标Android应用程序中的Java类。
    // Java.use函数指定要使用com.ad2001.frida0x3包中的Checker类。
    var a = Java.use("com.ad2001.frida0x3.Checker");

    // 使用变量a将所选类的code变量值修改为512
    a.code.value = 512; 

});

使用frida还是和前面的步骤一样,然后我们输入指令frida -U -f com.ad2001.frida0x3 -l hook.js,然后再使用jadx进行动态调试,为的是控制程序运行,点击两次运行,会出现最开始的页面,最后点击Click me,就会出现flag

得到flag

总结:Frida 更改变量值的脚本模板

Java.perform(function (){

     var <class_reference> = Java.use(<package_name>.<class>);

     <class_reference>.<variable>.value = <value>; //需要修改的变量

})

四. .js脚本创建类实例
工具不变,拿到附件apk文件,依旧先安装到雷神模拟器上安装运行一下。没有任何输入框或者按钮,只有一个打招呼的。那么就丢到jadx里分析看看具体函数逻辑

这是MainActivity类,没啥有用的信息。再看看Check

判断参数a的值是否是1337,是的话就会把解码的flag输出,但是,这个类以及类中的方法并没有在程序的任何地方被使用,因此我们仅仅打开程序无法获取到flag的值,同时,这里的get_flag方法并不是静态方法。所以我们就可以利用frida创建一个实例,再用这个实例来调用这个方法,进而输出flag。

那么我们就可以开始操作了,前期开始工作是一样的,然后我们直接使用脚本,

Java.perform(function() {

    var check = Java.use("com.ad2001.frida0x4.Check");
    // 声明了一个变量 check 来表示目标 Android 应用程序中的 Java 类。
    // Java.use 函数指定要使用 com.ad2001.frida0x4 包的 Check 类。

    var check_obj = check.$new(); 
    // $new() 是 Frida 中的方法,用于实例化特定类的对象;
    // 使用 $new() 方法创建 Check 类的实例 check_obj。

    var res = check_obj.get_flag(1337); 
    // 使用 check_obj 实例调用 get_flag,并传入参数 1337。
    // 创建一个变量 res 来保存 get_flag 方法的返回值。

    console.log("FLAG:" + res);
    // 将保存的 flag 输出到脚本运行日志中。

});

拿到flag

总结:Frida 创建类实例的脚本模板

Java.perform(function() {

  var <class_reference> = Java.use("<package_name>.<class>");
  var <class_instance> = <class_reference>.$new(); // 创建类的实例
  <class_instance>.<method>(); // 调用该实例中的方法

})

五.最后
Frida相关例题链接:(https://github.com/DERE-ad2001/Frida-Labs)

0 条评论
某人
表情
可输入 255
目录