相关技术点

  • 混淆后java字节码修改
  • 基于netty的socket字节码处理

概述

该游戏的授权机制大致如下:
1:客户端与授权服务器建立socket链接
2:启动时客户端与服务器建立链接,发送任意字符串
3:服务端接收到信息后,或缺客户端的IP
4:服务端检测IP是否可用
5:验证完成后发送验证结果返回给客户端
6:客户端根据授权情况来确定是否启动,完成授权验证c

客户端分析

代码解读

客户端是基于springboot实现,关键代码使用了代码混淆方式,所以反编译后的代码会有正常的java不一致.但只是对变量以及一些实现做了变种,整体规则还是符合jvm的规范(这点比较重要,要是使用了代码加密,配套特殊的classloader,分析代码的难度会增加不少).
将代码导出source后,导入eclipse,构建简单的java项目,用于分析后续代码.
经分析与授权相关的关键代码如下:

  1. 类:com.bootstrap.xy2.forward.bootstrap.a 用于配置链接授权服务器的地址,后续需要变更此处
  2. 启动逻辑:
    a:启动时读取预配置项
    b:连接授权服务器,获取授权情况,
    c:如果授权通过则读取client.properties配置项
    d:如果授权不通过,则清空相关client.properties配置项
  3. 关键代码
//连通授权服务器
//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地址

授权服务器实现

主要是实现思路

  1. 启动socket服务
  2. 设置解码器
  3. 设置加密器
  4. 添加消息处理方法

实现说明

  1. 设置解码加密器
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);
    }
}
  1. 设置消息处理器
 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");
          }  
    }
  1. 配置并启动
 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);
    }
Last modification:April 23, 2020
If you think my article is useful to you, please feel free to appreciate