Frida hook基础(一)

作者: completed_1 | 来源:发表于2020-08-04 07:41 被阅读0次
    • 调用静态函数和调用非静态函数
    • 设置(同名)成员变量
    • 内部类,枚举类的函数并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/

    相关文章

      网友评论

        本文标题:Frida hook基础(一)

        本文链接:https://www.haomeiwen.com/subject/dsbhrktx.html