初见
下载附件,是一个zip。
解压后有一个docx,一个apk。
打开docx看看,里面就一句话:
不懂安卓,所以就只是和安卓扯了扯边,,,Have fun~
没看出什么作用。
在模拟器中运行一下apk:
随便输入用户名和注册码:
没有更多信息了,反编译看看。
静态分析
将apk重命名为zip,解压。
使用dex2jar对解压得到的dex文件进行反编译:
使用jd-jui查看反编译得到的jar包。
在MainActivity的onCreate函数中可以看到注册按钮的事件响应函数的代码:
this.btn_register.setOnClickListener(new View.OnClickListener() {
public void onClick(View param1View) {
if (!MainActivity.this.checkSN(MainActivity.this.edit_userName.getText().toString().trim(), MainActivity.this.edit_sn.getText().toString().trim())) {
Toast.makeText((Context)MainActivity.this, 2131034123, 0).show();
return;
}
Toast.makeText((Context)MainActivity.this, 2131034120, 0).show();
MainActivity.this.btn_register.setEnabled(false);
MainActivity.this.setTitle(2131034118);
}
});
核心就是调用checkSN函数,参数为用户名和注册码字符串。checkSN函数的返回值就表示注册是否成功。
当checkSN返回0时,通过Toast打印编号为2131034123的字符串。
当checkSN返回1时,通过Toast打印编号为2131034120的字符串。
在jd-gui左侧的类列表中切换到R.class,可以看到这两个编号对应的字符串名分别为:
public static final class string {
... ...
public static final int successed = 2131034120;
... ...
public static final int unsuccessed = 2131034123;
... ...
}
从字符串名字就能知道,编号2131034120表示成功,也就是checkSN函数返回1,表示注册成功。
下面中的看看checkSN函数,看看如何让它返回1。
checkSN
反编译后的函数代码为:
private boolean checkSN(String paramString1, String paramString2) {
boolean bool = true;
try {
if (paramString1.length() == 0 && paramString2.length() == 0)
return true;
if (paramString1 == null || paramString1.length() == 0)
return false;
if (paramString2 == null || paramString2.length() != 16)
return false;
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.reset();
messageDigest.update(paramString1.getBytes());
paramString1 = toHexString(messageDigest.digest(), "");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < paramString1.length(); i += 2)
stringBuilder.append(paramString1.charAt(i));
boolean bool1 = stringBuilder.toString().equalsIgnoreCase(paramString2);
if (!bool1)
return false;
} catch (NoSuchAlgorithmException noSuchAlgorithmException) {
noSuchAlgorithmException.printStackTrace();
bool = false;
}
return bool;
}
看上去,内容挺多的,里面还出现了计算MD5的代码。
但是,当我们检查返回值时,发现一个有趣的地方,在第一个if判断中,如果参数两个字符串都为空,函数返回1。
if (paramString1.length() == 0 && paramString2.length() == 0)
return true;
这不正是我们想要的嘛。
但这里又存在两种情况:
- 注册成功后,程序打印出要提交的flag。这种情况下,只要checkSN返回1即可。
- 输入正确的flag,程序才注册成功。这种情况下,输入空字符串,虽然能让checkSN返回1,但还是不知道该提交的flag。
不管是哪种情况,我们先试一下,输入空用户名和注册码,结果如下:
得到一串md5值,到md5破解网站解密一下,明文为:
YOU_KNOW_
直接提交,不正确。这串字符串是做什么的,暂时不知道。
既然取巧失败,就回到checkSN函数本身去分析下它的验证算法。
这里是一个很明确的MD5计算过程。就是对用户名计算MD5,然后和注册码比较。
但比较过程时不是去完整的MD5值,而是隔一个字符取一个。MD5一共32个字符,这里隔一个字符取一个,取得16个字符:
for (int i = 0; i < paramString1.length(); i += 2)
stringBuilder.append(paramString1.charAt(i));
如果取出的16个字符,等于输入的注册码,就注册成功。
根据算法,提供用户名:A;注册码:7c67ea0a153b2ab2;(原MD5:7fc56270e7a70fa81a5935b72eacbe29)
注册成功,弹出和上面一样的MD5值。
这表示,能注册成功的,用户名和注册码组合有无数对。对任一用户名,都有一正确的注册码与之对应。
小机灵鬼?
到这里没思路了,后来查了别人的解题才理解作者的意图。
docx文档里的那就话,在这里就要用了。文档中说不懂安卓,而这里就要在md5解密后的字符串后面填上ANDROID。变成:
YOU_KNOW_ANDROID
提交这串字符串就对了。
还是脑洞不够大!
———————————————————————————————————————————
欢迎关注我的微博:大雄_RE。专注软件逆向,分享最新的好文章、好工具,追踪行业大佬的研究成果。