Solidity Jail 1
Solidity Jail writeup showing how to bypass keyword restrictions and use the contract interface to read the flag.
Introduction
The given tasks gives us two files:
- Jail.sol
- jailTalk.py
Chall Description:
Bash Jail? Boring. Pyjail? Too Common.
Introducing for the first time, Solidity Jail! Make a contract to read the flag!
This is a blockhain/jail challenge so the flag has to be retrieved breaking the jail.
The python file is the entrypoint and it does three tasks:
- Check the input against a list of blocked keywords
blacklist = [
"flag",
"transfer",
"address",
"this",
"block",
"tx",
"origin",
"gas",
"fallback",
"receive",
"selfdestruct",
"suicide"
]
if any(banned in body for banned in blacklist):
raise ValueError(f"Blacklisted string found in contract.")
- Compile the contract (written in solidity) with the code injected by us (
body):
source = f"""// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Solution {{
function main() external returns (string memory) {{
{body}
}}
}}
"""
print("Final contract with inserted main() body:")
print(source)
compiled = compile_standard(
{
"language": "Solidity",
"sources": {"Solution.sol": {"content": source}},
"settings": {
"outputSelection": {
"*": {
"*": ["evm.bytecode.object"]
}
}
},
},
solc_version="0.8.20",
)
- Then retrieve the address of the contract below and calls the run function with the bytecode of our contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract BytecodeRunner {
string public flag = "wwf{REDACTED}";
function run(bytes memory _bytecode, bytes32 _salt) public
returns (bool success, bytes memory result)
{
address newContract;
assembly {
newContract := create2(
0,
add(_bytecode, 0x20),
mload(_bytecode),
_salt
)
if iszero(newContract) {
revert(0, 0)
}
}
bytes memory callData = abi.encodeWithSelector(
bytes4(keccak256("main()"))
);
(success, result) = newContract.call(callData);
require(success, "Execution of main() failed.");
}
}
bytecode_hex = "0x" + compiled["contracts"]["Solution.sol"]["Solution"]["evm"]["bytecode"]["object"]
salt_hex = "0x" + os.urandom(32).hex()
web3 = Web3(Web3.HTTPProvider(rpc_url))
contr_abi = [...]
contr = web3.eth.contract(address=contr_add, abi=contr_abi)
bytecode_bytes = Web3.to_bytes(hexstr=bytecode_hex)
salt_bytes = Web3.to_bytes(hexstr=salt_hex)
print(contr.functions.run(bytecode_bytes, salt_bytes).call())
Solution
The objective is to retrieve the flag inside the BytecodeRunner using solidity’s keywords that are not banned.
In Solidity, the function selector is the first four bytes of keccak256("functionSignature"). For flag(), that hash’s first four bytes are 0x890eba68.
Then abi.encodePacked(sel) produces a 4-byte array containing the selector.
With this we are able to call the flag() (in the context of the challenge, msg.sender is the challenge contract itself as it invokes the Solution).
Finally we return the result.
bytes4 sel = bytes4(0x890eba68);
bytes memory payload = abi.encodePacked(sel);
(bool ok, bytes memory ret) = msg.sender.call{value:0}(payload);
require(ok);
return abi.decode(ret, (string));
What we get from the program is this output where we can clearly distinguish the flag:
[True, b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(wwf{y0u_4r3_7h3_7ru3_m4573r_0f_s0l1d17y}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']