Frida hook基础(一)

  • 调用静态函数和调用非静态函数
  • 设置(同名)成员变量
  • 内部类,枚举类的函数并hook,trace原型1
  • 查找接口,hook动态加载dex
  • 枚举class,trace原型2
  • objection不能切换classloader

Frida hook : 打印参数、返回值/设置返回值/主动调用

首先是安卓的登录的代码

public class LoginActivity extends AppCompatActivity {
    /* access modifiers changed from: private */
    public Context mContext;
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        this.mContext = this;
        setContentView((int) R.layout.activity_login);
        final EditText editText = (EditText) findViewById(R.id.username);
        final EditText editText2 = (EditText) findViewById(R.id.password);
        ((Button) findViewById(R.id.login)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                String obj = editText.getText().toString();
                String obj2 = editText2.getText().toString();
                if (TextUtils.isEmpty(obj) || TextUtils.isEmpty(obj2)) {
                    Toast.makeText(LoginActivity.this.mContext, "username or password is empty.", 1).show();
                } else if (LoginActivity.a(obj, obj).equals(obj2)) {
                    LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1.class));
                    LoginActivity.this.finishActivity(0);
                } else {
                    Toast.makeText(LoginActivity.this.mContext, "Login failed.", 1).show();
                }
            }
        });
    }
    private static String a(byte[] bArr) {
        StringBuilder sb = new StringBuilder();
        int i = 0;
        while (bArr != null && i < bArr.length) {
            String hexString = Integer.toHexString(bArr[i] & 255);
            if (hexString.length() == 1) {
                sb.append('0');
            }
            sb.append(hexString);
            i++;
        }
        return sb.toString().toLowerCase();
    }
    /* access modifiers changed from: private */
    public static String a(String str, String str2) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(str2.getBytes(), "HmacSHA256");
            Mac instance = Mac.getInstance("HmacSHA256");
            instance.init(secretKeySpec);
            return a(instance.doFinal(str.getBytes()));
        } catch (Exception e) {
            e.printStackTrace();
            return BuildConfig.FLAVOR;
        }
    }
}

LoginActivity.a(obj, obj).equals(obj2)分析之后可得obj2来自password,由从username得来的obj,经过a函数运算之后得到一个值,这两个值相等则登录成功。
所以这里关键是hook a函数的参数,最简脚本如下。

打印参数、返回值
//打印参数、返回值
function Login(){
    Java.perform(function(){
        Java.use("com.example.androiddemo.Activity.LoginActivity").a.overload('java.lang.String', 'java.lang.String').implementation = function (str, str2){
            var result = this.a(str, str2);
            console.log("args0:"+str+" args1:"+str2+" result:"+result);
            return result;
        }
    })
}
setImmediate(Login)
观察输入和输出,这里也可以直接主动调用
function login() {
    Java.perform(function () {
        console.log("start")
        var login = Java.use("com.example.androiddemo.Activity.LoginActivity")
        var result = login.a("1234","1234")
        console.log(result)
    })
}
setImmediate(login)

结果


image.png

然后

adb shell input text "4e4feaea959d426155a480dc07ef92f4754ee93edbe56d993d74f131497e66fb"

接下来是第一关

直接把返回值喂给函数

安卓代码

ublic class FridaActivity1 extends BaseFridaActivity {
    private static final char[] table = {'L', 'K', 'N', 'M', 'O', 'Q', 'P', 'R', 'S', 'A', 'T', 'B', 'C', 'E', 'D', 'F', 'G', 'H', 'I', 'J', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'o', 'd', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'e', 'f', 'g', 'h', 'j', 'i', 'k', 'l', 'm', 'n', 'y', 'z', '0', '1', '2', '3', '4', '6', '5', '7', '8', '9', '+', '/'};

    public String getNextCheckTitle() {
        return "当前第1关";
    }

    public void onCheck() {
        try {
            if (a(b("请输入密码:")).equals("R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=")) {
                CheckSuccess();
                startActivity(new Intent(this, FridaActivity2.class));
                finishActivity(0);
                return;
            }
            super.CheckFailed();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String a(byte[] bArr) throws Exception {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i <= bArr.length - 1; i += 3) {
            byte[] bArr2 = new byte[4];
            byte b = 0;
            for (int i2 = 0; i2 <= 2; i2++) {
                int i3 = i + i2;
                if (i3 <= bArr.length - 1) {
                    bArr2[i2] = (byte) (b | ((bArr[i3] & 255) >>> ((i2 * 2) + 2)));
                    b = (byte) ((((bArr[i3] & 255) << (((2 - i2) * 2) + 2)) & 255) >>> 2);
                } else {
                    bArr2[i2] = b;
                    b = 64;
                }
            }
            bArr2[3] = b;
            for (int i4 = 0; i4 <= 3; i4++) {
                if (bArr2[i4] <= 63) {
                    sb.append(table[bArr2[i4]]);
                } else {
                    sb.append('=');
                }
            }
        }
        return sb.toString();
    }

    public static byte[] b(String str) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            GZIPOutputStream gZIPOutputStream = new GZIPOutputStream(byteArrayOutputStream);
            gZIPOutputStream.write(str.getBytes());
            gZIPOutputStream.finish();
            gZIPOutputStream.close();
            byte[] byteArray = byteArrayOutputStream.toByteArray();
            try {
                byteArrayOutputStream.close();
                return byteArray;
            } catch (Exception e) {
                e.printStackTrace();
                return byteArray;
            }
        } catch (Exception unused) {
            return null;
        }
    }
}

关键函授在aa(b("请输入密码:")).equals("R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=")
这里直接 hook a 让其返回值为 "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=" 就可以进入下一关

function challenge1(){
    Java.perform(function(){
        Java.use("com.example.androiddemo.Activity.FridaActivity1").a.implementation = function(bArr){
            console.log("inside Frida1 a function")
            return Java.use('java.lang.String').$new("R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=");
        }
    })
}
setImmediate(challenge1)

Frida hook : 主动调用静态/非静态函数 以及 设置静态/非静态成员变量的值

总结:

  • 静态函数直接use class然后调用方法,非静态函数需要先choose实例然后调用
  • 设置成员变量的值,写法是xx.value = yy,其他方面和函数一样。
  • 如果有一个成员变量和成员函数的名字相同,则在其前面加一个_,如_xx.value = yy

然后是第二关

public class FridaActivity2 extends BaseFridaActivity {
    private static boolean static_bool_var = false;
    private boolean bool_var = false;

    public String getNextCheckTitle() {
        return "当前第2关";
    }

    private static void setStatic_bool_var() {
        static_bool_var = true;
    }

    private void setBool_var() {
        this.bool_var = true;
    }

    public void onCheck() {
        if (!static_bool_var || !this.bool_var) {
            super.CheckFailed();
            return;
        }
        CheckSuccess();
        startActivity(new Intent(this, FridaActivity3.class));
        finishActivity(0);
    }
}

这一关的关键在于下面的if判断要为false,则static_bool_var和this.bool_var都要为true。

function challenge2(){
    Java.perform(function(){
        //hook静态函数直接调用
        var FridaActivity2 = Java.use("com.example.androiddemo.Activity.FridaActivity2")
        FridaActivity2.setStatic_bool_var();
        
        //hook动态函数,找到instance实例,从实例调用函数方法
        Java.choose("com.example.androiddemo.Activity.FridaActivity2",{
            onMatch:function(instance){
                instance.setBool_var();
            },onComplete:function(){}
        })

    })
}
setImmediate(challenge2)

接下来是第三关

public class FridaActivity3 extends BaseFridaActivity {
    private static boolean static_bool_var = false;
    private boolean bool_var = false;
    private boolean same_name_bool_var = false;

    public String getNextCheckTitle() {
        return "当前第3关";
    }

    private void same_name_bool_var() {
        Log.d("Frida", static_bool_var + " " + this.bool_var + " " + this.same_name_bool_var);
    }

    public void onCheck() {
        if (!static_bool_var || !this.bool_var || !this.same_name_bool_var) {
            super.CheckFailed();
            return;
        }
        CheckSuccess();
        startActivity(new Intent(this, FridaActivity4.class));
        finishActivity(0);
    }
}

关键还是让if (!static_bool_var || !this.bool_var || !this.same_name_bool_var)为false,则三个变量都要为true

function challenge3(){
    Java.perform(function(){
        var Frida3 = Java.use("com.example.androiddemo.Activity.FridaActivity3");
        //静态成员变量可以直接设置结果
        Frida3.static_bool_var.value = true;
        console.log("After set new value 1:"+Frida3.static_bool_var.value);
        //动态成员变量需要找到实例,给实例设置结果;
        Java.choose("com.example.androiddemo.Activity.FridaActivity3",{
            onMatch:function(instance){
                instance.bool_var.value = true ;
                console.log("After set new value 2:"+instance.bool_var.value);
                instance._same_name_bool_var.value = true ;
                console.log("After set new value 3:"+instance._same_name_bool_var.value);
            },onComplete:function(){}
        })
    })
}
setImmediate(challenge3)

这里要注意类里有一个成员函数和成员变量都叫做same_name_bool_var,这种时候在成员变量前加一个_,修改值的形式为xx.value = yy

Frida hook : 内部类,枚举类的函数并hook,trace原型1

总结:

  • 对于内部类,通过类名$内部类名去use或者choose
  • 对use得到的clazz应用反射,如clazz.class.getDeclaredMethods()可以得到类里面声明的所有方法,即可以枚举类里面的所有函数。

接下来是第四关

public class FridaActivity4 extends BaseFridaActivity {
    public String getNextCheckTitle() {
        return "当前第4关";
    }

    private static class InnerClasses {
        public static boolean check1() {
            return false;
        }

        public static boolean check2() {
            return false;
        }

        public static boolean check3() {
            return false;
        }

        public static boolean check4() {
            return false;
        }

        public static boolean check5() {
            return false;
        }

        public static boolean check6() {
            return false;
        }

        private InnerClasses() {
        }
    }

    public void onCheck() {
        if (!InnerClasses.check1() || !InnerClasses.check2() || !InnerClasses.check3() || !InnerClasses.check4() || !InnerClasses.check5() || !InnerClasses.check6()) {
            super.CheckFailed();
            return;
        }
        CheckSuccess();
        startActivity(new Intent(this, FridaActivity5.class));
        finishActivity(0);
    }
}

hook 内部类下的所有方法

function challenge4(){
    Java.perform(function(){
        //内部类
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check1.implementation = function(){return true;}
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check2.implementation = function(){return true;}
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check3.implementation = function(){return true;}
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check4.implementation = function(){return true;}
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check5.implementation = function(){return true;}
        Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses").check6.implementation = function(){
            console.log("enter check6")
            return true;
        }
    })
}
setImmediate(challenge4)

利用反射,获取类中的所有method声明,然后字符串拼接去获取到方法名,例如下面的check1,然后就可以批量hook,而不用像我上面那样一个一个写。

function challenge42(){
    Java.perform(function(){
        var class_name = "com.example.androiddemo.Activity.FridaActivity4$InnerClasses"
        var InnerClass = Java.use(class_name);
        var all_methods = InnerClass.class.getDeclaredMethods();
        console.log(all_methods);
        for(var i = 0;i<all_methods.length;i++){
            var method = all_methods[i];
            console.log(method);
            var methodStr = method.toString();
            var substring = methodStr.substr(methodStr.indexOf(class_name)+class_name.length+1);
            var finalMethodString = substring.substr(0,substring.indexOf("("));
            console.log(finalMethodString);
            InnerClass[finalMethodString].implementation = function(){return true};
        }
    })
}
setImmediate(challenge42)

Frida hook : hook动态加载的dex,与查找interface,

总结:

  • 通过enumerateClassLoaders来枚举加载进内存的classloader,再loader.findClass(xxx)寻找是否包括我们想要的interface的实现类,最后通过Java.classFactory.loader = loader来切换classloader,从而加载该实现类。

第五关比较有趣,它的check函数是动态加载进来的。
java里有interface的概念,是指一系列抽象的接口,需要类来实现。

public class FridaActivity5 extends BaseFridaActivity {
    private CheckInterface DynamicDexCheck = null;

    public String getNextCheckTitle() {
        return "当前第5关";
    }
 
    public static void copyFiles(android.content.Context r2, java.lang.String r3, java.io.File r4) {
   
        throw new UnsupportedOperationException("Method not decompiled: com.example.androiddemo.Activity.FridaActivity5.copyFiles(android.content.Context, java.lang.String, java.io.File):void");
    }

    private void loaddex() {
        File filesDir = getFilesDir();
        if (!filesDir.exists()) {
            filesDir.mkdir();
        }
        String str = filesDir.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, filesDir.getAbsolutePath(), (String) 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;
    }

    /* access modifiers changed from: protected */
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        loaddex();
    }

    public void onCheck() {
        if (getDynamicDexCheck() == null) {
            Toast.makeText(this, "onClick loaddex Failed!", 1).show();
        } else if (getDynamicDexCheck().check()) {
            CheckSuccess();
            startActivity(new Intent(this, FridaActivity6.class));
            finishActivity(0);
        } else {
            super.CheckFailed();
        }
    }
}

这里有个loaddex其实就是先从资源文件加载classloader到内存里,再loadClass DynamicCheck,创建出一个实例,最终调用这个实例的check。
所以现在我们就要先枚举class loader,找到能实例化我们要的class的那个class loader,然后把它设置成Java的默认class factory的loader。
现在就可以用这个class loader来使用.use去import一个给定的类。

function challenge5(){
    Java.perform(function(){
        Java.choose("com.example.androiddemo.Activity.FridaActivity5",{
            onMatch:function(instace){
                console.log(instace.getDynamicDexCheck().$className)
            },onComplete:function(){}
        })
        Java.enumerateClassLoaders({
            onMatch:function(loader){
                try{
                    if(loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")){
                        console.log("Successfully found loader")
                        console.log(loader);
                        Java.classFactory.loader = loader ;
                    }
                }catch(error){
                    console.log("find error:"+error)
                }
            },onComplete:function(){}
        })

        var DynamicCheck = Java.use("com.example.androiddemo.Dynamic.DynamicCheck");
        console.log(DynamicCheck);
        DynamicCheck.check.implementation = function(){return true};
    })
}
setImmediate(challenge5)

Frida hook : 枚举class,trace原型2

总结: 通过Java.enumerateLoadedClasses来枚举类,然后name.indexOf(str)过滤一下并hook。

接下来是第六关

public class FridaActivity6 extends BaseFridaActivity {
    public String getNextCheckTitle() {
        return "当前第6关";
    }

    public void onCheck() {
        if (!Frida6Class0.check() || !Frida6Class1.check() || !Frida6Class2.check()) {
            super.CheckFailed();
            return;
        }
        CheckSuccess();
        startActivity(new Intent(this, FridaActivity7.class));
        finishActivity(0);
    }
}

这关是import了一些类,然后调用类里的静态方法,所以我们枚举所有的类,然后过滤一下,并把过滤出来的结果hook上,改掉其返回值。

function challenge6(){
    Java.perform(function(){
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class0").check.implementation = function(){return true};
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class1").check.implementation = function(){return true};
        Java.use("com.example.androiddemo.Activity.Frida6.Frida6Class2").check.implementation = function(){return true};
    })
}
setImmediate(challenge6)

Frida hook : 搜索interface的具体实现类

第7关

public class FridaActivity7 extends BaseFridaActivity {
    public String getNextCheckTitle() {
        return "当前第7关";
    }

    public void onCheck() {
    }
}

利用反射得到类里面实现的interface数组,并打印出来。

//枚举class,trace原型2
function challenge62(){
    Java.perform(function(){
        Java.enumerateLoadedClasses({
            onMatch:function(name,handle){
                //console.log("name:"+name+" handle:"+handle)
                if(name.indexOf("com.example.androiddemo.Activity.Frida6")>=0){
                    console.log("name:"+name+" handle:"+handle)
                    Java.use(name).check.implementation=function(){return true}
                }
            },onComplete:function(){}
        })
    })
}
setImmediate(challenge62)

参考:https://eternalsakura13.com/2020/07/04/frida/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351