-
-
[原创]KCTF2020秋季赛 第四题 突破重围
-
2020-11-24 10:11 3351
-
本题安卓题目,反编译apk文件,首先查看onCreate,加载了crack.so文件和asset目录的b.txt文件
System.loadLibrary("crack"); this.init(); // 加载b.txt
然后在按键回调函数调用关键check函数,如下函数check返回true,验证成功。
public void onClick(View arg6) { if(MainActivity.this.check(((EditText)this.findViewById(0x7F070063)).getText().toString())) { // id:inputEditText new AlertDialog.Builder(this).setTitle("result").setMessage("Congratulations! app is exit.....").show(); ((EditText)this.findViewById(0x7F070063)).setText(""); // id:inputEditText new Thread(new Runnable() { @Override public void run() { try { Thread.currentThread(); Thread.sleep(5000L); } catch(InterruptedException e) { e.printStackTrace(); } System.exit(0); } }).start(); return; } ....
顺着思路,查看MainActivity.this.check,该函数中调用了类“com.kanxue.crackme.Crack”的check方法。
public boolean check(String arg10) { Class v0_2; DexClassLoader v0 = MainActivity.dexClassLoader; if(v0 != null) { Class clazz = null; try { v0_2 = v0.loadClass("com.kanxue.crackme.Crack"); } catch(ClassNotFoundException e) { e.printStackTrace(); goto label_12; } clazz = v0_2; label_12: if(clazz != null) { try { Method[] methods = clazz.getDeclaredMethods(); Method methodCheck = null; int v0_5; for(v0_5 = 0; v0_5 < methods.length; ++v0_5) { Method i = methods[v0_5]; if(i.getName().contains("check")) { methodCheck = i; } } return ((Boolean)methodCheck.invoke(null, arg10)).booleanValue(); } catch(IllegalAccessException e) { e.printStackTrace(); return 0; } catch(InvocationTargetException e) { } e.printStackTrace(); return 0; e.printStackTrace(); } } return 0; }
我们可以看到b.txt文件是一个dex格式文件,反编译后,可以找到上面提到的check方法。分析该方法,可以得到整个数据加密流程
sn->rc4->crackjni->rc4->base64->result
如果result == checkResult则check成功
checkResult是类“com.kanxue.crackme.MyCrack”的field“crypt”
MyCrack.crypt = "otVvmpP4ZI58pqB26OTaYw==
public static boolean check(String sn) { int v6; Field field; if(sn != null) { if(sn.length() != 16) { } else { byte[] sn_rc4 = Crack.rc4(sn.getBytes()); Method crackjni = null; try { Class v1 = Crack.class.getClassLoader().loadClass("com.kanxue.crackme.MyCrack"); field = v1.getDeclaredField("crypt"); Method[] v5_2 = v1.getDeclaredMethods(); v6 = 0; while(true) { label_21: if(v6 < v5_2.length) { if(v5_2[v6].getName().equals("crackjni")) { crackjni = v5_2[v6]; } break; } else { goto label_38; } } } catch(NoSuchFieldException v5) { goto label_34; } catch(ClassNotFoundException v5_1) { goto label_37; } ++v6; goto label_21; label_37: v5_1.printStackTrace(); goto label_38; label_34: v5.printStackTrace(); label_38: Object v5_3 = null; if(crackjni != null) { try { Object v3_1 = crackjni.invoke(v5_3, sn_rc4); } catch(InvocationTargetException v7) { v7.printStackTrace(); } catch(IllegalAccessException v7_1) { v7_1.printStackTrace(); } } String result = Base64.encodeToString(Crack.rc4(sn_rc4), 0); String v8 = "test"; if(field != null && v1 != null) { try { Object checkResult = field.get(v5_3); } catch(IllegalAccessException v5_4) { v5_4.printStackTrace(); } } if(result.equals(checkResult)) { return 1; } return 0; } } return 0; }
接下来分析crackjni,核心算法是AES。
v21 = GetByteArrayElements(evn, sn, 0); snLen = GetArrayLength(evn, sn); snBuf1 = (jbyte *)operator new[](snLen); memset(snBuf1, 0, snLen); qmemcpy(snBuf1, v21, snLen); snBk = operator new[](snLen); aes((int)snBuf1, (int)&g_aesKey, snBk); ReleaseByteArrayElements(evn, sn, (int)v21, 0);
到此感觉可以收工。
正向加密流:sn->rc4->aes->rc4->base64->result==checkResult
逆向求解 :checkResult->base64->rc4->aes->rc4->sn
发现掉入作者的坑里,返回填坑。
在crackjin中,还有一部分code,之前感觉和sn没有关系,忽略了
if ( g_rc4Flag ) { if ( g_pdex ) { prc4Key1 = g_pdex + 0x16D3A6; val = *(_BYTE *)(g_pdex + 0x16D3A6); v4 = g_rc4Flag++; *(_BYTE *)(g_pdex + 0x16D3A6) = val + v4; *(_BYTE *)(prc4Key1 + 1) = 0x3D; v11 = sub_C8C2(evn, &unk_1F200); v10 = sub_C8EC(evn, v11, a0S, &unk_1F230); v9 = sub_C934(evn, &unk_1F270); sub_C960(evn, v11, v10, v9); } ++g_rc4Flag; } else { if ( g_pdex ) { prc4Key2 = g_pdex + 0x16D3A6; *(_BYTE *)(g_pdex + 0x16D3A6) = 0xD3; *(_BYTE *)(prc4Key2 + 1) = 0x3D; v15 = sub_C8C2(evn, &unk_1F200); v14 = sub_C8EC(evn, v15, a0S, &unk_1F230); v13 = sub_C934(evn, &unk_1F250); sub_C960(evn, v15, v14, v13); } ++g_rc4Flag; }
这个0x16D3A6是b.text(dex)中rc4方法的rc4key字符串ID的位置。
public static byte[] rc4(byte[] data) { byte v9; int v1_1; String rc4Key = "kaokaonio"; byte[] v1 = null; if(data == null) { return v1; }
如下图
如果按程序意思将3DCF修改为3DD3,我们就拿到了第二次RC4的key
填坑完毕。按照上面逆向思路,注意两次RC4的key,成功获取到flag{thisiskey}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课