5月27日晚,微博上看到些QQ 上发送 [菜刀]+数字+[表情]
就会被转为骂人的话,28日中午就分析完了,但一直懒得发出来,今天补上。吐槽一下知乎上的某些沙雕分析文(小声 bb)
一、demo 演示视频挺多的,这没什么好被和谐的,随便贴两个视频链接吧
https://weibo.com/tv/v/GiE0hBfQe?fid=1034:2bd3171c50e53c24e1bcd6669c610e31
https://weibo.com/tv/v/GiDDnbtYD?fid=1034:4ff5a3565d2ee8b3cead9daca06a8902
是不是很刺激?第一反应是某开发不高兴,在里面内嵌了脏话,或者是测试时候用脏话测试的,忘了删掉了。
二、分析 1、先下载最新版的 QQ,确认一下情况,确实存在这种现象,先判断一下是本地还是远程的吧,幸运的是在本地配置的,在 classes2.dex 里搜到了硬编码的脏话,而且是一个脏话数组。(过于恶心的我就打码了)
本地试了一下,以2个testcase 为例,[菜刀]+"1"+[心]
被翻译为死胖子,[菜刀]+" "+[心]
被翻译为[跳舞]AmN you
。
2、硬编码位于 com.tencent.mobileqq.lovelanguage.LoveLanguageConfig
,附近还有LoveLanguageManager
。
dirtyList
的引用地方不多,分别是以下几处
a. String LoveLanguageConfig.convertCase(String)
,将前6个char改变大小写,这也是为什么"damn you"被转为了"[跳舞]AmN you"
的一个原因 b. boolean LoveLanguageConfig.isValidIndex(char)
,看是否下标越界 c. String LoveLanguageManager.AAA(String)
似乎是判断是否收到骂人的话,是的话就本地和谐掉,换成友好的表情 d. int LoveLanguageManager.BBB(EditText)
似乎是判断是否发出骂人的话,是的话就本地和谐掉,换成友好的表情 e. void LoveLanguageManager.CCC(EditText)
判断是否输入0x11,a,b,c,是的话就替换为脏话 这里有两个对 char 的运算
1 2 3 4 5 6 7 public static int a (char arg1) { return arg1 - 30 ; } public static int a (int arg1) { return arg1 + 30 ; }
既然dirtyList 是数组,那么肯定是有对应关系的,这两个方法很可能与它有关,因为输入[菜刀]+"1"+[心]
被翻译为死胖子,[菜刀]+" "+[心]
被翻译为[跳舞]AmN you
。而"1"-" "
是17,死胖子和 damn you
的间隔也是17,稍微猜一下就是线性的。
而" " - (char)30 =2
,"damn you"
的index也是2,所以这个运算就是将 char 和 dirtyWord 对应起来。
3、LoveLanguageManager
这里面 Log 有很多,有两句给了我们提醒,handleLoveLanguageConvert
和 handleLoveLanguageRevert
,似乎是转化之间的关系。
按顺序看吧,【其实我当时没看到后面那个方法】
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 public int a (EditText arg12) { int v10 = 2 ; this .d = false ; String v2 = arg12.getText().toString(); long v6 = System.currentTimeMillis(); int v1 = v2.length(); int v0 = 0 ; int v3 = 0 ; while (v0 < v1) { if (v2.charAt(v0) == 17 && v0 < v1 - 3 && (LoveLanguageConfig.a(v2.charAt(v0 + 1 )))) { v1 = v0 + 4 ; String v5 = LoveLanguageConfig.convertToDirty(v2.substring(v0, v1)); arg12.getEditableText().replace(v0, v1, ((CharSequence)v5)); v2 = arg12.getText().toString(); v1 = v2.length(); v0 = v0 + v5.length() - 1 ; ++v3; } ++v0; } this .d = true ; if (QLog.isColorLevel()) { Object[] v1_1 = new Object[6 ]; v1_1[0 ] = "love language handleLoveLanguageRevert count = " ; v1_1[1 ] = Integer.valueOf(v3); v1_1[v10] = ",cost =" ; v1_1[3 ] = Long.valueOf(Math.abs(System.currentTimeMillis() - v6)); v1_1[4 ] = ",send:" ; v1_1[5 ] = v2; QLog.d("LoveLanguageManager" , v10, v1_1); } this .a(); return v3; }
这段代码,安排明白了吧?看中间那段拿到输入框内容以后的逻辑,扫描一下,扫到 '\x11' + charA + charB + charC
这种格式的,调用 LoveLanguageConfig
的函数,进行转化后替换掉原来的文本,之后发出去。
绝大部分情况下,用户肯定不会输入 \x11
这个字符,所以猜测是 [菜刀]
表情编码中带有\x11,然后拼接了后面的几个任意的 char,就会被替换成脏话。【后来发现菜刀确实是 \x14\x11
组成的】
记住这个函数啊,叫 int a(EditText)
,附近还有个函数叫 void a(EditText)
,后面这个函数是将脏话替换为表情的。
验证一下猜想,输入[菜刀]+"111"
,触发;输入[菜刀]+1234567
,发现123消失了。
所以,我觉得是手滑把这两个函数写反了,毕竟参数一样,返回值可有可无这种。
4、上 xposed
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 48 49 50 51 52 53 54 55 56 57 58 59 ClassLoader loader = loadPackageParam.classLoader; Log.d(TAG, "start hook" ); XposedHelpers.findAndHookMethod("com.tencent.qphone.base.util.QLog" , loader, "d" , String.class, int .class, String.class, new XC\_MethodHook() { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Log.d(TAG, param.args[0 ] + "\\t" + param.args[2 ]); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); } }); XposedHelpers.findAndHookMethod("com.tencent.qphone.base.util.QLog" , loader, "isColorLevel" , new XC\_MethodReplacement() { @Override protected Object replaceHookedMethod (MethodHookParam methodHookParam) throws Throwable { return true ; } }); XposedHelpers.findAndHookMethod("com.tencent.mobileqq.lovelanguage.LoveLanguageManager" , loader, "a" , EditText.class, new XC\_MethodReplacement() { @Override protected Object replaceHookedMethod (MethodHookParam methodHookParam) throws Throwable { return 0 ; } }); XposedHelpers.findAndHookMethod("com.tencent.mobileqq.lovelanguage.LoveLanguageManager" , loader, "a" , EditText.class, new XC\_MethodHook() { @Override protected void beforeHookedMethod (MethodHookParam param) throws Throwable { super .beforeHookedMethod(param); Object obj = param.thisObject; Method[] methods = obj.getClass().getDeclaredMethods(); for (Method m : methods) { Log.e(TAG, m.toGenericString() + "===" + printHexString(m.getName())); } EditText editText = (EditText) param.args[0 ]; String s = editText.getText().toString(); Log.d(TAG, "input=" + s); Log.d(TAG, "input=" + printHexString(s)); } @Override protected void afterHookedMethod (MethodHookParam param) throws Throwable { super .afterHookedMethod(param); EditText editText = (EditText) param.args[0 ]; String s = editText.getText().toString(); Log.d(TAG, "after=" + s); Log.d(TAG, "after=" + printHexString(s)); } }); Log.d(TAG, "end hook" );
我们输入:[菜刀]1[心]
的时候 输入输出的Log:
1 2 3 4 5 05-28 04:24:03.716 1511-1511/com.tencent.mobileqq D/LDB: input=1J 05-28 04:24:03.716 1511-1511/com.tencent.mobileqq D/LDB: input=141131144a 05-28 04:24:03.866 1511-1511/com.tencent.mobileqq D/LDB: LoveLanguageManager love language report 0X8009167 05-28 04:24:03.866 1511-1511/com.tencent.mobileqq D/LDB: after=死胖子 05-28 04:24:03.866 1511-1511/com.tencent.mobileqq D/LDB: after=14e6adbbe88396e5ad90
我们输入:[菜刀]123
的时候
1 2 3 4 05-28 05:04:28.338 1511-1511/com.tencent.mobileqq D/LDB: input=123 05-28 05:04:28.338 1511-1511/com.tencent.mobileqq D/LDB: input=1411313233 05-28 05:04:28.347 1511-1511/com.tencent.mobileqq D/LDB: after=死胖子 05-28 05:04:28.347 1511-1511/com.tencent.mobileqq D/LDB: after=14e6adbbe88396e5ad90
我们输入:[菜刀]空格[心]
的时候
1 2 3 4 05-28 05:11:06.107 1511-1511/com.tencent.mobileqq D/LDB: input= J 05-28 05:11:06.107 1511-1511/com.tencent.mobileqq D/LDB: input=141120144a 05-28 05:11:06.120 1511-1511/com.tencent.mobileqq D/LDB: after=dAmN you 05-28 05:11:06.120 1511-1511/com.tencent.mobileqq D/LDB: after=1464416d4e20796f75
5、[菜刀]表情包含2个 char,是\\x14\\x11
,既然如此,这里又有一个细节,其实转化为脏话以后,第一个 char 还是保留着的,当它与字母拼接时,是可以组合成 QQ 快捷表情发出去的,我们可以通过复制粘贴在聊天界面里获得它。
三、总结 开发手滑,两个函数调用弄混了,虽然\\x11
这个字符不容易写出来,但[菜刀]
加上任意3个字符还是很常见的,经过3个小版本,还是被用户不小心测出来了,哈哈哈,就看谁来背这个锅吧。