题目附件为一个jar文件。
提示信息为:flag{xxxxxx},这是flag格式的提示。
直接运行jar:
提示输入password。
输入错误显示“Incorrect password”,并要求再次输入:
从运行情况看,重点在password的验证上。
下面静态分析一下jar文件。
使用jd-gui打开jar文件,看见里面有6个文件:
三个后缀名为class的为类文件。
简单看一下三个类文件,可以发现,checkPassword类基础自ClassLoader,并实现了main方法。也就是这个jar文件的入口函数。
main方法主要做两件事:
main方法里,提示用户输入、提示输入错误的字符串,和运行时看到的一致:
CheckInterface checkerObject = loadCheckerObject();
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("Enter password:");
String line = stdin.readLine();
if (checkerObject.checkPassword(line)) {
System.out.println("Well done, that is the correct password");
System.exit(0);
continue;
}
System.out.println("Incorrect password");
}
现在要重点搞清楚checkerObject的checkPassword方法如何检测用户的输入。
直接在jd-gui点击checkPassword方法名,会定位到CheckInterface类:
public interface CheckInterface {
boolean checkPassword(String paramString);
}
可见,CheckInterface类是一个接口,里面包含一个方法checkPassword。
那么谁实现了这个接口呢,我第一反应想到了CheckPassword和CheckInterface之外的第三个类newClassName:
public class CheckPass implements CheckInterface {
public boolean checkPassword(String input) {
MessageDigest md5Obj = null;
try {
md5Obj = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
System.out.println("Hash Algorithm not supported");
System.exit(-1);
}
byte[] hashBytes = new byte[40];
md5Obj.update(input.getBytes(), 0, input.length());
hashBytes = md5Obj.digest();
return byteArrayToHexString(hashBytes).equals("fa3733c647dca53a66cf8df953c2d539");
}
... ...
}
可见这个类实现了CheckInterface接口,并且有checkPassword方法。
这个checkPassword方法也很简单,就是对输入的字符串计算MD5,比较MD5值是否为"fa3733c647dca53a66cf8df953c2d539"。到MD5破解网站上可以直接得到明文:
将monkey99提交给jar程序,显示验证通过:
提交flag为:flag{monkey99},题目验证通过。
看上去题目已经做完了,但真的如此嘛?
在CheckPassword类的main方法中可以看到,checkerObject对象来自于loadCheckerObject函数的返回值。看看loadCheckerObject函数的反编译代码,发现checkerObject对象其实和newClassName没有关系,而是来自对ClassEnc文件的解密:
private static CheckInterface loadCheckerObject() {
CheckPassword mycl = new CheckPassword();
InputStream in = CheckPassword.class.getClass().getResourceAsStream("/ClassEnc");
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int len = 0;
byte[] bytes = new byte[512];
while ((len = in.read(bytes)) > -1)
bout.write(bytes, 0, len);
byte[] myClassBytesEnc = bout.toByteArray();
in.close();
SecretKeySpec secretKeySpec = new SecretKeySpec(hexStringToByteArray(hexKey), "AES");
Cipher decAEScipher = Cipher.getInstance("AES");
decAEScipher.init(2, secretKeySpec);
byte[] myClassBytes = decAEScipher.doFinal(myClassBytesEnc);
Class myclass = (Class)mycl.defineClass(null, myClassBytes, 0, myClassBytes.length);
CheckInterface passCheckObject = myclass.newInstance();
return passCheckObject;
}
这个函数其实就是以hexKey为秘钥,用AES算法对ClassEnc文件进行解密,并用解密后的内容创建一个新类,最后创建一个这个新类的对象并返回。
可见,checkerObject对象其实是这里解密后的类的对象,而不是newClassName类的对象,之前我们仅根据newClassName类实现了CheckInterface接口,假设这两者有关系,虽然成功解题了,却只是歪打正着。
将jar文件的后缀名修改为zip,解压后,即可得到ClassEnc文件。使用下面的python脚本即可对ClassEnc文件进行解密:
import os
from Crypto.Cipher import AES
filename="ClassEnc"
key = "bb27630cf264f8567d185008c10c3f96"
key_bytes = bytes.fromhex(key)
aes = AES.new((key_bytes), AES.MODE_ECB)
data = bytearray(os.path.getsize(filename))
with open(filename, 'rb') as f:
f.readinto(data)
f.close()
decryption_data = aes.decrypt(data)
with open(filename+"_decryption", 'ba') as f:
f.write(decryption_data)
f.close()
运行该脚本后,解密ClassEnc文件,并将解密后内容保存在ClassEnc_decryption文件中。
有趣的是,这里解密后的ClassEnc_decryption文件和jar包里自带的newClassName.class文件是完全一致的。难怪按照newClassName.class类也可以解出题目。
这里不知是题目作者故意留下newClassName.class文件还是忘了删除newClassName.class文件。