Skip to content

Commit 6510757

Browse files
dergoeggeclaude
andcommitted
fuzzamoto-libafl: give the byte mutator a Bitcoin Script dictionary
All `LoadBytes` values are mutated through `LibAflByteMutator`, and they end up as raw output/input scripts, witness stack elements and raw p2p message payloads. The mutator ran `havoc_mutations()` over a state with no `Tokens` metadata, so it did pure structureless havoc with no Bitcoin awareness and rarely assembled a valid opcode sequence or a recognizable script template by chance. Load a curated dictionary of Script opcodes and standard script-template fragments into the mutator's state and switch to `havoc_mutations().merge(tokens_mutations())` so `TokenInsert`/ `TokenReplace` can splice meaningful fragments in, reaching script interpreter branches that random bytes almost never hit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7396147 commit 6510757

1 file changed

Lines changed: 78 additions & 3 deletions

File tree

fuzzamoto-libafl/src/mutators.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ use libafl::{
77
common::HasMetadata,
88
corpus::{Corpus, CorpusId, NopCorpus},
99
inputs::BytesInput,
10-
mutators::{HavocScheduledMutator, MutationResult, Mutator, havoc_mutations},
10+
mutators::{
11+
HavocScheduledMutator, MutationResult, Mutator, Tokens, havoc_mutations, tokens_mutations,
12+
},
1113
random_corpus_id,
1214
state::{HasCorpus, HasRand, StdState},
1315
};
1416
use libafl_bolts::{
1517
HasLen, Named,
1618
rands::{Rand, StdRand},
19+
tuples::Merge,
1720
};
1821
use rand::RngCore;
1922

@@ -288,13 +291,82 @@ impl<M, R> Named for IrGenerator<M, R> {
288291
}
289292
}
290293

294+
/// A dictionary of Bitcoin-significant byte sequences for the raw byte mutator.
295+
///
296+
/// `OperationMutator` drives all `LoadBytes` mutations through [`LibAflByteMutator`], and
297+
/// `LoadBytes` values end up as raw output/input scripts (`BuildRawScripts`), witness stack
298+
/// elements (`AddWitness`) and raw p2p message payloads. Plain havoc rarely assembles a valid
299+
/// opcode sequence or a recognizable script template by chance, so the script interpreter and the
300+
/// structured parsers behind these buffers stay shallowly explored. Feeding the havoc stage a
301+
/// dictionary of real Script opcodes and standard script templates lets `TokenInsert`/
302+
/// `TokenReplace` splice meaningful fragments in, reaching interpreter branches that random bytes
303+
/// almost never hit.
304+
fn bitcoin_script_dictionary() -> Tokens {
305+
// Single-byte Script opcodes. Values mirror Bitcoin Core's `script/script.h`.
306+
let opcodes: &[u8] = &[
307+
// Push value
308+
0x00, // OP_0 / OP_FALSE
309+
0x4f, // OP_1NEGATE
310+
0x51, // OP_1 / OP_TRUE
311+
0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // OP_2 ..= OP_8
312+
0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, // OP_9 ..= OP_16
313+
// Push-data length prefixes
314+
0x4c, 0x4d, 0x4e, // OP_PUSHDATA1/2/4
315+
// Control flow
316+
0x61, // OP_NOP
317+
0x63, 0x64, 0x67, 0x68, // OP_IF, OP_NOTIF, OP_ELSE, OP_ENDIF
318+
0x69, 0x6a, // OP_VERIFY, OP_RETURN
319+
// Stack / alt-stack ops (OP_TOALTSTACK .. OP_TUCK)
320+
0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, //
321+
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, // .. OP_DROP/OP_DUP/OP_SWAP/OP_TUCK
322+
// Splice / size (OP_CAT .. OP_SIZE, splice ops disabled)
323+
0x7e, 0x7f, 0x80, 0x81, 0x82, //
324+
// Bitwise / equality (OP_INVERT .. OP_EQUALVERIFY, bitwise ops disabled)
325+
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, // .. OP_EQUAL/OP_EQUALVERIFY
326+
// Numeric / arithmetic ops (OP_1ADD .. OP_WITHIN)
327+
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, //
328+
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, //
329+
0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, // .. OP_WITHIN
330+
// Crypto
331+
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, // OP_RIPEMD160 ..= OP_HASH256
332+
0xab, // OP_CODESEPARATOR
333+
0xac, 0xad, // OP_CHECKSIG, OP_CHECKSIGVERIFY
334+
0xae, 0xaf, // OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
335+
// Locktime / Taproot
336+
0xb1, 0xb2, // OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY
337+
0xba, // OP_CHECKSIGADD (tapscript)
338+
];
339+
340+
let mut tokens = Tokens::new();
341+
tokens.add_tokens(opcodes.iter().map(|op| vec![*op]));
342+
343+
// Multi-byte structural templates: standard scriptPubKey shapes, witness program prefixes and
344+
// a few protocol-level constants. Inserting these whole lets the mutator land on standard
345+
// output types (and their near-misses) in one step.
346+
let templates: &[&[u8]] = &[
347+
&[0x00, 0x14], // P2WPKH prefix: OP_0 <20-byte push>
348+
&[0x00, 0x20], // P2WSH prefix: OP_0 <32-byte push>
349+
&[0x51, 0x20], // P2TR prefix: OP_1 <32-byte push>
350+
&[0x76, 0xa9, 0x14], // P2PKH head: OP_DUP OP_HASH160 <20-byte push>
351+
&[0x88, 0xac], // P2PKH tail: OP_EQUALVERIFY OP_CHECKSIG
352+
&[0xa9, 0x14], // P2SH head: OP_HASH160 <20-byte push>
353+
&[0x87], // P2SH tail: OP_EQUAL
354+
&[0xc0], // Tapscript leaf version
355+
&[0x50], // Taproot annex prefix
356+
&[0x00, 0x01], // segwit marker+flag
357+
];
358+
tokens.add_tokens(templates.iter().map(|t| t.to_vec()));
359+
360+
tokens
361+
}
362+
291363
pub struct LibAflByteMutator {
292364
state: StdState<NopCorpus<BytesInput>, BytesInput, StdRand, NopCorpus<BytesInput>>,
293365
}
294366

295367
impl LibAflByteMutator {
296368
pub fn new() -> Self {
297-
let state = StdState::new(
369+
let mut state = StdState::new(
298370
StdRand::new(),
299371
NopCorpus::<BytesInput>::new(),
300372
NopCorpus::new(),
@@ -303,6 +375,9 @@ impl LibAflByteMutator {
303375
)
304376
.unwrap();
305377

378+
// Make a Bitcoin Script dictionary available to the token mutators below.
379+
state.add_metadata(bitcoin_script_dictionary());
380+
306381
Self { state }
307382
}
308383
}
@@ -311,7 +386,7 @@ impl fuzzamoto_ir::OperationByteMutator for LibAflByteMutator {
311386
fn mutate_bytes(&mut self, bytes: &mut Vec<u8>) {
312387
let mut input = BytesInput::from(bytes.clone());
313388

314-
let mut mutator = HavocScheduledMutator::new(havoc_mutations());
389+
let mut mutator = HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
315390
let _ = mutator.mutate(&mut self.state, &mut input);
316391

317392
bytes.clear();

0 commit comments

Comments
 (0)