-
-
[原创]攻防世界Mobile通关记录_人民的名义-抓捕赵德汉1-200_wp
-
发表于: 2022-10-12 14:10 5933
-
人民的名义-抓捕赵德汉1-200
题目描述
下载附件
169e139f152e45d5ae634223fe53e6be.jar
运行它,发现报错
1 2 3 4 | java - jar 169e139f152e45d5ae634223fe53e6be .jar Exception in thread "main" java.lang.NullPointerException at CheckPassword.loadCheckerObject(CheckPassword.java: 52 ) at CheckPassword.main(CheckPassword.java: 23 ) |
把jdk11换成8就可以了。
分析
使用jd查看jar的反编译代码
发现有三个类
类CheckPassword来自于ClassLoader,并实现的main方法,即入口函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; public class CheckPassword extends ClassLoader / / CheckPassword来自于ClassLoader { static String hexKey = "bb27630cf264f8567d185008c10c3f96" ; public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { 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)) { / / 调用checkerObject的checkPassword方法 System.out.println( "Well done, that is the correct password" ); System.exit( 0 ); continue ; }System.out.println( "Incorrect password" ); } } private static CheckInterface loadCheckerObject() throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, ClassFormatError, InstantiationException, IllegalAccessException { 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 = mycl.defineClass(null, myClassBytes, 0 , myClassBytes.length); CheckInterface passCheckObject = (CheckInterface)myclass.newInstance(); return passCheckObject; } private static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[ len / 2 ]; for ( int i = 0 ; i < len ; i + = 2 ) { data[(i / 2 )] = (byte)((Character.digit(s.charAt(i), 16 ) << 4 ) + Character.digit(s.charAt(i + 1 ), 16 )); } return data; } } |
程序逻辑代码在CheckPassword类中
调用了checkerObject.checkPassword(line)方法,参数为我们的输入。
而第23行就是将CheckInterface类给实列化一个对象checkerObject。
类CheckInterface
1 2 3 4 | public abstract interface CheckInterface { public abstract boolean checkPassword(String paramString); } |
即CheckInterface类其实是个接口类,然后想谁实现了这个接口类。这里我们想到了第三个类newClassName
类newClassName
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import java.io.PrintStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class CheckPass implements CheckInterface { public boolean checkPassword(String input ) / / checkPassword方法 { 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()); / / 将 input 给进行md5加密 hashBytes = md5Obj.digest(); return byteArrayToHexString(hashBytes).equals( "fa3733c647dca53a66cf8df953c2d539" ); } private static String byteArrayToHexString(byte[] data) { StringBuffer buf = new StringBuffer(); for ( int i = 0 ; i < data.length; i + + ) { int halfbyte = data[i] >>> 4 & 0xF ; int two_halfs = 0 ; do { if ((halfbyte > = 0 ) && (halfbyte < = 9 )) buf.append((char)( 48 + halfbyte)); else buf.append((char)( 97 + (halfbyte - 10 ))); halfbyte = data[i] & 0xF ; } while ( two_halfs + + < 1 ); } return buf.toString(); } } |
即在这里发现将我们的输入经过md5加密后和
fa3733c647dca53a66cf8df953c2d539作比较如果相同就正确。
所以这里将它进行md5解密就可。
所以flag为
1 | flag{monkey99} |
解题
但其实真正的程序逻辑是在CheckPassword类中的main方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { 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对象其实是loadCheckerObject方法的返回值,loadCheckerObject方法为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | private static CheckInterface loadCheckerObject() throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, ClassFormatError, InstantiationException, IllegalAccessException { CheckPassword mycl = new CheckPassword(); InputStream in = CheckPassword. class .getClass().getResourceAsStream( "/ClassEnc" ); ByteArrayOutputStream bout = new ByteArrayOutputStream(); / / 获取ClassEnc的字节流 int len = 0 ; byte[] bytes = new byte[ 512 ]; while (( len = in .read(bytes)) > - 1 ) { bout.write(bytes, 0 , len ); } byte[] myClassBytesEnc = bout.toByteArray(); / / 并将其存入到myClassBytesEnc数组中 in .close(); / / hexKey为 "bb27630cf264f8567d185008c10c3f96" SecretKeySpec secretKeySpec = new SecretKeySpec(hexStringToByteArray(hexKey), "AES" ); Cipher decAEScipher = Cipher.getInstance( "AES" ); decAEScipher.init( 2 , secretKeySpec); byte[] myClassBytes = decAEScipher.doFinal(myClassBytesEnc); / / 将myClassBytesEnc进行aes解密,key为 "bb27630cf264f8567d185008c10c3f96" / / 解密后的结果存入myClassBytes中 Class myclass = mycl.defineClass(null, myClassBytes, 0 , myClassBytes.length); CheckInterface passCheckObject = (CheckInterface)myclass.newInstance(); / / 然后再将这个返回结果去以newClassName类创建一个实例 return passCheckObject; } |
所以这里应该正确的解题是将ClassEnc文件进行aes解密,key为bb27630cf264f8567d185008c10c3f96,解密结果其实就是一个类作为返回值。接着再看CheckPassword类中的main方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { CheckInterface checkerObject = loadCheckerObject(); / / 解密得到的类实例化了checkerObject BufferedReader stdin = new BufferedReader(new InputStreamReader(System. in )); while (true) { System.out.println( "Enter password:" ); String line = stdin.readLine(); if (checkerObject.checkPassword(line)) { / / 然后调用了checkerObject对象的checkPassword System.out.println( "Well done, that is the correct password" ); System.exit( 0 ); continue ; }System.out.println( "Incorrect password" ); } } |
所以正解应该是首先获得解密后的类,然后查看checkerObject对象的checkPassword方法的内部实现。
将题目附件的后缀从jar改成zip,将ClassEnc给提取出来然后进行aes解密即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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() |
解密后发现和newClassName.class数据是一样的
所以上面直接newClassName类中的checkPassword方法去将我们的输入经过md5加密后和
fa3733c647dca53a66cf8df953c2d539作比较是歪打正找,如果解密后的.class文件和newClassName。class不一致的话这样操作就不会正确了。
但现在要如何查看这个解密后的源码呢。用java写一个同样的自定义类加载器将.class文件给加载并打印出来?
不知道出题人没有将附件中的newClassName类给删除掉是不是故意降低题目难度。
这道题涉及到了自定义类加载器的知识点。实际看下具体是什么效果,从而更利于我们以后遇到同类型的题更好地分析。
自定义类加载器
参考
这个跟着参考复现下。
(32条消息) JVM——自定义类加载器_SEUCalvin的博客-CSDN博客自定义类加载器的应用场景
准备
准备一个.class文件
记事本创建Pelpeo.java,然后通过命令行工具javac People.java生成Pelpeo.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class People { / / javac People.java private String name; public People() {} public People(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return "I am a people, my name is " + name; } } |
这里注意下,如果java代码中有中文,默认情况下会报错。
1 2 3 4 5 | >javac People.java People.java: 2 : 错误: 编码GBK的不可映射字符 / / 璇ョ被鍐欏湪璁颁簨鏈噷锛屽湪鐢╦avac鍛戒护琛岀紪璇戞垚 class 鏂囦欢锛屾斁鍦╠鐩樻牴鐩綍涓? ^ 1 个错误 |
不过其实加上-encoding utf8可以解决
1 | javac - encoding utf8 People.java |
将People.class放到E盘根目录中。
然后创建project
自定义类加载器
创建一个自定义的类加载器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package com.example.zidingyileijiazai_20221011; import java.io.ByteArrayOutputStream; import java.io. File ; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; public class MyClassLoader extends ClassLoader{ public MyClassLoader() { } public MyClassLoader(ClassLoader parent){ super (parent); } protected Class<?> findClass(String name) throws ClassNotFoundException{ / / File file = new File ( "E:" + File .separator + "People.class" ); File file = new File ( "E:/People.class" ); / / File file = new File ( "/data/local/tmp/People.class" ); try { byte[] bytes = getClassBytes( file ); / / Class<?> c = this.defineClass(name,bytes , 0 , bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super .findClass(name); } private byte[] getClassBytes( File file ) throws Exception { / / 这里要读入. class 的字节,因此要使用字节流 FileInputStream fis = new FileInputStream( file ); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate( 1024 ); while (true){ int i = fc.read(by); if (i = = 0 || i = = - 1 ) break ; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } } |
构造Main类
1 2 3 4 5 6 7 8 9 10 11 12 13 | package com.example.zidingyileijiazai_20221011; public class Main { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyClassLoader mcl = new MyClassLoader(); Class<?> clazz = Class.forName( "People" , true, mcl); Object obj = clazz.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader()); / / 打印出我们的自定义类加载器 } } |
效果
然后右键对其进行Debug
不过却显示这个
不过不影响其实。不管它。
运行的时候发现报以下错
1 2 3 4 5 6 7 8 9 | FAILURE: Build failed with an exception. * Where: Initialization script 'C:\Users\yang\AppData\Local\Temp\Main_main__.gradle' line: 41 * What went wrong: A problem occurred configuring project ':app' . > Could not create task ':app:Main.main()' . > SourceSet with name 'main' not found. |
解决办法:
在.idea/gradle.xml中加入一行代码即可
参考于:
(32条消息) 在 Android Studio 上运行 Java 的 main 函数_右手的鼠标的博客-CSDN博客
成功解决后发现成功加载了。