上次我们通过X64dbg 拿到了运行脚本,本次我们使用Java Agent做进一步分析

  1. 试着dump 字节码,能否将解密后的字节码拿到
  2. 分析程序运行过程,试着动态修改变量以达成目的

在开始之前,大体介绍一下Java Agent,在java程序启动前或启动中可以对字节码进行修改。非常多框架和特性用到了java agent,比如 AspectJ 通过使用java agent织入字节码增强实现AOP,一些RPC框架使用java agent来做一些session id统一侵入等。参考文档:https://blog.csdn.net/ancinsdn/article/details/58276945

要加载java agent 当然,启动脚本也要做相应的修改:

java -javaagent:F:\workspace\class-dump-agent\target\dump-agent-1.0-SNAPSHOT.jar -server -Dnet.sf.odinms.wzpath=wz gui.    其中F:\workspace\class-dump-agent\target\dump-agent-1.0-SNAPSHOT.jar为java agent package后的jar包    

1.dump 字节码

在类加载后,可以尝试将字节码存到文件中,逻辑很简单

public class ClassDumpAgent implements ClassFileTransformer {
 
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
 
        String filePath = "/tmp/" + className.replace('.', '/') + ".class";
        System.out.println("dump "+className + " to "+filePath + " size="+classfileBuffer.length);
        try {
            File file = new File(filePath);
            File fileParent = file.getParentFile();
            if(!fileParent.exists()){
                fileParent.mkdirs();
            }
            OutputStream out = new FileOutputStream(filePath);
            InputStream is = new ByteArrayInputStream(classfileBuffer);
            byte[] buff = new byte[1024];
            int len = 0;
            while((len=is.read(buff))!=-1){
                out.write(buff, 0, len);
            }
            is.close();
            out.close();
            System.out.println("dump "+ className + " complete");
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
}

但结果不理想,dump出来的字节码依旧还是加密过的。
究其原因,是该加密的class文件是jvm里的C++书写的BootStrap ClassLoader解密和加载的,而不是在java层自己写的ClassLoader负责解密的,java agent拿到的依旧是输入到jvm之前的字节码。
可以说该JVM中,class文件的格式本就是加密后的方式,相当于jvm输入字节码的协议都修改了。

2.实时修改变量

虽然字节码反编译不好做,但是加载之后的类还是要遵循java内存模型的,所以我们可以通过反射拿到这些类的方法、成员变量,看看有什么办法。

public class ClassDumpAgent implements ClassFileTransformer {
 
    static Set classNames = new HashSet(){{
        this.add("constants.ServerConstants");
        this.add("gui.ZEVMS");
        this.add("server.ServerProperties");
        this.add("handling.world.MapleParty");
    }};
    public byte[] transform(ClassLoader loader, final String className, final Class<?> classBeingRedefined, ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException {
 
        String classNamePot = className.replace('/','.');
        if(!classNames.contains(classNamePot)){
            return null;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                startDump(className,classBeingRedefined,classfileBuffer);
            }
        }).start();
 
        return null;
    }
 
    public void startDump(String className,Class<?> classBeingRedefined, byte[] classfileBuffer){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String classNamePot = className.replace('/','.');
        try {
            Class cc = Class.forName(classNamePot);
            Field[] fields = Class.forName(classNamePot).getDeclaredFields();
            Method[] methods = cc.getDeclaredMethods();
            System.out.println("CLASS: "+classNamePot);
            System.out.println("field-------");
            Object instance = null;
            for(Field field : fields){
                System.out.println(field.getName() + ":" +field.getType().getName()+ "static:" + Modifier.isStatic(field.getModifiers()));
 
                if(field.getName().equals("instance")){
                    field.setAccessible(true);
                    instance = field.get(Object.class);
                    System.out.println(instance);
                }
                if(field.getName().equals("显示人数")){
                    field.setAccessible(true);
                    Object f = field.get(instance);
                    System.out.println(f);
                }
                if(field.getName().equals("props")){
                    field.setAccessible(true);
                    Object f = field.get(Object.class);
                    Properties props = (Properties)f;
                    props.list(System.out);
                }
            }
            System.out.println("method-------");
            for(Method method: methods){
                System.out.println(method.getName());
            }
            System.out.println("dump "+ className + " complete");
 
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}

这样就可以将一些感兴趣的类静态变量内容打印出来看一下了

javaagent1

接下来这就是个纯体力和解密的活了,最终我找到了这个变量, handling.world.MapleParty 类中的静态变量”容纳人数”(PS:这个老哥是真的都起中文变量名呀)。

只消将这个变量设置大一些,就可以了。
实际运行时发现该变量不是程序启动后就设置的,而是有一个延时,所以程序里做了个循环设置的操作

            Class cc = Class.forName(classNamePot);
            Field[] fields = Class.forName(classNamePot).getDeclaredFields();
            Method[] methods = cc.getDeclaredMethods();
            Object instance = null;
            for(Field field : fields){
                if(Modifier.isStatic(field.getModifiers())){
                    field.setAccessible(true);
                    Object f = field.get(Object.class);
                    System.out.println("class="+classNamePot+" field=" + field.getName()+
                                ":" +field.getType().getName()+ " static:" + " value=" + f);
 
                }
                if(field.getName().equals("容纳人数")){
                    field.setAccessible(true);
 
                    field.set(Object.class,MAX_USERS);
                    final Field fl = field;
                    instance = field.get(Object.class);
                    System.out.println(field.getName() +" value="+instance);
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                int trys=6;
                                while (trys>0) {
                                    Thread.sleep(10000);
                                    Object f = fl.get(Object.class);
                                    System.out.println(fl.getName() + " value=" + f);
                                    fl.set(Object.class,MAX_USERS);
                                    System.out.println(fl.getName() + " value=" + MAX_USERS);
                                    trys--;
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
 
                        }
                    }).start();
                }
            }

至此,通过外挂一个java agent 程序来动态修改程序参数的程序,最终实现了解除服务端不允许超过一个用户登录的限制,虽然粗糙但是管用。

浏览:0
© 2020 Zhonghcc 's Blog Suffusion theme by Sayontan Sinha