Enchantment seeds are very insecure
Overview
It is possible for a player with a vanilla client to (in this order):
- Guess the enchantment seed for the current enchantment in order to get all the enchantments they would get on an item.
- Guess all future (and technically past) enchantment seeds by using two consecutive enchantment seeds to crack the state of the player's LCG, which is used to generate the enchantment seeds.
- Change the enchantment seed into one which generates useful (as defined by the player) enchantments, with always at most one dummy enchantment.
If you have a hacked client it can be done much quicker, since 12 bits of the enchantment seed are sent to the client, which a vanilla player would not have access to.
Detailed explanation
This is already in my code, and since I can't be bothered typing it out again, I've just copied it here
. For the vanilla client, just ignore the bit which says "the server gives you 12 bits of the 32-bit enchantment seed"; instead we just guess all 32 bits, which takes 20-30 seconds.
/* * The enchantment cracker works as follows: * * First, crack the first few XP seeds. When you open an enchantment table GUI, * the server gives you 12 bits of the 32-bit enchantment seed. Vanilla uses * this masked version of the seed to generate the galactic alphabet text in the * GUI. We use brute force to guess the other 20 bits, matching each possibility * and what it would generate with certain things the server tells us, such as * the enchantment hints. We can narrow down the possibilities to 1 after * putting a few items into the enchantment table. * * Second, we know that the above XP seeds are generated by calling the player * entity's RNG's unbounded nextInt() method. This means that after a doing the * above a few times, enchanting an item after each time, we have a few * consecutive values of nextInt(). Each time an item is enchanted, we narrow * down the possibilities of what the player RNG's state could be. The first * value of nextInt() gives us 32 bits of its 48-bit internal state. Each time * nextInt() is next called, we narrow down its internal state by brute force. * It usually only takes two values of nextInt() to guess the internal state. * * There's one small catch: for this to work, we have to know that the values of * nextInt() are indeed consecutive. The first XP seed, if it's cracked, cannot * be used as one of these values since it was generated an unknown length of * time in the past, possibly even before a server restart - so we have to * ignore that. More obviously, there are many, many other things which use the * player's RNG and hence affect its internal state. We have to detect on the * client side when one of these things is likely to be happening. This is only * possible to do for certain if the server is running vanilla because some mod * could use the player's RNG for some miscellaneous task. * * Third, we can take advantage of the fact that generating XP seeds is not the * only thing that the player RNG does, to manipulate the RNG to produce an XP * seed which we want. The /cenchant command, which calls the * manipulateEnchantments method of this class, does this. We change the state * of the player RNG in a predictable way by throwing out items of the player's * inventory. Each time the player throws out an item, rand.nextFloat() gets * called 4 times to determine the velocity of the item which is thrown out. If * we throw out n items before we then do a dummy enchantment to generate our * new enchantment seed, then we can change n to change the enchantment seed. By * simulating which XP seed each n (up to a limit) will generate, and which * enchantments that XP seed will generate, we can filter out which enchantments * we want and determine n. */
The full code which exploits the enchantment cracking can be found here.
The source code for the vanilla tool is not available, but most of the code is copied from the above source.
Steps to reproduce (vanilla client)
- Follow the instructions in the video.
- The video also contains an animated explanation of how the enchantment cracking works.
- It takes about 6 minutes to set up, then an average of 86 seconds per item you're enchanting, depending on the rarity of the enchantments (timed over 45 diamond swords, each with sharpness 4, unbreaking 3, looting 3).
Steps to reproduce (hacked client)
- Make sure Forge is installed.
- Download my mod (attached), rename it to .jar, and put it in .minecraft/mods
- Start a vanilla server and connect to it with the modded client.
- Build a full enchanting setup with 15 bookshelves. The bookshelves are not technically required, but speed up the process.
- Run the client-side command /ctemprule set enchantingPrediction true.
- Waste your first enchantment (enchant an item and throw it away)
- Place an item into the enchanting table without actually enchanting it.
- Change the inputs to the enchantment generation algorithm. For example, you could put a different item into the table, or you could block some bookshelves with a torch.
- Repeat steps 7 and 8 until you're in state CRACKED_ENCH_SEED (top left of enchantment table gui).
- Enchant the item.
- Repeat steps 7-10 until you're in state CRACKED.
- Now you're ready to use the /cenchant client-side command (see below). To speed this up, make sure there is a reasonably large amount (2-3 stacks) of any item in your inventory. Once you run the command, don't do anything until it stops throwing out items, then follow the instructions the command gives you.
This may seem pretty long-winded, but I was able to do it in less than 30 seconds after a bit of practice.
Be careful not to do anything else which affects the player RNG's state. You will have to start the process again. The mod should notify you if you accidentally do this.
The /cenchant command
The purpose of this command is to prepare a useful enchantment seed for you. You first specify the item which you are intending to put in the table, then a list of zero or more restrictions on the list of enchantments which would be on that item. A with restriction indicates that the specified enchantment must be included in the list. A without restriction indicates that the specified enchantment must not be included in the list. The enchantment levels can either be a single number or a range, a bit like the ranges in the new 1.13 entity selectors.
Examples:
- /cenchant diamond_sword with looting 3 without smite 1..5 without bane_of_arthropods 1..5
- /cenchant fishing_rod with lure 3 with luck_of_the_sea 3
Suggested fix
I mean this is really for you guys to decide, but my guess would be to make the enchantment seed at least bits long, and send the client a few less bits to generate the galactic text. This way, there is no need to change the protocol. Also, if you feel it's necessary, the enchantment seeds could be generated by a global SecureRandom instead of the player's RNG.
2017-12-28, 12:20 AM
2023-10-26, 07:43 PM
4
5
-