移动安全测试 针对 Xposed hook 密码框的防护

易寒 · 2016年03月17日 · 最后由 易寒 回复于 2016年03月17日 · 1741 次阅读
本帖已被设为精华帖!

原理

TextWatcher 接口为 EditText 控件的响应字符输入事件提供了 3 个方法,这三个方法分别为:

  • public void beforeTextChanged(CharSequence s, int start, int count, int after)
  • public void onTextChanged(CharSequence s, int start, int before, int count)
  • public void afterTextChanged(Editable s)

我们通过自定义一个类,实现该接口,然后重写这三个方法,将输入的字符保存到 StringBuilder 中,而在 EditText 实际显示的是*号。这样当你 hook 住 EditText.getText() 的时候,获取的密码肯定是多个*,而实际的密码保存在StringBuilder中。

步骤

自定义 TextWatcher 类

public class EditChangedListener implements TextWatcher {

    private static final String TAG = "EditChangedListener";
    private CharSequence temp;
    private EditText editText;
    private boolean add = false;
    private StringBuilder password = new StringBuilder();
    private int mStart;

    public EditChangedListener(EditText editText) {
        this.editText = editText;
    }

    //after表示这次新加了几个字符,count表示旧的内容中被修改了几个字符,start表示修改的开始位置
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//        Log.i(TAG, "beforeTextChanged : " + s + ",start = " + start + ",count = " + count + ",after = " + after);


    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //before 原有内容被修改的数量,count为新增的数量,start为操作位置
        if (s.length() == 0) return;
//        Log.i(TAG, "onTextChanged : " + s + ",start = " + start + ",count = " + count + ",before = " + before);

        if (count == 0 && password.length() > start) {
            password.deleteCharAt(start);
            Log.i(TAG, "删除了第" + start + "个字符 : " + s.charAt(start - 1));
            inputStarForEdit(password.length());
            add = false;
            return;
        }

        if (count > 0) {
            add = true;
        }
    }


    @Override
    public void afterTextChanged(Editable s) {

        if (!add || s.length() == 0) return;
        int length = s.length();
        char c = s.charAt(length - 1);

        if (c == '*') return;
        password.append(c);
        inputStarForEdit(length);
    }

    private void inputStarForEdit(int length) {

        editText.getText().clear();

        for (int i = 0; i < length; ++i) {
            editText.getText().append("*");
        }
        Log.i(TAG, "真实的为密码: " + password.toString() + ",伪装的密码为 : " + editText.getText());
    }
}

这里面的代码逻辑,一定要理解,核心在这

设置密码框的响应器为上面的类


mPasswordView = (EditText) findViewById(R.id.password);

mPasswordView.addTextChangedListener(new EditChangedListener(mPasswordView));

效果

总结

具体应用到你项目中,可能要考虑的问题比上面的 demo 要多,但是原理基本差不多,招行银行的策略与上面类似。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 4 条回复 时间 点赞

#2 楼 @seveniruby 其实没有真正能防住的,更强的是通过绘制的方式,但也是徒劳,只要有心,总能拿到,只是给出一个小的方法而已

这招只是提升了门槛. 实际上防不住. 只要继承自 android 的标准控件就没法防护. dalvik 层不安全. 我一般都是 hook beforeTextChanged 的. 甚至有的时候直接 hook String 类的所有方法. 来截获可能有价值的字符串.

其他 app 为了做防护. 用自定义控件结合自定义键盘什么的方法都有. 就是为了干扰 hook

赞啊~

弱弱地问个问题,程序中其他地方需要获取输入框文字时,也需要使用一些特殊的方法来获取这个内部的 StringBuilder 了?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册