5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Bukkitで送受信されているパケットにプラグインで割り込む

Posted at

概要

本解説は1.7.x系を前提としています。
Bukkitで送受信されているパケットにプラグインで割り込み、改ざんします。
このプラグインにはBukkitAPIの他にnet.minecraftパッケージを必要とするので、
CraftBukkitをビルドパスに入れてください。
mavenを使用している場合は下記のように書き換えてください

pom.xml
<dependencies>
    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.7.2-R0.4-SNAPSHOT</version>
    </dependency>
</dependencies>

割り込み

Nettyを使用してpacket_handlerに割り込みます。
それにあたって3つのクラスを作成します。

NettyInjecter.java
public class NettyInjector implements Listener {

    private Plugin plugin;

    HashMap<PlayerChannelHandler, Player> connection = new HashMap<>();

    private Class EntityPlayer;
    private Field EntityPlayer_playerConnection;

    private Class PlayerConnection;
    private Field PlayerConnection_networkManager;

    private Class NetworkManager;
    private Field NetworkManager_K;
    private Field NetworkManager_M;

    public NettyInjector(Plugin plugin){
        this.plugin = plugin;
        Bukkit.getServer().getPluginManager().registerEvents(this, plugin);

        try {

            EntityPlayer = Reflection.getCraftClass("EntityPlayer");
            EntityPlayer_playerConnection = Reflection.getField(EntityPlayer, "playerConnection");

            PlayerConnection = Reflection.getCraftClass("PlayerConnection");
            PlayerConnection_networkManager = Reflection.getField(PlayerConnection, "networkManager");

            NetworkManager = Reflection.getCraftClass("NetworkManager");
            NetworkManager_K = Reflection.getField(NetworkManager, "k");
            NetworkManager_M = Reflection.getField(NetworkManager, "m");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void inject(Player player) throws Exception{
        Object entityPlayer = Reflection.getEntityPlayer(player);
        Object networkManager = getNetworkManager(entityPlayer);
        Channel channel = getChannel(networkManager);
        PlayerChannelHandler pch = new PlayerChannelHandler();
        if(channel.pipeline().get(PlayerChannelHandler.class) == null){
            channel.pipeline().addBefore("packet_handler", "YourPluginName", pch);
            connection.put(pch, player);
        }
    }

    public void remove(final Player player) throws Exception{
        Object entityPlayer = Reflection.getEntityPlayer(player);
        Object networkManager = getNetworkManager(entityPlayer);
        final Channel channel = getChannel(networkManager);
        if(channel.pipeline().get(PlayerChannelHandler.class) != null){
            channel.pipeline().remove(PlayerChannelHandler.class);
            for (Map.Entry<PlayerChannelHandler, Player> es : connection.entrySet()) {
                if (es.getValue().equals(player)) {
                    connection.remove(es.getKey());
                }
            }
        }
    }

    @EventHandler
    public void onPluginDisable(PluginDisableEvent e) throws Exception{
        if(e.getPlugin().equals(plugin)){
            for(Player player: Bukkit.getOnlinePlayers()) {
                remove(player);
            }
        }
    }

    private Object getNetworkManager(Object entityPlayer) {
        Object pc = Reflection.getFieldValue(EntityPlayer_playerConnection, entityPlayer);
        return Reflection.getFieldValue(PlayerConnection_networkManager, pc);
    }

    private Channel getChannel(Object networkManager) {
        Channel ch = null;
        try {
            ch = Reflection.getFieldValue(NetworkManager_K, networkManager);
        } catch (Exception e) {
            ch = Reflection.getFieldValue(NetworkManager_M, networkManager);
        }
        return ch;
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent e) throws Exception{
        inject(e.getPlayer());
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent e) throws Exception{
        remove(e.getPlayer());
    }

    @EventHandler
    public void onPluginEnable(PluginEnableEvent e) throws Exception{
        if(e.getPlugin().equals(plugin)){
            for(Player player: Bukkit.getOnlinePlayers()){
                inject(player);
            }
        }
    }
}
Reflection.java
public class Reflection {

    public static String getPackageName(){
        String packagename = "net.minecraft.server."+getCraftBukkitVersion();
        return packagename;
    }

    public static String getCraftBukkitVersion(){
        String version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
        return version;
    }

    public static Class<?> getCraftClass(String s) throws Exception {
        Class<?> craftclass = Class.forName(getPackageName()+"."+s);
        return craftclass;
    }

    public static Object getEntityPlayer(Player p) throws Exception{
        Method getHandle = p.getClass().getMethod("getHandle");
        return getHandle.invoke(p);
    }

    public static Object getFieldValue(Object instance, String fieldName) throws Exception {
        Field field = instance.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(instance);
    }

    @SuppressWarnings("unchecked")
    public static  <T> T getFieldValue(Field field, Object obj) {
        try {
            return (T) field.get(obj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Field getField(Class<?> clazz, String fieldName) throws Exception {
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field;
    }

    public static void setFieldValue(Field field, Object obj, Object value) {
        try {
            field.set(obj, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
PlayerChannelHandler.java
public class PlayerChannelHandler extends ChannelDuplexHandler {

    /**
     * クライアントからパケットを受信したとき
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg.getClass().getSimpleName().equalsIgnoreCase("PacketPlayInTabComplete")){
           String s = (String)Reflection.getFieldValue(msg, "a");
            if(!(s.startsWith("/ver") || s.startsWith("/version"))){
                super.channelRead(ctx, msg);
            }
        }else{
            super.channelRead(ctx, msg);
        }
    }

    /**
     * サーバーからクライアントへパケットを送信するとき
     * @param ctx
     * @param msg
     * @param promise
     * @throws Exception
     */
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        super.write(ctx, msg, promise);
    }
}

3つのクラスを作成し終わったら、最後にonEnableに下記のように記述します

onEnable
@Override
public void onEnable() {
    new NettyInjector(this);
}

この記事の例ではPacketPlayInTabCompleteというタブ補完を行うときにサーバーに送られるパケットが**"/ver"または、"/version"**だった場合パケットを破棄するという処理を行っています。

どのパケットが何をしているかは下記をURL見てください。
https://github.com/Bukkit/mc-dev/tree/master/net/minecraft/server
http://wiki.vg/Protocol

下記の画像の状態でTABキーを押してもプラグインリストが出なければ成功です。
alt

参考

https://github.com/Bukkit/mc-dev/blob/master/net/minecraft/server/PacketPlayInTabComplete.java
http://wiki.vg/Protocol#Tab-Complete

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?