deception
The goal is just to flip the target’s public solved value to true. The supplied Solidity even appears to hand us the password. With a challenge called deception, that was suspiciously generous.
The setup deploys one target and considers the instance solved when TARGET.solved() returns true.
contract Setup {
deception public immutable TARGET;
constructor() payable {
TARGET = new deception();
}
function isSolved() public view returns (bool) {
return TARGET.solved();
}
}
The target stores a private owner, exposes a protected password function and accepts a string whose Keccak-256 hash matches a hard-coded value.
contract deception {
address private owner;
bool public solved;
constructor() {
owner = msg.sender;
solved = false;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can access");
_;
}
function password() onlyOwner public view returns (string memory) {
return "secret";
}
function solve(string memory secret) public {
require(
keccak256(abi.encodePacked(secret)) ==
0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b,
"invalid"
);
solved = true;
}
}
Hashing the visible password produces exactly the constant from solve(), so I first tried the obvious transaction.
> keccak256(abi.encodePacked("secret"))
0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b
$ cast send TARGET "solve(string)" "secret" ...
Error: execution reverted: invalid
That contradiction is the real challenge. The transaction is reaching a contract whose behavior does not match the published source. Instead of trusting the file, I queried the deployed contract. password() is restricted to the owner, but the owner is the Setup contract that created the target. For an eth_call we can set the simulated sender with --from, so no private key for the Setup address is needed.
$ cast call TARGET "password()(string)" \
--rpc-url RPC \
--from SETUP
xyzabc
The live contract returns xyzabc, not secret. Sending the value reported by the deployed bytecode succeeds.
$ cast send TARGET "solve(string)" "xyzabc" \
--rpc-url RPC --private-key PRIVATE_KEY
$ cast call SETUP "isSolved()(bool)" --rpc-url RPC
true
After asking the challenge service for the flag we get:
crew{d0nt_tru5t_wh4t_y0u_s3e_4s5_50urc3!}
The supplied file was a decoy. The chain never cared what it said; asking the deployed contract from the owner’s address was the entire solve.