首页
社区
课程
招聘
[原创]安卓日记_使用frida进行hook
发表于: 2022-10-24 09:54 8694

[原创]安卓日记_使用frida进行hook

2022-10-24 09:54
8694

前言

业余时间写的,纯兴趣更新!

项目地址

Vukky97/frida-demo: Intentionally vulnerable Android application for Frida Demonstration. (github.com)

 

运行apk

PIN Bypass

常规逆向

反编译

 

查看MainActivity,发现有三个界面,默认停留在Pin Bypass界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package infosecadventures.fridademo;
 
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import infosecadventures.fridademo.fragments.EncryptionKey;
import infosecadventures.fridademo.fragments.PinBypass;
import infosecadventures.fridademo.fragments.RootBypass;
 
/* loaded from: classes.dex */
public class MainActivity extends AppCompatActivity {
    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { // from class: infosecadventures.fridademo.MainActivity.1
            @Override // com.google.android.material.bottomnavigation.BottomNavigationView.OnNavigationItemSelectedListener
            public boolean onNavigationItemSelected(MenuItem menuItem) {
                switch (menuItem.getItemId()) {
                    case R.id.encryption_key /* 2131230801 */:
                        MainActivity.this.getSupportFragmentManager().beginTransaction().replace(R.id.frame, new EncryptionKey()).commit();
                        return true;
                    case R.id.pin_bypass /* 2131230868 */:
                        MainActivity.this.getSupportFragmentManager().beginTransaction().replace(R.id.frame, new PinBypass()).commit();
                        return true;
                    case R.id.root_bypass /* 2131230880 */:
                        MainActivity.this.getSupportFragmentManager().beginTransaction().replace(R.id.frame, new RootBypass()).commit();
                        return true;
                    default:
                        return false;
                }
            }
        });
        navigation.setSelectedItemId(R.id.pin_bypass);
    }
}

看下PinBypass类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package infosecadventures.fridademo.fragments;
 
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import infosecadventures.fridademo.R;
import infosecadventures.fridademo.utils.PinUtil;
 
/* loaded from: classes.dex */
public class PinBypass extends Fragment {
    @Override // androidx.fragment.app.Fragment
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_pin_bypass, container, false);
        final EditText pin = (EditText) view.findViewById(R.id.pin);
        view.findViewById(R.id.pin_check).setOnClickListener(new View.OnClickListener() { // from class: infosecadventures.fridademo.fragments.PinBypass.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                if (pin.getText().toString().isEmpty()) {
                    Toast.makeText(PinBypass.this.getContext(), "PIN is not provided!", 0).show();
                } else if (PinUtil.checkPin(pin.getText().toString())) {
                    new AlertDialog.Builder(PinBypass.this.getActivity()).setTitle(PinBypass.this.getString(R.string.granted)).setMessage(PinBypass.this.getString(R.string.success_pin)).setPositiveButton("Dismiss", new DialogInterface.OnClickListener() { // from class: infosecadventures.fridademo.fragments.PinBypass.1.1
                        @Override // android.content.DialogInterface.OnClickListener
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).show();
                } else {
                    new AlertDialog.Builder(PinBypass.this.getActivity()).setTitle(PinBypass.this.getString(R.string.denied)).setMessage(PinBypass.this.getString(R.string.fail_pin)).setPositiveButton("Dismiss", new DialogInterface.OnClickListener() { // from class: infosecadventures.fridademo.fragments.PinBypass.1.2
                        @Override // android.content.DialogInterface.OnClickListener
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).show();
                }
            }
        });
        return view;
    }
}

当我们点击按钮后,会调用类PinUtil.checkPin方法,参数为我们的输入。

 

看PinUtil.checkPin方法是怎么实现的

1
2
3
4
5
6
7
8
9
10
package infosecadventures.fridademo.utils;
 
import android.util.Base64;
 
/* loaded from: classes.dex */
public class PinUtil {
    public static boolean checkPin(String pin) {
        return pin.equals(new String(Base64.decode("NDg2Mw==", 0)));
    }
}

输入的字符串应该和"NDg2Mw=="解密后的结果即"4863"是一致的。

 

但不是今天的重点。

tips

在frida在attach进程的时候 居然填的是Frida-ps -U查出来的对应的进程名字才可以!

 

使用ps -A 查出来的进程那么是'infosecadventures.fridademo',pid是20035

 

使用Frida-ps -U查出来的进程那么是'Frida Demo',pid也是20035

 

但在run.py中写process_name写成'infosecadventures.fridademo'报错找不到进程,写成'Frida Demo'才可以。

 

但process_name写成进程pid即20035也可。

run.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#coding:utf8
import sys
import frida
#process_name = 'infosecadventures.fridademo'
process_name = 'Frida Demo'
#process_name = 20035
# 发送信息回调函数
def on_message(message, data):
    if message['type'] == 'send':
        print(f"[*] {message['payload']}")
    else:
        print(message)
if __name__ == '__main__':
    try:
        device = frida.get_usb_device(timeout=1000)
        print("* get usb device成功")
    except:
        device = frida.get_remote_device(timeout=1000)
        print("* get remote device成功")
    if not device:
        print("* 连接到Frida Server失败")
    else:
        process = device.attach(process_name)
        #pid = device.spawn(["infosecadventures.fridademo"])
        #process = device.attach(pid)
        # 加载JS脚本
        js = open('hook.js', encoding='utf-8').read()
        print(js)
        script = process.create_script(js)
        script.on('message', on_message)
        script.load()
        # 读取返回输入
        input()
        script.unload()

hook.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function() {
    //获取指定类
    var cls = Java.use('infosecadventures.fridademo.utils.PinUtil');
    //HookCheckPin函数
    cls.checkPin.overload("java.lang.String").implementation = function(arg1) {
        //打印入参
        console.log('checkPin-in:', arg1);
        //调用原函数
        var result = this.checkPin(arg1);
        //打印出参
        console.log('checkPin-out:', result);
        //返回给原函数的调用
        return result;
    }
});

可以看到hook成功。

 

hook1.js

让hook函数直接返回true,然后就可以弹框success_pin了。

1
2
3
4
5
6
7
8
9
10
Java.perform(function() {
    var cls = Java.use('infosecadventures.fridademo.utils.PinUtil');
    //HookCheckPin函数
    cls.checkPin.overload("java.lang.String").implementation = function(arg1) {
        //打印入参
        console.log('checkPin-in', arg1);
        //直接返回true
        return true;
    }
});

 

hook2.js

当我们点击check按钮的时候,便会执行我们写的hook函数,暴力破解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java.perform(function() {
    var cls = Java.use('infosecadventures.fridademo.utils.PinUtil');
    //HookCheckPin函数
    cls.checkPin.overload("java.lang.String").implementation = function(arg1) {
        //函数进入
        console.log('开始破解密码');
        //暴力破解
        for (var i = 0;; i++) {
            var result = this.checkPin(i.toString());
            if (result) {
                console.log('破解完成,密码是', i)
                break;
            } else {
                console.log('密码错误', i)
            }
        }
        //返回给原函数的调用
        return result;
    }
});

 

爆破成功。

 

root Bypass

 

查看反编译代码

类RootBypass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package infosecadventures.fridademo.fragments;
 
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import infosecadventures.fridademo.R;
import infosecadventures.fridademo.utils.RootUtil;
 
/* loaded from: classes.dex */
public class RootBypass extends Fragment {
    @Override // androidx.fragment.app.Fragment
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_root_bypass, container, false);
        view.findViewById(R.id.root_check).setOnClickListener(new View.OnClickListener() { // from class: infosecadventures.fridademo.fragments.RootBypass.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                if (RootUtil.isRootAvailable()) { //关键函数RootUtil.isRootAvailable()
                    new AlertDialog.Builder(RootBypass.this.getActivity()).setTitle(RootBypass.this.getString(R.string.denied)).setMessage(RootBypass.this.getString(R.string.fail_message)).setPositiveButton("Dismiss", new DialogInterface.OnClickListener() { // from class: infosecadventures.fridademo.fragments.RootBypass.1.1
                        @Override // android.content.DialogInterface.OnClickListener
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).show();
                } else {
                    new AlertDialog.Builder(RootBypass.this.getActivity()).setTitle(RootBypass.this.getString(R.string.granted)).setMessage(RootBypass.this.getString(R.string.success_message)).setPositiveButton("Dismiss", new DialogInterface.OnClickListener() { // from class: infosecadventures.fridademo.fragments.RootBypass.1.2
                        @Override // android.content.DialogInterface.OnClickListener
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    }).show();
                }
            }
        });
        return view;
    }
}

RootUtil.isRootAvailable()

遍历paths中的目录是否存在如果存在就返回true,否则返回flase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package infosecadventures.fridademo.utils;
 
import java.io.File;
 
/* loaded from: classes.dex */
public class RootUtil {
    public static boolean isRootAvailable() {
        String[] paths = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
        for (String path : paths) {
            if (new File(path).exists()) {
                return true;
            }
        }
        return false;
    }
}

root_hook.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function() {
    //获取指定类
    var cls = Java.use('infosecadventures.fridademo.utils.RootUtil');
    //Hook指定函数
    cls.isRootAvailable.overload().implementation = function() {
        //进入函数
        console.log('isRootAvailable-in');
        //调用原函数
        var result = this.isRootAvailable();
        //打印出参
        console.log('isRootAvailable-out', result);
        //返回给原函数的调用
        return result;
    }
});

hook成功。(因为测试机本身已经root了)

 

 

通过hook的方式可以绕过root检测

 

第一种是直接不调用原函数,直接返回flase。

 

第二种是java.io.File对象的exists方法,即使文件存在也返回flase。

 

第一种很简单不记录了,记录下第二种。

root_hook1.js

当类java.io.File的方法exists的参数是paths的其中一个时,做个flag为true标记,hook函数返回为flase,否则调用原函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Java.perform(function() {
    //获取指定类
    var cls = Java.use('infosecadventures.fridademo.utils.RootUtil');
    //Hook指定函数
    cls.isRootAvailable.overload().implementation = function() {
        //进入函数
        console.log('isRootAvailable -in');
        //调用原函数
        var result = this.isRootAvailable();
        //打印出参
        console.log('isRootAvailable -out', result);
        //返回给原函数的调用
        return result;
    }
    var cls2 = Java.use('java.io.File');
    //Hook指定函数
    cls2.exists.overload().implementation = function() {
        //进入函数
        console.log('exists-in');
        //获取自身的path
        var mypath = this.getPath();
        //root特征文件列表
        var paths = new Array("/system/app/Superuser.apk", "/sbin/su",
            "/system/bin/su", "/system/xbin/su",
            "/data/local/xbin/su", "/data/local/bin/su",
            "/system/sd/xbin/su", "/system/bin/failsafe/su",
            "/data/local/su", "/su/bin/su");
        //判断路径是否匹配root特征
        var flag = false;
        paths.forEach(function(path) {
            if (mypath == path) {
                console.log('检测到root特征文件', path);
                flag = true;
                return;
            }
        });
        if (!flag) {
            //调用原函数,避免影响正常功能
            var result = this.exists();
        } else {
            //返回false,绕过root检测
            result = false;
        }
        console.log('exists-out', result)
        return result;
    }
});

 

发现成功绕过

Encrypto Key

类EncryptionKey

程序逻辑,当点击ENCRYPTO按钮后,调用EncryptionUtil.encrypt方法,参数1为"infosecadventure",参数二为用户的输入。然后打印出返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package infosecadventures.fridademo.fragments;
 
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import infosecadventures.fridademo.R;
import infosecadventures.fridademo.utils.EncryptionUtil;
 
/* loaded from: classes.dex */
public class EncryptionKey extends Fragment {
    @Override // androidx.fragment.app.Fragment
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_encryption_key, container, false);
        final EditText plain = (EditText) view.findViewById(R.id.plain);
        final EditText cipher = (EditText) view.findViewById(R.id.cipher);
        view.findViewById(R.id.encrypt).setOnClickListener(new View.OnClickListener() { // from class: infosecadventures.fridademo.fragments.EncryptionKey.1
            @Override // android.view.View.OnClickListener
            public void onClick(View v) {
                cipher.setText("");
                String plain_text = plain.getText().toString();
                if (!plain_text.isEmpty()) {
                    cipher.setText(EncryptionUtil.encrypt("infosecadventure", plain_text));
                } else {
                    Toast.makeText(EncryptionKey.this.getContext(), EncryptionKey.this.getString(R.string.no_plaintext), 0).show();
                }
            }
        });
        return view;
    }
}

看下EncryptionUtil.encrypt方法

EncryptionUtil.encrypt()

发现将我们的输入进行了AES加密,但单纯看代码并没有对参数一进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package infosecadventures.fridademo.utils;
 
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
 
/* loaded from: classes.dex */
public class EncryptionUtil {
    public static String encrypt(String key, String value) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
            cipher.init(1, secretKeySpec);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            return new String(encrypted);
        } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

这个demo的加密模块没明白要用来做什么hook测试的,aes加密的key已经在源码中写出来了呢。

 

对EncryptionUtil.encrypt()进行hook,打印参数和返回值吧。

encrypto_hook.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function() {
    //获取指定类
    var cls = Java.use('infosecadventures.fridademo.utils.EncryptionUtil');
    //Hook指定函数
    cls.encrypt.overload('java.lang.String', 'java.lang.String').implementation = function(arg1, arg2) {
        //打印入参
        console.log('encrypt-in', arg1, arg2);
        //调用原函数
        var result = this.encrypt(arg1, arg2);
        //打印出参
        console.log('encrypt-out', result);
        //返回给原函数的调用
        return result;
    }
});


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 310
活跃值: (960)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
本人菜鸡一枚,欢迎大佬们陆续加入到QQ群 801022487 一起交流学习
2022-10-25 09:47
0
雪    币: 2328
活跃值: (10364)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
2022-10-25 17:01
0
雪    币: 230
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
2022-10-28 11:20
0
游客
登录 | 注册 方可回帖
返回
//