< Back

BlockStalk: My Minecraft Server Scanning Experience

28/09/2025

A lot of people ask me, how did you get into programming and hacking? And, while that's a story for another day, scanning for minecraft servers has definitely played a huge role in it. The fact that I'm writing this post in neovim right now, all those fiascos that yall know I love to pull, was all pretty much because I got into minecraft hacking and minecraft server scanning. In this post, I also talk about how I made my own minecraft server scanner called BlockStalk™.

Personal Lore

I've divided this up into stages since there's some pretty big time gaps between a lot of this stuff.

Early

I had always been into minecraft hacking, griefing, etc. Not quite who I am now, but I honestly don't really mind griefing. I was always hanging around in those sort of communities and I heard about server scanning sometime in 2021 or earlier. That shit went hella mainstream back when fitmc made a video about it.

In fact, ever heard of mcstalker? I was unknowingly friends with the lead python developer. Only found out later that he was associated with it. For some time I had even joined the Ether project and was helping out until I had a crashout and left. To be honest that was all during the pandemic so I don't have much of a recollection of what happened. I've just spent hours going through old messages lol.

LiveOverflow

ahhhh yes... the liveoverflow SMP era was soooo much fun for me. I just wish I was there for the whole thing.

To those who don't know, the youtuber LiveOverflow started a minecraft SMP a while back. Except, it wasn't really a normal SMP... and it wasn't even intended to be an SMP. Some old friends from the server scanning community ended up stumbling upon his server, though, and it ended with him locked up inside a prison on his own server.

What was my role? Well... this is where I believe I could have done better. At the time, I didn't actually have any resources (as in network architecture stuff like a VPS) to scan for servers. I programmed my own implementation of the minecraft server list ping protocol from the ground-up in java, but when it came to scanning, I realised that... it's impossible for me to do this, at least with my WiFi speed of 0.5 bytes per second (XD) and the chance of my ISP coming into my house guns 'ablowin and taking my dumbass for ransom.

So what did I do? I tried a few things

  • Attempt 1: Spamming a bunch of people for the server IP. Outcome: my requests were denied
  • Attempt 2: Build my own server scanner. Outcome: It was slow as hell and ran on my own network
  • Attempt 3: I skidded... sorta. I just went on shodan and searched for the MOTD of the SMP. Outcome: BINGO!! 🎉🎉
     

Here's a picture that gives me nostalgia:
Me seething and coping

As yall can tell, I wasn't the most chatty person.

Okay, for real, though. We're gonna talk more about why I kept "leaving and rejoining" later. Actuallyyyy... let's do it now. So, when I first started playing, there was a plugin that made it so that you had to be walking in a straight line at all times to make sure that you're "not a human". I managed to create a cheeky bypass to this without coding anything. Yet, sadly, I couldn't make it work with flight and the spawnpoint was some stupid ditch, so I was stuck. More on my great escape later.

'Till then, all I really have to say is that the situation actually stayed like this for some time. I moved on and didn't really partcipate as much as I should have.

Present Day

A while back, I got an invite to a server that pretty much refilled my nostalgia bucket. It wasn't what it used to be. I wasn't in the server earlier, but I knew a lot of the people from it beforehand. It was sad to see a community that I used to participate in quite heavily so degraded, but I guess they brought it on themselves. A large part of the original members were the leading contributers to the problem anyways. It's not really that bad, per se, but definitely can't live up to its former glory. The people who are there now are still as awesome as they used to be, but the community seems to have diverged from its original role, not that that's bad.

Well, I wanted to find the server again, so I did. But... it wasn't that simple. After hours of searching and finding the original SMP IP, I got the shocking Server Full. That's when I thought back to a conversation with someone, them talking about building an "artificial whitelist" for the server. That's when I did some research and found out about the N00bbot proxy. I'm too lazy to explain so here's a diagram of how it works:

N00bbot proxy diagram

So basically, I needed to find the IP for the proxy server too if I wanted to join again. This one actually took a bit of effort because of my process:

  • I knew the server was hosted on hetzener so I had an IP list prepared
  • I coded a really bad temporary scanner in python and scanned the list
  • I extracted all the favicons and looked through all of them individually
  • I found a favicon that looked like the liveoverflow one
  • This was the liveoverflow server
  • I found another
  • Bam! N00bbot proxy acquired

At this point, I had come so far that I decided that it was time to sort of put an end to all this. I had been meaning to make a server scanner for a while, and this seemed like the best time to do it. Ergo, blockstalk™.

Technical

How do they work?

This part is trivially simple. Minecraft has its own protocol. Most minecraft servers run on port 25565. To get server info, we use a protocol called the server list ping, which I will refer to and is abbreviated as SLP. Let's talk a bit about that right now.

Here's a rough outline of the process.
 

  • The Handshake
    • Client sends a Handshake packet with next state = 1 (i.e. status).
    • Fields include: protocol version (VarInt), server address (string), server port (Unsigned Short), and next state (VarInt)
       
  • The Status Request
    • The client responds with a Status Request packet (packet ID 0x00 in the status state)
       
  • The Status Response
    • Server replies with a "Response" packet (packet ID 0x00 in status state) containing a JSON string.
    • The JSON object includes:
      • version: name & protocol number
      • players: max, online, optionally a sample list (some player names + UUIDs)
      • description: the server’s MOTD / message
      • Optional extra fields like favicon, etc. in newer versions.

 

  • Then there's a boring ass latency test that goes down that nobody really cares about

 
Now, let's get into the real fun.

To the newbies here who haven't scanned the internet before, you can think of it as an nmap scan where the CIDR range that you input is literally just 0.0.0.0/0. Yep, everything :P. We use a blacklist though, of course. Imagine what would happen if we accidentally scanned the US DoD 😵‍💫

Making it

So I wanted to make a server scanner. Credit where credit is due, mat had really great advice on this.

“I have not failed. I’ve just found 10,000 ways that won’t work.” - Thomas Edison

And, at first, I did. I found 10,000 ways that just won’t work.

I started out with rustscan since they have a python scripting engine. I made a quick PR for the main change I needed (it got closed and then pulled since I made the changes on a broken commit)

Rustscan Commit

To my dismay, it was pointed out that "all 65k ports in 3 seconds" is quite slow. Sooooo... we scrap that.

Now, I have a few options:

  • fully rely on masscan to do minecraft pinging (and maybe tweak the code a little)
  • use masscan (or a similar program to it like zmap) only for getting ips, then do minecraft pings from your own program. this is what most people do
  • rewrite masscan from scratch (involves learning low level networking and reimplementing tcp)
  • tweak masscan and make it better by implementing things like xdp (involves learning weird linux apis)

Evaluating the options

Let's go over each one individually. This part is important, so pay attention.

  1. fully rely on masscan:
    masscan recently implemented their own version of the minecraft server list ping. I went through the code and understood how it worked, since they don't have much documentation on it. I got it working and then decided... nah I'm too cool for that

  2. use masscan and make your own implementation of SLP:
    This is self-explanatory. we'll write this one down

  3. rewrite masscan from scratch:
    Essentially, masscan uses their own TCP stack that scans as fast as possible, but it is, in no way, perfect. As far as I know, masscan uses the standard socket API with raw sockets, which aren't efficient enough since they make wayy too many syscalls. To go as fast as possible, they have support for PF_RING, a kernel driver. However, it doesn't work on many VPSes, it needs access to network hardware that most VPS providers don't give. This is why I could try to rewrite masscan from scratch using an API like AF_XDP.

  4. tweak the code of masscan:
    I would do this to make it support XDP. This requires a really good understanding of C, which my egoistic self thinks I have. I tried this and then got bored and quit.

Let's Code!

I ended up choosing option 2 because I like to think I have a life.

So, I made blockstalk. It takes a masscan JSON file and uses python as a sort of puppeteer to control a golang program that does the actual scanning using my own implementation of the minecraft server list ping.

This is pretty much the core of the code, which is the first handshake:

// First, we define some helper functions
func WriteVarInt(buf *bytes.Buffer, value int) {
	for {
		temp := byte(value & 0x7F)
		value >>= 7
		if value != 0 {
			temp |= 0x80
		}
		buf.WriteByte(temp)
		if value == 0 {
			break
		}
	}
}

func WriteString(buf *bytes.Buffer, s string) {
	WriteVarInt(buf, len(s))
	buf.WriteString(s)
}

// Then, send the handshake
var handshake bytes.Buffer
WriteVarInt(&handshake, 759) // Protocol version
WriteString(&handshake, address)
binary.Write(&handshake, binary.BigEndian, uint16(port))
WriteVarInt(&handshake, 1) // Status / Next state
if err := SendPacket(conn, 0x00, handshake.Bytes()); err != nil {
    return nil, 0, err
}

if err := SendPacket(conn, 0x00, []byte{}); err != nil {
    return nil, 0, err
}

Then, we read the response.

// One more helper function to read that sweet sweet VarInt
func ReadVarIntFromBuffer(buf *bytes.Buffer) (int, error) {
	var value, position int
	for {
		if position > 35 {
			return 0, fmt.Errorf("this VarInt is too big (size does matter)")
		}
		b, err := buf.ReadByte()
		if err != nil {
			return 0, err
		}
		value |= int(b&0x7F) << position
		if (b & 0x80) == 0 {
			break
		}
		position += 7
	}
	return value, nil
}

jsonLenBuf := bytes.NewBuffer(data)
jsonLength, err := ReadVarIntFromBuffer(jsonLenBuf)
if err != nil {
    return nil, 0, err
}
jsonData := make([]byte, jsonLength)
if _, err := io.ReadFull(jsonLenBuf, jsonData); err != nil {
    return nil, 0, err
}

var status ServerStatus
if err := json.Unmarshal(jsonData, &status); err != nil {
    return nil, 0, fmt.Errorf("invalid JSON: %w", err)
}

Bonus: Robowalk

As I mentioned previously, the liveoverflow server has a plugin that checks if you're moving like a normal player. If you're not, it kicks you from the game instantly. We need to bypass this. I have seen this referred to as "robowalk". I programmed this is a fabric mixin, which is like extra code that you inject into the game.

For coding robowalk, I use this smoothing function on my player coordinates:

private double smooth(double d) {
    double temp = (double) Math.round(d * 100) / 100;
    return Math.nextAfter(temp, temp + Math.signum(d));
}

To actually access them, though, we use something called accessors. (Thank you for telling me this, 0skie. Super helpful :P)
They're pretty simple and the code looks like this:

// PlayerMoveC2SPacketAccessor.java
package net.atomicbyte.atomicbomb.mixin;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(PlayerMoveC2SPacket.class)
public interface PlayerMoveC2SPacketAccessor {
    @Accessor
    double getX();

    @Accessor
    double getY();

    @Accessor
    double getZ();

    @Mutable
    @Accessor("x")
    void setX(double x);

    @Mutable
    @Accessor("y")
    void setY(double y);

    @Mutable
    @Accessor("z")
    void setZ(double z);
}

Now, we put it all together in a final mixin:

package net.atomicbyte.atomicbomb.mixin;

import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import static net.atomicbyte.atomicbomb.AtomicBomb.LOGGER;

@Mixin(ClientConnection.class)
public abstract class roboWalkMixin {
    private double smooth(double d) {
        double temp = (double) Math.round(d * 100) / 100;
        return Math.nextAfter(temp, temp + Math.signum(d));
    }

    @Inject(method = "send(Lnet/minecraft/network/packet/Packet;)V", at = @At("HEAD"), cancellable = true)
    private void sendPacket(Packet packet, CallbackInfo info) {
        final boolean roboWalk = true;
        String id = packet.getPacketType().toString();
        if (!id.equals("serverbound/minecraft:client_tick_end")) {
            LOGGER.info(id);
            LOGGER.info(packet.getClass().toString());
        }

        if (id.equals("serverbound/minecraft:move_player_pos") && roboWalk) {
            PlayerMoveC2SPacketAccessor packet_accessor = (PlayerMoveC2SPacketAccessor) packet;
            double x = smooth(packet_accessor.getX());
            double z = smooth(packet_accessor.getZ());
            packet_accessor.setX(x);
            packet_accessor.setZ(z);

        }
    }
}

Conclusion

I wish I could use darknet diaries' badass outro music, but this is a blog post and I could get copyright striked, so here's what it sounds like so you can imagine it:
dun. dun dun. dun dun dun. dundundundundundundun! dun. dun dun. dun dun dun. dun dun dun dun dun dun dun dun dun dun dun dun dun dun dun dun (slightly higher pitched)!

Seriously though listen to it on bandcamp. I listened to it yesterday and it was sooo good.

You can try out blockstalk on my discord server.


For spear phishing attempts, business inquiries, love letters, etc: signal icon or email icon