ctf中的Frida——脚本详解(零基础)
1111815583505006 发表于 山东 移动安全 2379浏览 · 2024-05-21 08:08

ctf中的Frida——脚本详解

前言

看到网上有很多frida hook的解题思路,却鲜有人对脚本进行讲解。本文将以一个小白的视角,从零基础讲解ctf中常用的几种hook脚本的实现,frida配置不再赘述,网上资料很多。文章末尾附刚结束的ciscn国赛安卓题目

Frida hook

我们常说‘‘用frida脚本去hook’’。hook一词即为钩子,程序运行时我们的脚本钩住目标函数,对其进行修改从而达到目的

Frida 脚本基本框架(js)

多说几句:js中,function用于定义函数,末尾的main()的作用是调用函数。console.log()即在控制台输出;var 或const都可以用于声明变量。javascript对缩进无要求,末尾分号加不加都可

先来看Java.perform : Java.perform(fn): ensure that the current thread is attached to the VM and call fn. ——Java.perform(fn):确保当前线程连接到虚拟机并调用fn。这个Api确保我们后面写的函数附加到进程而生效

注入进去看看

了解了Frida基本框架,以下将通过闯关的形式进行更深入的探索Frida的魅力

主动调用函数

第0关


a方法接受两个相同的参数,返回第一个参数的sha256值

基本逻辑:若username的sha256值与password的值相等则验证通过

脚本如下

Java,use() 用来获取当前类,在上述脚本中赋值给变量LoginActivity。

LoginActivity.a即调用这个class里的a方法,传入值123,那么result的值为123的sha256值

因此,username填入123,password填result,就可以通过验证

修改函数参数或返回值

第一关使用jeb分析汇编,因为第二关的过关条件永远为假,jadx看不到汇编无法分析

基本逻辑:“R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=”若与“R4jSLLLLLLLLLRknplkBpZDpis69kh7i+YAPTmMn2ABsOLLLLL==”相等则过关,这显然不现实。而后者字符串是a函数的返回值,由此我们可以对返回值修改成“R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=”使比较条件为真

.implentation作用是修改函数实现,将a函数替换为上述脚本中的function(),强行改变返回值

点击按钮就进入了下一关

调用非静态函数

仍然分析反汇编

static_bool_var和bool_var都为真即过关

而setStatic_bool_var函数和setBool_var函数的作用是将那两个值设置为true

思路显然易见,调用这两个函数不就好了吗!

但是,setStatic_bool_var函数前有static字样,而setBool_var函数没有,这说明setBool_var函数是非静态函数,像这样setBool_var()来调用将会报错,因为没有创建它的实例

详解见图片

这里要调用非静态函数,使用Java.choose这一Api

Java.choose(className, callbacks):通过扫描Java堆枚举className类的活动实例,其中callbacks是一个对象,指定:

onMatch(instance):用一个现成的实例调用每个活动实例

onComplete():在枚举所有实例后调用

设置成员变量与设置和函数名相同的成员变量

来到第三关

跟上一关差不多,但是没有现成的函数来设置值为真,需要手动设置,使用.value进行修改

重点:在这一关的class中,既有same_name_bool_var属性,又有same_name_bool_var函数。

我们想对属性进行修改,则一定要使用xxx._same_name_bool_var.value=true

多加的这个下划线代表着这里是对属性设置值而不是函数。

hook动态加载的dex

package com.example.androiddemo.Activity;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import com.example.androiddemo.Dynamic.CheckInterface;
import dalvik.system.DexClassLoader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/* loaded from: classes.dex */
public class FridaActivity5 extends BaseFridaActivity {
    private CheckInterface DynamicDexCheck = null;

    @Override // com.example.androiddemo.Activity.BaseFridaActivity
    public String getNextCheckTitle() {
        return "当前第5关";
    }

    /* JADX WARN: Removed duplicated region for block: B:46:0x0060 A[Catch: IOException -> 0x005c, TRY_LEAVE, TryCatch #6 {IOException -> 0x005c, blocks: (B:42:0x0058, B:46:0x0060), top: B:55:0x0058 }] */
    /* JADX WARN: Removed duplicated region for block: B:55:0x0058 A[EXC_TOP_SPLITTER, SYNTHETIC] */
    /*
        Code decompiled incorrectly, please refer to instructions dump.
    */
    public static void copyFiles(Context context, String str, File file) {
        InputStream inputStream;
        FileOutputStream fileOutputStream;
        FileOutputStream fileOutputStream2 = null;
        fileOutputStream2 = null;
        InputStream inputStream2 = null;
        try {
            try {
                inputStream = context.getApplicationContext().getAssets().open(str);
            } catch (IOException e) {
                e = e;
                fileOutputStream = null;
            } catch (Throwable th) {
                th = th;
                inputStream = null;
            }
            try {
                fileOutputStream = new FileOutputStream(file.getAbsolutePath());
                try {
                    byte[] bArr = new byte[1024];
                    while (true) {
                        int read = inputStream.read(bArr);
                        if (read == -1) {
                            break;
                        }
                        fileOutputStream.write(bArr, 0, read);
                    }
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    fileOutputStream.close();
                } catch (IOException e2) {
                    e = e2;
                    inputStream2 = inputStream;
                    try {
                        e.printStackTrace();
                        if (inputStream2 != null) {
                            inputStream2.close();
                        }
                        if (fileOutputStream != null) {
                            fileOutputStream.close();
                        }
                    } catch (Throwable th2) {
                        th = th2;
                        inputStream = inputStream2;
                        fileOutputStream2 = fileOutputStream;
                        if (inputStream != null) {
                            try {
                                inputStream.close();
                            } catch (IOException e3) {
                                e3.printStackTrace();
                                throw th;
                            }
                        }
                        if (fileOutputStream2 != null) {
                            fileOutputStream2.close();
                        }
                        throw th;
                    }
                } catch (Throwable th3) {
                    th = th3;
                    fileOutputStream2 = fileOutputStream;
                    if (inputStream != null) {
                    }
                    if (fileOutputStream2 != null) {
                    }
                    throw th;
                }
            } catch (IOException e4) {
                e = e4;
                fileOutputStream = null;
            } catch (Throwable th4) {
                th = th4;
                if (inputStream != null) {
                }
                if (fileOutputStream2 != null) {
                }
                throw th;
            }
        } catch (IOException e5) {
            e5.printStackTrace();
        }
    }

    private void loaddex() {
        File cacheDir = getCacheDir();
        if (!cacheDir.exists()) {
            cacheDir.mkdir();
        }
        String str = cacheDir.getAbsolutePath() + File.separator + "DynamicPlugin.dex";
        File file = new File(str);
        try {
            if (!file.exists()) {
                file.createNewFile();
                copyFiles(this, "DynamicPlugin.dex", file);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.DynamicDexCheck = (CheckInterface) new DexClassLoader(str, cacheDir.getAbsolutePath(), null, getClassLoader()).loadClass("com.example.androiddemo.Dynamic.DynamicCheck").newInstance();
            if (this.DynamicDexCheck == null) {
                Toast.makeText(this, "loaddex Failed!", 1).show();
            }
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    public CheckInterface getDynamicDexCheck() {
        if (this.DynamicDexCheck == null) {
            loaddex();
        }
        return this.DynamicDexCheck;
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // com.example.androiddemo.Activity.BaseFridaActivity, androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        loaddex();
    }

    @Override // com.example.androiddemo.Activity.BaseFridaActivity
    public void onCheck() {
        if (getDynamicDexCheck() != null) {
            if (getDynamicDexCheck().check()) {
                CheckSuccess();
                startActivity(new Intent(this, FridaActivity6.class));
                finishActivity(0);
                return;
            }
            super.CheckFailed();
            return;
        }
        Toast.makeText(this, "onClick loaddex Failed!", 1).show();
    }
}

这关代码有点长,check函数在动态加载的dex中,像上文直接hook会提示找不到路径

看到代码中的dexclassloader其实就意味着要用此方法了!

Java.enumerateClassLoaders(): Java. enumerateclassloaders (callbacks):枚举Java VM中存在的类加载器,其中callbacks是一个对象,指定:

onMatch(loader):使用loader对每个类加载器调用,loader是特定java.lang.ClassLoader的包装器。

onComplete():在枚举了所有类加载器后调用。

这个函数仍然有两个回调。

要点1:使用Java.enumerateClassLoaders()查找所有classloader

要点2:使用Java.classFactory.loader切换classloader为当前loader(dalvik.system.DexClassLoader),而不是其他pathloader,只有dexloader才能找到我们动态加载的dex中的check函数

实战——2024ciscn安卓题目

check部分:

private boolean legal(String paramString) {
        return paramString.length() == 38 && paramString.startsWith("flag{") && paramString.charAt(paramString.length() - 1) == '}' && !inspect.inspect(paramString.substring(5, paramString.length() - 1));

逻辑在inspect函数中,跟进看看

加密文本有了,解一个DES即可,看看key和iv

key和iv在native层,静态函数,可以直接hook

hook两次把key和iv都整出来

flag就出来了

总结

通过实战做题提升熟练度,同一结果可能有多种实现方式,不要使思维固化
官方网站查看Api:https://frida.re/docs/javascript-api/#script

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