MaimMim Man in the middle minetest interceptor

- prototype
- between client and server, allow to capture all exchanges and potentially change them
- created to capture server maps in laby
- first test get only MapBlock, support version serialization version 28
  - prepartion for 29 with zstd but untested.

# Conflicts:
#	fetch_dependencies.sh
This commit is contained in:
philippe lhardy
2023-07-30 10:18:45 +02:00
parent d789870fbc
commit 9792b79c56
29 changed files with 1144 additions and 12 deletions

View File

@@ -0,0 +1,4 @@
package org.artisanlogiciel.games.minetest.net;
public class Address {
}

View File

@@ -0,0 +1,28 @@
package org.artisanlogiciel.games.minetest.net;
import org.artisanlogiciel.games.minetest.core.PacketException;
import org.artisanlogiciel.games.minetest.core.Serialize;
public class BufferedPacket {
public static int BASE_HEADER_SIZE = 7;
// Data of the packet, including headers
byte[] m_data;
BufferedPacket(byte[] data)
{
m_data = data;
}
int getSeqNum()
throws PacketException
{
return Serialize.readU16(m_data, BASE_HEADER_SIZE + 1, size());
}
int size()
{
return m_data.length;
}
}

View File

@@ -0,0 +1,9 @@
package org.artisanlogiciel.games.minetest.net;
public class Channel {
int readNextIncomingSeqNum()
{
return 0;
}
}

View File

@@ -0,0 +1,45 @@
package org.artisanlogiciel.games.minetest.net;
public class ConnectionEvent {
ConnectionEventType m_event_type;
int m_peer_id;
byte[] m_data;
boolean m_timeout;
ConnectionEvent(ConnectionEventType eventType)
{
m_event_type = eventType;
}
String describe()
{
return m_event_type.toString();
}
ConnectionEventPtr create(ConnectionEventType type)
{
return null;
}
ConnectionEventPtr dataReceived(int peer_id, byte[] data)
{
return null;
}
ConnectionEventPtr peerAdded(int peer_id, Address address)
{
return null;
}
ConnectionEventPtr peerRemoved(int peer_id, boolean is_timeout, Address address)
{
return null;
}
ConnectionEventPtr bindFailed()
{
return null;
}
}

View File

@@ -0,0 +1,4 @@
package org.artisanlogiciel.games.minetest.net;
public class ConnectionEventPtr {
}

View File

@@ -0,0 +1,9 @@
package org.artisanlogiciel.games.minetest.net;
public enum ConnectionEventType {
CONNEVENT_NONE,
CONNEVENT_DATA_RECEIVED,
CONNEVENT_PEER_ADDED,
CONNEVENT_PEER_REMOVED,
CONNEVENT_BIND_FAILED
}

View File

@@ -0,0 +1,90 @@
package org.artisanlogiciel.games.minetest.net;
import org.artisanlogiciel.games.minetest.core.PacketException;
import org.artisanlogiciel.games.minetest.core.Serialize;
import java.util.ArrayList;
public class IncomingSplitBuffer {
int m_seqnum;
int m_chunk_count;
int m_chunk_num;
// number of chunks
int m_got = 0;
NetworkPacket m_list[] = null;
BufferedPacket m_reassembled = null;
BufferedPacket insert(NetworkPacket networkPacket)
throws PacketException
{
byte[] buffer = networkPacket.getBuffer();
int length = networkPacket.getLength();
int offset = networkPacket.getOffset();
int type = Serialize.readU8(buffer , offset, length);
int seqnum = Serialize.readU16(buffer, offset + 1, length);
// total number of chunk
int chunk_count = Serialize.readU16(buffer, offset + 3, length);
// this chunk number
int chunk_num = Serialize.readU16(buffer, offset+ 5, length);
System.out.println("Split length " + length + " type " + type + " seqnum " + seqnum + " chunk_num/chunk_count " + chunk_num + "/" + chunk_count );
// move to next header
networkPacket.addOffset(7);
if (m_reassembled != null)
{
return m_reassembled;
}
if (m_list == null )
{
m_list = new NetworkPacket[chunk_count];
}
if ( chunk_num < chunk_count && (m_list[chunk_num] == null) )
{
m_list[chunk_num] = networkPacket;
m_got ++;
}
//todo seqnum
m_seqnum = seqnum;
// fully obtained
if ( m_got == chunk_count )
{
reassemble();
}
return m_reassembled;
}
private void reassemble()
{
// keep first header since result is a valid BufferedPacket
int fullLength = BufferedPacket.BASE_HEADER_SIZE;
for (NetworkPacket networkPacket : m_list )
{
fullLength += networkPacket.getLength() - networkPacket.getOffset();
}
byte[] reassembled = new byte[fullLength];
NetworkPacket first = m_list[0];
System.arraycopy(first.getBuffer(),0,reassembled,0,BufferedPacket.BASE_HEADER_SIZE);
int offset = BufferedPacket.BASE_HEADER_SIZE;
for (NetworkPacket networkPacket : m_list )
{
int dataLength = networkPacket.getLength() - networkPacket.getOffset();
System.arraycopy(networkPacket.getBuffer(), networkPacket.getOffset(),reassembled,offset,dataLength);
offset+=dataLength;
}
m_reassembled = new BufferedPacket(reassembled);
}
}

View File

@@ -0,0 +1,127 @@
package org.artisanlogiciel.games.minetest.net;
import org.artisanlogiciel.games.minetest.core.PacketException;
import java.io.IOException;
import java.net.*;
/**
* Man in the middle UDP
* very simple for one client only
*/
public class MiM {
DatagramSocket serverSocket;
boolean running = false;
InetSocketAddress serverAddress;
// will be captured, incoming address & port
SocketAddress fromClient = null;
int localPort;
PacketHandler handler = null;
public MiM(int myPort, InetSocketAddress remoteAddress)
{
localPort = myPort;
serverAddress = remoteAddress;
}
public void launch()
{
// ServerSocket socket = new ServerSocket()
try {
handler = new PacketHandler();
serverSocket = new DatagramSocket(localPort);
DatagramSocket in = serverSocket;
SocketAddress fromServer = serverAddress;
Thread toServer = new Thread( () -> {runFromToServer(in,fromServer);});
running = true;
toServer.start();
}
catch(Exception e)
{
System.out.println("problem");
e.printStackTrace(System.err);
running = false;
}
}
public void stop()
{
running = false;
}
public void runFromToServer(DatagramSocket in, SocketAddress fromServer) {
try {
while (running) {
// quick way, a new buffer at each reception
// to handle split packets that are buffered
byte[] buf = new byte[4096];
SocketAddress toRemote = null;
DatagramPacket packet = new DatagramPacket(buf, buf.length);
in.receive(packet);
SocketAddress from = packet.getSocketAddress();
if ( from.equals(fromServer)) {
// no client yet
if ( fromClient == null )
{
continue;
}
fromServer(buf,packet.getLength());
toRemote = fromClient;
}
else
{
// record client
// later on to be smart : could try to record peer id ?
// will add a constraint : only one auth at a time
if ( fromClient == null ) {
fromClient = from;
}
fromClient(buf,packet.getLength());
toRemote = fromServer;
}
packet = new DatagramPacket(buf, packet.getLength(), toRemote);
in.send(packet);
}
}
catch( IOException ioException)
{
//
System.out.println("oops");
}
// socket.close();
}
void fromServer(byte[] buffer, int length)
{
try {
// reply from server
if (handler != null) {
handler.fromServer(buffer, length);
}
}
catch (PacketException packetException)
{
//
}
}
void fromClient(byte[] buffer, int length)
{
try {
// reply from client
if (handler != null) {
handler.fromClient(buffer, length);
}
}
catch (PacketException packetException)
{
//
}
}
}

View File

@@ -0,0 +1,70 @@
package org.artisanlogiciel.games.minetest.net;
import org.artisanlogiciel.games.minetest.core.PacketException;
import org.artisanlogiciel.games.minetest.core.Serialize;
import org.artisanlogiciel.games.minetest.core.v3s16;
import java.nio.ByteBuffer;
public class NetworkPacket {
int m_protocol_id;
int m_peer_id;
int m_channel;
public PacketType packetType;
// somehow a BufferedPacket ...
byte[] m_buffer; // m_data
// used part in buffer
int m_length; // m_datasize ?
// current header index in packet
int m_offset; // m_read_offset
short m_command = 0;
public NetworkPacket(int protocol_id, int peer_id, int channel, PacketType type, byte[] buffer, int length) {
this.m_protocol_id = protocol_id;
this.m_peer_id = peer_id;
this.m_channel = channel;
this.packetType = type;
m_buffer = buffer;
m_length = length;
m_offset = 0;
}
public int getChannel()
{
return m_channel;
}
public byte[] getBuffer()
{
return m_buffer;
}
public int getOffset() {
return m_offset;
}
public int getLength() {
return m_length;
}
void addOffset(int offset)
{
m_offset += offset;
}
// Serialization
public v3s16 v3s16()
throws PacketException
{
return Serialize.readV3S16(m_buffer, m_offset, m_length);
}
public ByteBuffer getByteBuffer()
{
ByteBuffer buffer = ByteBuffer.wrap(m_buffer,m_offset,m_length-m_offset);
return buffer;
}
}

View File

@@ -0,0 +1,287 @@
package org.artisanlogiciel.games.minetest.net;
import com.github.luben.zstd.Zstd;
import org.artisanlogiciel.games.minetest.MapBlock;
import org.artisanlogiciel.games.minetest.MapNode;
import org.artisanlogiciel.games.minetest.core.PacketException;
import org.artisanlogiciel.games.minetest.core.Serialize;
import org.artisanlogiciel.games.minetest.core.v2s16;
import org.artisanlogiciel.games.minetest.core.v3s16;
import java.nio.ByteBuffer;
/**
* see src/network/networkprotocol.h of minetest sources
*
*
* === NOTES ===
*
* A packet is sent through a channel to a peer with a basic header:
* Header (7 bytes):
* [0] u32 protocol_id
* [4] session_t sender_peer_id
* [6] u8 channel
* sender_peer_id:
* Unique to each peer.
* value 0 (PEER_ID_INEXISTENT) is reserved for making new connections
* value 1 (PEER_ID_SERVER) is reserved for server
* these constants are defined in constants.h
* channel:
* Channel numbers have no intrinsic meaning. Currently only 0, 1, 2 exist.
*
*
* */
public class PacketHandler {
ReliablePacketBuffer reliableBuffer = null;
IncomingSplitBuffer[] incomingChanneleSplitBuffer = new IncomingSplitBuffer[4];
// server serialization in hello version
int ser_version;
// minetest protocol should be 0x4f457403
static int readProtocolId(byte[] buffer, int length)
throws PacketException
{
return Serialize.readU32(buffer, 0, length);
}
static int readPeerId(byte[] buffer, int length)
throws PacketException
{
return Serialize.readU16(buffer,4,length);
}
static int readChannel(byte[] buffer, int length)
throws PacketException
{
return Serialize.readU8(buffer, 6, length);
}
NetworkPacket handleAny(byte[] buffer, int length)
throws PacketException
{
int protocol_id = readProtocolId(buffer, length);
int peer_id = readPeerId(buffer, length);
int channel = readChannel(buffer, length);
int type = Serialize.readU8(buffer,7,length);
PacketType packetType = PacketType.getPacketType(type);
NetworkPacket networkPacket = new NetworkPacket(protocol_id,peer_id,channel,packetType, buffer, length);
// offset of packet within buffer.
networkPacket.addOffset(BufferedPacket.BASE_HEADER_SIZE);
System.out.println("length " + length + " protocol_id " + String.format("%x", protocol_id) + " peer_id " + peer_id + " channel " + channel + " type " + packetType);
return networkPacket;
}
void handlePacketType_Control(NetworkPacket networkPacket)
throws PacketException {
}
void handlePacketType_Reliable(NetworkPacket networkPacket)
throws PacketException
{
// TODO handle misordered...
/*
if ( reliableBuffer == null )
{
reliableBuffer = new ReliablePacketBuffer();
}
BufferedPacket packet = new BufferedPacket(buffer);
int nextExpected = 0;
reliableBuffer.insert(packet, nextExpected);
*/
// handle nested packet
networkPacket.addOffset(3);
int offset = networkPacket.getOffset();
byte buffer[] = networkPacket.getBuffer();
int length = networkPacket.getLength();
PacketType packetType = PacketType.getPacketType(Serialize.readU8(buffer,offset,length));
System.out.println( "reliable " + packetType.toString());
switch (packetType)
{
case PACKET_TYPE_CONTROL:
//
handlePacketType_Control(networkPacket);
break;
case PACKET_TYPE_ORIGINAL:
handlePacketType_Original(networkPacket);
break;
case PACKET_TYPE_SPLIT:
handlePacketType_Split(networkPacket);
break;
case PACKET_TYPE_RELIABLE:
// this is an error, no nested reliable accepted.
throw new PacketException("nested reliable");
default: // error
throw new PacketException("unknown type");
}
}
void handleCommand_Hello(NetworkPacket networkPacket)
throws PacketException {
// u8 deployed serialisation version
ByteBuffer buffer = networkPacket.getByteBuffer();
int ser_ver = Serialize.readU8(buffer);
ser_version = ser_ver;
// u16 deployed network compression mode
int net_compress = Serialize.readU16(buffer);
// u16 deployed protocol version
int deployed = Serialize.readU16(buffer);
System.out.println(String.format("HELLO ser_ver=%d net_compress %d deployed %d", ser_ver,net_compress,deployed));
// u32 supported auth methods
// std::string username that should be used for legacy hash (for proper casing)
}
// clientpackethandler.cpp Client::handleCommand_BlockData(NetworkPacket* pkt)
// TOCLIENT_BLOCKDATA
void handleCommand_BlockData(NetworkPacket networkPacket)
throws PacketException
{
v3s16 p = networkPacket.v3s16();
// will be used to get vector...
// v2s16 p2d = new v2s16(p.X,p.Z);
System.out.println(String.format(" (X,Y,Z) %d %d %d", p.X, p.Y,p.Z));
//
networkPacket.addOffset(6);
// FIXME get it from handshake
int version = 28;
// Should be a MapBlock
MapBlock block = new MapBlock();
block.deSerialize(networkPacket.getByteBuffer(),version);
// now we have a block ! what to do with it ?
}
void handlePacketCommand(NetworkPacket networkPacket)
throws PacketException {
// consume type and ??? well ... 2
int command = Serialize.readU16(networkPacket.getBuffer(),networkPacket.getOffset(),networkPacket.getLength());
System.out.println(String.format("command %x length %d", command, networkPacket.getLength()));
networkPacket.addOffset(2);
// Original ... toClient - toServer
switch (command)
{
case 0x02:
{
System.out.println("TOCLIENT_HELLO");
handleCommand_Hello(networkPacket);
}
break;
case 0x20:
{
System.out.println("TOCLIENT_BLOCKDATA");
handleCommand_BlockData(networkPacket);
}
break;
case 0x4f:
{
//TOCLIENT_SET_SKY = 0x4f,
System.out.println("TOCLIENT_SET_SKY");
}
break;
}
}
void handlePacketType_Original(NetworkPacket networkPacket)
throws PacketException {
// eat original type 1.
networkPacket.addOffset(1);
handlePacketCommand(networkPacket);
}
void handlePacketType_Split(NetworkPacket networkPacket)
throws PacketException {
int channel = networkPacket.getChannel();
if ( channel < incomingChanneleSplitBuffer.length) {
IncomingSplitBuffer splitBuffer = incomingChanneleSplitBuffer[channel];
if ( splitBuffer == null )
{
splitBuffer = new IncomingSplitBuffer();
incomingChanneleSplitBuffer[channel] = splitBuffer;
}
if ( splitBuffer != null ) {
BufferedPacket bufferedPacket = splitBuffer.insert(networkPacket);
if ( bufferedPacket != null ) {
// well should handle it.
System.out.println("Reassembled packet size " + bufferedPacket.m_data.length);
// reset it.
incomingChanneleSplitBuffer[channel] = new IncomingSplitBuffer();
handleBufferedPacket(bufferedPacket);
}
}
}
else {
throw new PacketException("invalid channel " + channel);
}
}
void handleBufferedPacket(BufferedPacket bufferedPacket)
throws PacketException
{
// FIXME, why a BufferedPacket anyway since we finaly need a NetworkPacket ?
NetworkPacket networkPacket = new NetworkPacket(0,0,0, PacketType.PACKET_TYPE_ORIGINAL,bufferedPacket.m_data, bufferedPacket.size());
networkPacket.addOffset(BufferedPacket.BASE_HEADER_SIZE);
handlePacketCommand(networkPacket);
}
void fromServer(byte[] buffer, int length)
throws PacketException
{
System.out.print(" <- ");
NetworkPacket networkPacket = handleAny(buffer,length);
switch (networkPacket.packetType)
{
case PACKET_TYPE_CONTROL:
//
handlePacketType_Control(networkPacket);
break;
case PACKET_TYPE_RELIABLE:
//
handlePacketType_Reliable(networkPacket);
break;
case PACKET_TYPE_ORIGINAL:
handlePacketType_Original(networkPacket);
break;
case PACKET_TYPE_SPLIT:
handlePacketType_Split(networkPacket);
break;
default: // error
throw new PacketException("unknown type");
}
}
void fromClient(byte[] buffer, int length)
throws PacketException
{
System.out.print(" -> ");
handleAny(buffer,length);
}
}

View File

@@ -0,0 +1,27 @@
package org.artisanlogiciel.games.minetest.net;
public enum PacketType {
PACKET_TYPE_CONTROL,
PACKET_TYPE_ORIGINAL,
PACKET_TYPE_SPLIT,
PACKET_TYPE_RELIABLE,
PACKET_TYPE_ERROR;
static PacketType getPacketType(int value)
{
switch (value)
{
case 0:
return PACKET_TYPE_CONTROL;
case 1:
return PACKET_TYPE_ORIGINAL;
case 2:
return PACKET_TYPE_SPLIT;
case 3:
return PACKET_TYPE_RELIABLE;
default:
return PACKET_TYPE_ERROR;
}
}
}

View File

@@ -0,0 +1,20 @@
package org.artisanlogiciel.games.minetest.net;
import org.artisanlogiciel.games.minetest.core.PacketException;
import java.util.LinkedList;
import java.util.List;
public class ReliablePacketBuffer {
List<BufferedPacket> m_list = new LinkedList<>();
void insert(BufferedPacket packet, int nextExpected)
throws PacketException
{
//
int seqNum = packet.getSeqNum();
m_list.add(packet);
}
}