ZEVMS 冒险岛服务端V79 ver2 破解服务端登录人数限制(3)Java Agent动态修改静态变量
上次我们通过X64dbg 拿到了运行脚本,本次我们使用Java Agent做进一步分析
- 试着dump 字节码,能否将解密后的字节码拿到
- 分析程序运行过程,试着动态修改变量以达成目的
在开始之前,大体介绍一下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.ZEVMS 其中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();
}
}
}
这样就可以将一些感兴趣的类静态变量内容打印出来看一下了
接下来这就是个纯体力和解密的活了,最终我找到了这个变量, 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 程序来动态修改程序参数的程序,最终实现了解除服务端不允许超过一个用户登录的限制,虽然粗糙但是管用。
看的我热血沸腾啊
怎么收藏这篇文章?
想想你的文章写的特别好https://www.237fa.com/
想想你的文章写的特别好https://www.ea55.com/
想想你的文章写的特别好www.jiwenlaw.com
文章的确不错啊https://www.cscnn.com/
兄弟写的非常好 https://www.cscnn.com/
真好呢
真好呢
《了不起的女孩》国产剧高清在线免费观看:https://www.jgz518.com/xingkong/35060.html
《帕哈甘吉(导演剪辑版 )》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/135688.html
哈哈哈,写的太好了https://www.lawjida.com/
哈哈哈,写的太好了https://www.lawjida.com/