下载附件后解压得到 nuaactf.jar 使用 jdgui反编译
查看入口Main类
密码校验的逻辑只是简单做了字符比对 加密结果 caf4cbafdf72ce0f2f2eadc4309916e8c96f0de8
密码加密的逻辑比较复杂 但是由于长度有限且提示中有爆破 把这段逻辑copy出来后使用字典遍历执行 爆破出密码
下图为加密主逻辑
爆破代码如下
// scala 脚本平台不支持选的java 将就看
private def dynamicClassLoader(): Unit = {
val pass = "caf4cbafdf72ce0f2f2eadc4309916e8c96f0de8"
val table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
val tLen = table.length.toLong
val len = Math.pow(tLen.toDouble,4).toLong
println(len)
0L.until(len).foreach(i=> {
val key = table(((i/(tLen*tLen*tLen))%tLen).toInt).toString +
table(((i/(tLen*tLen))%tLen).toInt).toString +
table(((i/tLen)%tLen).toInt).toString +
table((i%tLen).toInt).toString
val passStr = dynamicClassLoaderPassDigest(key.getBytes).map(b=>String.format("%02x", b&0xff)).mkString
if(pass.equals(passStr)){
println(key + ":" + passStr)
}
})
}
private def dynamicClassLoaderPassDigest(x: Array[Byte]): Array[Byte] = {
val blks = new Array[Int](((x.length + 8 >> 6) + 1) * 16)
var i = 0
i = 0
while (i < x.length) {
blks(i >> 2) = blks(i >> 2) | x(i) << 24 - i % 4 * 8
i += 1
}
blks(i >> 2) = blks(i >> 2) | 128 << 24 - i % 4 * 8
blks(blks.length - 1) = x.length * 8
val w = new Array[Int](80)
var a = 1732584193
var b = -271733879
var c = -1732584194
var d = 271733878
var e = -1009589776
i = 0
while (i < blks.length) {
val olda = a
val oldb = b
val oldc = c
val oldd = d
val olde = e
for (j <- 0 until 80) {
w(j) = if (j < 16) blks(i + j)
else dynamicClassLoaderPassRol(w(j - 3) ^ w(j - 8) ^ w(j - 14) ^ w(j - 16), 1)
val t = dynamicClassLoaderPassRol(a, 5) + e + w(j) + (if (j < 20) 1518500249 + (b & c | (b ^ 0xFFFFFFFF) & d)
else if ((j < 40)) (1859775393 + (b ^ c ^ d))
else (if ((j < 60)) (-(1894007588) + (b & c | b & d | c & d))
else -(899497514) + (b ^ c ^ d)))
e = d
d = c
c = dynamicClassLoaderPassRol(b, 30)
b = a
a = t
}
a += olda
b += oldb
c += oldc
d += oldd
e += olde
i += 16
}
val digest = new Array[Byte](20)
dynamicClassLoaderPassFill(a, digest, 0)
dynamicClassLoaderPassFill(b, digest, 4)
dynamicClassLoaderPassFill(c, digest, 8)
dynamicClassLoaderPassFill(d, digest, 12)
dynamicClassLoaderPassFill(e, digest, 16)
digest
}
private def dynamicClassLoaderPassRol(num: Int, cnt: Int) = num << cnt | num >>> 32 - cnt
private def dynamicClassLoaderPassFill(value: Int, arr: Array[Byte], off: Int): Unit = {
arr(off + 0) = (value >> 24 & 0xFF).toByte
arr(off + 1) = (value >> 16 & 0xFF).toByte
arr(off + 2) = (value >> 8 & 0xFF).toByte
arr(off + 3) = (value >> 0 & 0xFF).toByte
}
执行结果如下
得到密码 mdzz 这个密码后面还有用所以这个爆破过程不能跳过
再看回主类里的flag逻辑部分 可以看到是通过 a14b64a0683594003b4efe8a2285acd8 类来加载其他类执行方法
a14b64a0683594003b4efe8a2285acd8类是重写的classloader
使用爆破的密码作为key 使用固定字串的iv通过AES算法解码加密类数据还原为真实类
复制这段逻辑将其他无法反编译的类还原之后再反编译
具体代码如下
// scala 脚本平台不支持选的java 将就看
val passByte = "mdzz".getBytes
val passStr = new String(passByte)
println(passStr)
val keyByte = Md5Utils.md5(passByte)
val data = FileUtils.readStream(new FileInputStream("目录\\旧类名.class"))
val target = AesUtils.decodeCbcPkcs5(data, keyByte, "****************".getBytes)
FileUtils.writeStream(target, new File("目录\\旧类名_new.class"))
反编还原类译结果为
根据调用链路依次为
校验目标
加工步骤1
加工步骤2
原地返回
逆向整个过程得到flag值
代码如下
// scala 脚本平台不支持选的java 将就看
val checkflag: Array[Byte] = Array(100, 106, 55, 53, 80, 48, 66, 0, 95, 81, 2, 55, 110, 108, 67, 54, 119, 51)
val checkflag2: Array[Byte] = checkflag.zipWithIndex.map(b=>{
if(b._2 % 3 == 0){
(b._1 ^ 0x6).toByte
}else{
b._1
}
})
val checkflag3: Array[Byte] = checkflag2.zipWithIndex.map(b=>{
if((b._2+2) % 3 == 0){
(b._1 ^ 0x33).toByte
}else{
b._1
}
})
println(new String(checkflag3))
运行结果为 bY73c0D3_W17h_C0D3
按代码格式提交 nuaactf{bY73c0D3_W17h_C0D3} 返回失败 直接提交 bY73c0D3_W17h_C0D3 还是失败
提交 flag{bY73c0D3_W17h_C0D3} 返回成功