// isotope
// An advanced data mutation fuzzer
// © Jean Pereira <counterswarm.de>
const version = 0.6
const readme = `isotope v${version}
Usage: isotope [options]
Optional parameters:
-d [data] Input data
-f [file] Input file
-r [directory] Input sample
-o [file] Output file
-n [count] Sample count
-c [count] Max mutation count
-m [module] Module to load
-s [seed] Seed value
`
const arg = require('arg')
function randomChunks(num) {
let result = [];
let remaining = num;
while (remaining > 0) {
let chunk = Math.floor(Math.random() * remaining) + 1;
result.push(chunk);
remaining -= chunk;
}
return result;
}
let args
try {
args = arg({
'--help': Boolean,
'-h': '--help',
'--count': Number,
'-c': '--count',
'--data': String,
'-d': '--data',
'--file': String,
'-f': '--file',
'--output': String,
'-o': '--output',
'--num': Number,
'-n': '--num',
'--random': String,
'-r': '--random',
'--seed': Number,
'-s': '--seed'
})
} catch(e){
displayHelp()
}
const required = []
checkRequiredArgs(args, required)
function a(name) {
return args[`--${name}`]
}
function displayHelp() {
console.log(readme)
process.exit(1)
}
if(a('help')) {
displayHelp()
}
function checkRequiredArgs(args, requiredArgs) {
required.forEach(arg => {
if (!args[arg]) {
displayHelp()
}
})
}
const crypto = require('crypto')
const fs = require('fs')
const net = require('net')
const glob = require('glob')
const path = require('path')
const minimatch = require('minimatch')
let useSeed = false
let seedValue = 0
if(a('seed')) {
useSeed = true
seedValue = parseInt(a('seed'))
}
const runServer = false
const mutationMethods = [
'fbi', 'fby', 'dby',
'dbi', 'rvb', 'rpb',
'trb', 'dub', 'rrb',
'spb', 'oby', 'rby',
'mat', 'cpb'
]
function byteArray(int) {
let result = []
while (int > 0) {
let chunk = Math.floor(Math.random() * int) + 1
result.push(chunk)
int -= chunk
}
return result
}
function generateByteMatrix(buf) {
if(Math.floor(Math.random() * 2) > 0) {
return byteArray(buf.length)
}
else {
return [1, 4, 8, 16, 24, 32, 64, 72, 144, 576]
}
}
function fbi(buf) { // bitflip: reverse the bits of a random byte
buf = Buffer.from(buf)
randPos = Math.floor(Math.random() * buf.length)
buf[randPos] = parseInt(buf[randPos].toString().split('').reverse().join(''))
return buf
}
function fby(buf) {
buf = Buffer.from(buf)
const randPos = Math.floor(Math.random() * buf.length)
const flipPos = Math.floor(Math.random() * buf.length)
const bufferCopy = buf[randPos]
buf[randPos] = buf[flipPos]
buf[flipPos] = bufferCopy
return buf
}
function rby(buf) {
buf = Buffer.from(buf)
const randPos = Math.floor(Math.random() * buf.length)
buf[randPos] = crypto.randomBytes(1)[0]
return buf
}
function mat(buf) {
buf = Buffer.from(buf)
const randPos = Math.floor(Math.random() * buf.length)
const randPos2 = Math.floor(Math.random() * buf.length)
const mathOps = [
'%', '/', '-',
'+', '|', '*'
]
const mathVars = [
'buf[randPos]',
'buf[randPos+1]',
'buf[randPos-1]',
'buf[randPos2]',
'buf[randPos]',
'crypto.randomBytes(1)[0]',
'0xFF',
'0x90',
'0x0A',
'0x00'
]
if(Math.floor(Math.random() * 200 ) > 0) {
eval(`buf[randPos] = buf[randPos] ${mathOps[Math.floor(Math.random() * mathOps.length)]} ${mathVars[Math.floor(Math.random() * mathVars.length)]}`)
} else {
eval(`buf[randPos] = ${mathVars[Math.floor(Math.random() * mathVars.length)]}`)
}
return buf
}
function dby(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length
const writePos = Math.floor(Math.random() * maxPos)
newBuf = Buffer.concat([
buf.slice(0, writePos),
fby(Buffer.from(buf.slice(writePos, writePos + byteCount))),
buf.slice(writePos + byteCount, buf.length)
])
buf = newBuf
return buf
}
function dbi(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length
const writePos = Math.floor(Math.random() * maxPos)
newBuf = Buffer.concat([
buf.slice(0, writePos),
fbi(Buffer.from(buf.slice(writePos, writePos + byteCount))),
buf.slice(writePos + byteCount, buf.length)
])
buf = newBuf
return buf
}
function oby(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length
const writePos = Math.floor(Math.random() * maxPos)
newBuf = Buffer.concat([
buf.slice(0, writePos),
crypto.randomBytes(byteCount),
buf.slice(writePos + byteCount, buf.length)
])
buf = newBuf
return buf
}
function rvb(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length
const writePos = Math.floor(Math.random() * maxPos)
newBuf = Buffer.concat([
buf.slice(0, writePos),
Buffer.from(buf.slice(writePos, writePos + byteCount).toString().split('').reverse().join('')),
buf.slice(writePos + byteCount, buf.length)
])
buf = newBuf
return buf
}
function rpb(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length - byteCount
const writePos = Math.floor(Math.random() * maxPos)
if ((writePos + byteCount) < maxPos) {
newBuf = Buffer.concat([
buf.slice(0, writePos),
Buffer.from(String.fromCharCode(buf[writePos]).repeat(byteCount)),
buf.slice(writePos + byteCount, buf.length)
])
if(Buffer.compare(newBuf, buf)) {
buf = newBuf
} else {
buf = rpb(buf)
}
} else {
buf = rpb(buf)
}
return buf
}
function cpb(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length
const writePos = Math.floor(Math.random() * maxPos)
if (writePos < maxPos) {
newBuf = Buffer.concat([
buf.slice(0, writePos),
Buffer.from(buf.slice(writePos, writePos + byteCount).toString().repeat(Math.floor(Math.random() * 100))),
buf.slice(writePos + byteCount, buf.length)
])
if(Buffer.compare(newBuf, buf)) {
buf = newBuf
} else {
buf = trb(buf)
}
} else {
buf = trb(buf)
}
return buf
}
function trb(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length
const writePos = Math.floor(Math.random() * maxPos)
if (writePos < maxPos) {
newBuf = Buffer.concat([
buf.slice(0, writePos),
buf.slice(writePos + byteCount, buf.length)
])
if(Buffer.compare(newBuf, buf)) {
buf = newBuf
} else {
buf = trb(buf)
}
} else {
buf = trb(buf)
}
return buf
}
function dub(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length - byteCount
const writePos = Math.floor(Math.random() * maxPos)
if ((writePos + byteCount) < maxPos) {
newBuf = Buffer.concat([
buf.slice(0, writePos),
Buffer.from(String.fromCharCode(buf[writePos]).repeat(byteCount)),
buf.slice(writePos, buf.length)
])
if(Buffer.compare(newBuf, buf)) {
buf = newBuf
} else {
buf = dub(buf)
}
} else {
buf = dub(buf)
}
return buf
}
function rrb(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length - byteCount
const writePos = Math.floor(Math.random() * maxPos)
if ((writePos + byteCount) < maxPos) {
newBuf = Buffer.concat([
buf.slice(0, writePos),
Buffer.from(String.fromCharCode(buf[writePos + byteCount]).repeat(byteCount)),
buf.slice(writePos + byteCount, buf.length)
])
if(Buffer.compare(newBuf, buf)) {
buf = newBuf
} else {
buf = rrb(buf)
}
} else {
buf = rrb(buf)
}
return buf
}
function spb(buf) {
const byteMatrix = generateByteMatrix(buf)
const randPos = Math.floor(Math.random() * buf.length)
const byteCount = byteMatrix[Math.floor(Math.random() * byteMatrix.length)]
const maxPos = buf.length - byteCount
const writePos = Math.floor(Math.random() * maxPos)
if ((writePos + byteCount) < maxPos) {
newBuf = Buffer.concat([
buf.slice(0, writePos),
Buffer.from(String.fromCharCode(buf[writePos + byteCount]).repeat(byteCount)),
buf.slice(writePos, buf.length)
])
if(Buffer.compare(newBuf, buf)) {
buf = newBuf
} else {
buf = spb(buf)
}
} else {
buf = spb(buf)
}
return buf
}
function dirGlob(pattern) {
const directory = path.dirname(pattern)
const basename = path.basename(pattern)
return fs.readdirSync(directory).filter(file => minimatch(file, basename)).map(f => `${directory}/${f}`)
}
function calculateHashValue(seed) {
const hash = crypto.createHash('sha256')
hash.update(seed.toString())
const bytes = hash.digest()
let random = 0
for (let i = 0; i < bytes.length; i++) {
random = random * 256 + bytes[i]
}
return random
}
function secureRandom(int, seed=false) {
if(!useSeed) {
return Math.floor(crypto.randomBytes(1)[0] / 256 * int)
} else {
seed = seedValue
}
random = calculateHashValue(seed)
return Math.floor(random / (256 ** 32) * int)
}
function srandProb(arr) {
arr = arr.map(x => Array(x[1]).fill(x[0])).flat();
return arr[Math.floor(Math.random() * arr.length)];
}
function randomArray(arr, seed=false) {
if(!seed) {
const randomIndex = crypto.randomBytes(1).readUInt8(0) % arr.length
return arr[randomIndex]
}
random = calculateHashValue(seed)
const randomIndex = random % arr.length
return arr[randomIndex]
}
function mutateBuffer(buf, recursive=false) {
const processMutations = (secureRandom(maxMutations-1)+1)
if(!recursive) {
if(a('output')) {
if(a('num') && a('output').match('%n')) { // add fallback method: filebla.txt-1, filebla.txt-2 ...
for(let i = 0; i < parseInt(a('num')); i++) {
let fileName = a('output').replace('%n', String(i))
console.log(`Writing file: ${fileName}`)
fs.writeFileSync(fileName, mutateBuffer(buf, true))
}
} else {
fs.writeFileSync(a('output'), mutateBuffer(buf, true))
}
} else {
return mutateBuffer(buf, true)
}
} else {
for(let i=0; i < processMutations; i++) {
if(useSeed) {
mutationType = randomArray(mutationMethods, (seedValue + i))
} else {
mutationType = randomArray(mutationMethods, false)
}
eval(`buf = ${mutationType}(buf)`)
}
}
return buf
}
let buf
let logData = false
if (a('file') || a('random')) {
if(a('random')) {
buf = fs.readFileSync(randomArray(dirGlob(a('random')), false))
} else {
buf = fs.readFileSync(a('file'))
}
} else {
if(a('data')) {
buf = Buffer.from(a('data'), 'utf-8')
} else {
buf = Buffer.from(crypto.randomBytes(secureRandom(10000)+1), 'utf-8')
}
logData = true
}
let maxMutations = srandProb([
[1,50], [5,50], [10,50], [15,50], [20,30],
[25,30], [35,30], [45,30], [50,20], [100,10],
[secureRandom(buf.length),1],
[buf.length,1]])
if(a('count')) {
maxMutations = parseInt(a('count'))
}
try {
buf = mutateBuffer(buf)
} catch(e) {
}
logData ? console.log(buf.toString()) : false