You might have heard about tixy.land, the minimalist javascript playground for creative coding.
While scrolling on the tixy feed, being amazed by how creative people can be, I stumbled upon a discussion between Martin Kleppe, the creator of Tixy, and Gareth Heyes, a well known security researcher, regarding creating a Fuzzer for tixy:
As I had experience with code modification tools, I decided to try hacking something up and make a quick fuzzer!
Want to see the result first?
Possible flashes warning
Sure, click here!
Getting started
Tixy setup
Setting up tixy locally is pretty easy, especially since it's on github!
However, since I only wanted to change the javascript a bit, I wanted a single file, index.html
, without having to compile the CSS myself.
I ended up copying the html of the live version at tixy.land, and replacing the script
content with the not minified index.js from the repo, and replacing the imported example.json
with a variable having the same value.
Adding jscodeshift
JSCodeShift is a powerful tool to navigate and modify the AST of source code which I'll be using.
Adding jscodeshift was slightly harder than setting tixy up: As I wanted to keep things simple, I couldn't use the existing npm version as it would require compiling the project.
I ended up using Browserify to compile it to a single file:
npx browserify jscodeshift/index.js -o jscodeshift.min.js --standalone jscodeshift
I then copied this file into my project, added a reference to it in the HTML, and was ready to go!
Getting samples
To fuzz values, we need to start by gathering existing examples, ideally ones with interesting effects.
I ended up merging the existing examples.json
and the ones under the tixy.land/gallery page.
Making the fuzzer
With our setup in place, let's start thinking about how to actually implement the fuzzer.
Here is a rough outline of the plan:
- Pick random samples out of the examples
- Convert them to random fragments
- Merge the fragments together
To split the project into smaller samples, we need to figure out exactly where to split!
After analyzing few of the tixies on astexplorer, I ended up picking two distinct operations which can usually be extracted without issues:
Binary Expressions and Call Expressions!
Binary Expressions are mostly arithmetic operators such as +
and -
, with few other exceptions.
You can view the complete list of these operators on the ast-types repository.
Extracting the binary expression node is picking both sides of the equation as well as the operator itself, and they are typically self-contained.
Call Expressions are function calls, such as Math.random()
or sin(y)
. Like the binary expressions, they usually are self contained, and small enough to extract without issues.
Now that we know what to extract, let's start on the how to extract them!
Picking random samples is simple: Pick a size, and pick random elements of the examples
array!
In this case, I picked an arbitrary size of 8
for the biggest sample count, for no particular reason.
const sampleCount = Math.floor(Math.random() * 8) + 1
const samples = new Array(sampleCount).fill(0).map(
() => pickRandom(allSamples)
);
Separating them by valid fragments is where we start using JSCodeShift.
From a string with valid JavaScript code, we can create our own jscodeshift
instance with it by calling jscodeshift(code)
.
Then, using the .find
method on the resulting object with a type such as BinaryExpression
gives us an array-like object with all of the binary expressions.
Finally, to convert the AST node returned by JSCodeShift back to JavaScript, we need to call the toSource
method.
Simple, isn't it?
Here is how the resulting code looks:
const availableOperations = [];
const jsc = jscodeshift(sample);
jsc.find("BinaryExpression").forEach(v => availableOperations.push(v));
const chosenSnippet = pickRandom(availableOperations);
const resultingCode = jscodeshift(chosenSnippet).toSource();
Finally, doing this on all of our selected samples, and on both binary expressions and call expressions, we end up with an array of random code snippets.
Now, to merge the fragments back together, I decided to add a random operator between each of them.
Thankfully, as both sides should be valid JavaScript strings, there is no need to use JSCodeShift anymore, a simple concatenation will do.
const delimiters = ['+', '-', '*', '%', '|', '<<', '>>', '%', '^'];
const newCode = fragments.reduce((acc, v) => {
return (acc ? acc + pickRandom(delimiters) : '') + v;
}, '');
Result
Where would be the fun of generating random snippets if we couldn't view the results!
I ripped the existing nextExample
function of the existing tixy
site and instead of using the next examples, used a random code snippet from the fuzzer.
Now, for the amazing results, I saved you the hassle of doing it yourself! Instead, you can visit garand.dev/projects/tixy/, and click on the tixy until you find interesting results!
For maximum viewing pleasure, I also swapped the gallery page to use my fuzzer instead of good examples: https://garand.dev/projects/tixy/gallery
Many of them are either a stroboscopic nightmare or an exact ripoff of the examples, but sometimes there are interesting patterns emerging.
Found any interesting ones? Please link to them in the comments! I'd love to see what can come out of this project!
Top comments (0)