首页
论坛
课程
招聘
[原创]KCTF2020秋季赛 第四题 突破重围
2020-11-24 10:11 1696

[原创]KCTF2020秋季赛 第四题 突破重围

2020-11-24 10:11
1696

本题安卓题目,反编译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}



[注意] 招人!base上海,课程运营、市场多个坑位等你投递!

最后于 2020-11-24 10:29 被ODPan编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回