相关技术点
- 混淆后java字节码修改
- 基于netty的socket字节码处理
概述
该游戏的授权机制大致如下:
1:客户端与授权服务器建立socket链接
2:启动时客户端与服务器建立链接,发送任意字符串
3:服务端接收到信息后,或缺客户端的IP
4:服务端检测IP是否可用
5:验证完成后发送验证结果返回给客户端
6:客户端根据授权情况来确定是否启动,完成授权验证c
客户端分析
代码解读
客户端是基于springboot实现,关键代码使用了代码混淆方式,所以反编译后的代码会有正常的java不一致.但只是对变量以及一些实现做了变种,整体规则还是符合jvm的规范(这点比较重要,要是使用了代码加密,配套特殊的classloader,分析代码的难度会增加不少).
将代码导出source后,导入eclipse,构建简单的java项目,用于分析后续代码.
经分析与授权相关的关键代码如下:
- 类:com.bootstrap.xy2.forward.bootstrap.a 用于配置链接授权服务器的地址,后续需要变更此处
- 启动逻辑:
a:启动时读取预配置项
b:连接授权服务器,获取授权情况,
c:如果授权通过则读取client.properties配置项
d:如果授权不通过,则清空相关client.properties配置项 - 关键代码
//连通授权服务器
//com.bootstrap.xy2.forward.bootstrap.a
{
this.a = new NioEventLoopGroup();
this.c = new Bootstrap();
this.c.option(ChannelOption.TCP_NODELAY, Boolean.valueOf(true));
((Bootstrap)((Bootstrap)((Bootstrap)this.c.group(this.a)).channel(NioSocketChannel.class)).option(ChannelOption.SO_KEEPALIVE, Boolean.valueOf(true))).handler(new b(this));
return c();
}
public boolean c()
{
if ((this.b != null) && (this.b.isActive()))
return true;
ChannelFuture localChannelFuture = this.c.connect("47.92.30.61", "7725");
localChannelFuture.addListener(new c(this));
return true;
}
//解密
protected void decode(ChannelHandlerContext paramChannelHandlerContext, ByteBuf paramByteBuf, List<Object> paramList)
{
if (paramByteBuf.readableBytes() < 4)
return;
byte[] arrayOfByte = b.b(paramByteBuf);
for (int i = 0; i < arrayOfByte.length; i++)
arrayOfByte[i] = ((byte)(0xF ^ arrayOfByte[i]));
paramList.add(new String(arrayOfByte, "UTF-8"));
}
//加密
protected void a(ChannelHandlerContext paramChannelHandlerContext, String paramString, ByteBuf paramByteBuf)
{
byte[] arrayOfByte = paramString.getBytes("UTF-8");
for (int i = 0; i < arrayOfByte.length; i++)
arrayOfByte[i] = ((byte)(0xA ^ arrayOfByte[i]));
paramByteBuf.writeBytes(arrayOfByte);
}
//com.bootstrap.xy2.forward.bootstrap.a:
public void a(ChannelHandlerContext paramChannelHandlerContext, String paramString)
{
try
{
if ("buibuikill".equals(paramString))
{
com.bootstrap.xy2.forward.c.a.c();
com.bootstrap.xy2.forward.e.a.a(true);
}
else
{
com.bootstrap.xy2.forward.c.a.a();
}
ReferenceCountUtil.release(paramString);
}
finally
{
ReferenceCountUtil.release(paramString);
}
}
客户端代码改造
由于代码受到了混淆,所以一般的修改方法已经失效,需要使用 "jclasslib bytecode viewer" 查看其字节码,软件可以在百度下载最新版
修改方法可以参照:通过修改字节修改方法体
主要修改授权服务器的ip.
基本上是使用字节码修改的方式按照位数修改相关字符串.改变其连接的IP地址
授权服务器实现
主要是实现思路
- 启动socket服务
- 设置解码器
- 设置加密器
添加消息处理方法
实现说明
设置解码加密器
public class XY2Decoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() < 4) return; byte[] arrayOfByte = new byte[in.readableBytes()]; in.readBytes(arrayOfByte); for (int i = 0; i < arrayOfByte.length; i++) arrayOfByte[i] = ((byte)(0xA ^ arrayOfByte[i])); out.add(new String(arrayOfByte, "UTF-8")); } } public class XY2Encoder extends MessageToByteEncoder<String> { @Override protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception { byte[] arrayOfByte = msg.getBytes("UTF-8"); for (int i = 0; i < arrayOfByte.length; i++) arrayOfByte[i] = ((byte)(0xF ^ arrayOfByte[i])); out.writeBytes(arrayOfByte); } }
设置消息处理器
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress(); String clientIp = insocket.getAddress().getHostAddress(); System.out.println("授权字段:="+msg+"clientIp="+clientIp); //ctx.writeAndFlush("biubiukill"); IXy2ServerService xy2ServerService = SpringUtils.getBean(IXy2ServerService.class); Xy2Server server = xy2ServerService.selectXy2ServerByIp(clientIp); if(server!=null) { ctx.writeAndFlush("buibuirun"); }else { ctx.writeAndFlush("buibuikill"); } }
配置并启动
protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast( new XY2Encoder()); pipeline.addLast( new XY2Decoder()); pipeline.addLast( new ServerHandler(ch)); } @Component public class UJServer { private static int portNumber = 7725; private ChannelFuture serverChannel; private EventLoopGroup bossGroup; private NioEventLoopGroup workerGroup ; @PostConstruct public void serviceStart() { bossGroup = new NioEventLoopGroup(); workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup); b.channel(NioServerSocketChannel.class); b.childHandler(new ServerInitializer()); serverChannel = b.bind(portNumber); System.out.println("服务器启动..."); System.out.println("监听端口: " + portNumber); }