Step 4
Figure out how to get the flag
Take a look at the smart contract source code
The first step in figuring out how to get the flag is view the source code. The source code was provided was the event and can be viewed here. Navigate to Module.move in the source directory to find the module source code.
The flag
Take a moment to look through the four contracts. Try to locate the get_flag() function.
public entry fun get_flag(box: TreasuryBox, ctx: &mut TxContext) {
let TreasuryBox { id } = box;
object::delete(id);
let d100 = random::rand_u64_range(0, 100, ctx);
if (d100 == 0) {
event::emit(Flag { user: tx_context::sender(ctx), flag: true });
}
}We can see that the get_flag() function is in the inventory module. The function is marked as entry which means we are able to call it directly via a script. This time, however, the function requires more than just the TxContext. It also requires box: TreasuryBox.
Treasury Box
The TreasuryBox is struct that can be located in the inventory module. Structs are custom types that hold complex data (or no data at all). Structs can also have special properties: copy, drop, key, store. View the move documentation to learn more about the struct abilities. It is important to note that the Flag event is also represented with a struct.
In order to call the get_flag() function, we need to have a TreasuryBox owned by our account which we pass into the function.
Take some time to find out how to get a TreasuryBox in the modules.
There is another function in the inventory module that creates a TreasuryBox. However, this function is not mark with the entry modifier. It is instead labeled as public(friend). This modifier means that only modules deployed in the same package (transaction) can call this function. We need to find where this function is being called in the modules in order to find out how to create a TreasuryBox.
The create_treasury_box() function is called in the slay_boar_king() function located in the adventure module. We can see that in this function, the a TreasuryBox is created and then transferred to the caller of the function (line 7, 8).
The bigger picture
Lets take some time to understand the adventure module as a whole.
The adventure contains two public entry functions, slay_boar() and slay_boar_king(). Both of these functions take in a Hero object as input. Without worrying about the Hero object, we can see that the functions emulate a battle between the provided Hero and a boar or boar king.
Both functions use the create_monster() function to create the respective monsters. This function is not defined as public, which means, by default, it is private and can only be called by the other functions in the adventure module. The function takes in a minimum and maximum value for hp (AKA health points), strength, and defense. The function then uses the rand_u64_range() in order to generate random values to create a new Monster object. The function finally returns the newly created Monster object.
The two slay functions then use the internal fight_monster() function to pit the Hero against the Monster. This fight_monster() uses the values of both the Hero and Monster to simulate a battle and declare the outcome:
let rst = 0u64; // 0: tie, 1: hero win, 2: monster win;
Take some time to look at the struct definitions for both the Hero and Monster, and understand what is used to determine the outcome of the battle.
Finally, after digesting the battle mechanics and the attributes of the Monster and Hero objects and looking at the slaying functions, we can conclude the following:
Successfully laying a boar or boar king will level up the hero 1 or 2 levels respectively
Regardless of the outcome of the battle, an
SlainEventevent is emitted in both functionsThe boar and boar king objects are both deleted after the battle.
After a successful battle with a boar, there is a 10% chance of the Hero gaining a sword and a 10% chance of the Hero gaining an armor piece. These objects increase the strength and defense of the Hero, which increases the odds of winning battles.
After a successful battle with a boar king, there is a 1% chance of getting a TreasuryBox.
Take a moment to make sure you can understand how to figure out these conclusions from scanning the modules.
You may have also noticed that even after getting a TreasuryBox, there is only a 1% chance of emitting the Flag event. After the get_flag() function call, the TreasuryBox is destroyed regardless of the Flag event being emitted or not. I don't know about you, but I don't like these odds. Lets take a closer look at how the random numbers are generated in the modules.
Hacking the random number generator (RNG)
We can tell that the random number generation might be a source of weakness in the module due to the warning in the beginning:
We know that all of the random number generation done in the other modules use the rand_u64_range() function. This function generates numbers based on the seed that is generated by the seed() function.
We can see that the seed is generated with the ctx data as well as the uid return from object::new(ctx). By taking a look at the Sui object module as well as the Sui TxContext module (located here), we can see that the object::new() function calls tx_context::new_object() function that shows the ids_created attribute of the TxContext object is what is being used to generate the uid. Now we know how the seed is created.
Since we know how the seed is created, we can use this information to predict the number that will be generated with the random number generator. However, we cannot just use the actual version of the generator to predict the number since that will change the ids_created attribute. We need to create local version of the random number generator that won't affect any of the global attributes like the ids_created attribute.
To do this, we will need to create our own version of the TxContext struct, a local copy of the RNG functions, local copies of all of the library functions that are used during the RNG, and some helpers to help us parse and organize all of the data.
I would also like to thank and give credit to nick0ve to helping provide this solution.
This code is also available in the writeup repo for better readability.
Creating a module to level up the hero
Before we are able to exploit the random number generator, we need to need to level up the hero in order to defeat the boar king and get a TreasuryBox.
In order for the hero to have a better chance of defeating a boar king to get the chance to get a TreasuryBox, we need to level up the hero which increases the hero's stats. To level up, we need the hero to have a experience level of least 100. We will can increase the experience level of the hero of repeatedly slaying boars.
Once we can slay the boar king, we are going to exploit the RNG to ensure that we will get a Treasury Box on the first try.
Deploying and using solution module
Once we have the solution module , we need a script that will interact with the module. In this challenge, we will also use the script to build and deploy our module automatically rather than manually deploy it.
Below is the script to interact with the module. Take a moment to understand everything that is happening.
Running this script will output the transaction digest that has the flag event. We can turn confirm this and get credit for the challenge.
Last updated