首页
社区
课程
招聘
[原创]攻防世界Mobile通关记录_人民的名义-抓捕赵德汉1-200_wp
发表于: 2022-10-12 14:10 5933

[原创]攻防世界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博客

 

成功解决后发现成功加载了。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 310
活跃值: (960)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
欢迎加入到QQ群 801022487 一起交流学习.
2022-10-12 17:47
0
游客
登录 | 注册 方可回帖
返回
//