UNPKG

jsfuzz

Version:
365 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); const math_1 = require("./math"); var crypto = require('crypto'); const INTERESTING8 = new Uint8Array([-128, -1, 0, 1, 16, 32, 64, 100, 127]); const INTERESTING16 = new Uint16Array([-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767, -128, -1, 0, 1, 16, 32, 64, 100, 127]); const INTERESTING32 = new Uint32Array([-2147483648, -100663046, -32769, 32768, 65535, 65536, 100663045, 2147483647, -32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767, -128, -1, 0, 1, 16, 32, 64, 100, 127]); class Corpus { constructor(dir, onlyAscii) { this.inputs = []; this.onlyAscii = onlyAscii; this.maxInputSize = 4096; for (let i of dir) { if (!fs.existsSync(i)) { fs.mkdirSync(i); } if (fs.lstatSync(i).isDirectory()) { if (!this.corpusPath) { this.corpusPath = i; } this.loadFiles(i); } else { this.inputs.push(fs.readFileSync(i)); } } this.seedLength = this.inputs.length; } loadFiles(dir) { fs.readdirSync(dir).forEach(file => { const full_path = path.join(dir, file); this.inputs.push(fs.readFileSync(full_path)); }); } getLength() { return this.inputs.length; } generateInput() { if (this.seedLength > 0) { this.seedLength -= 1; return this.inputs[this.seedLength]; } if (this.inputs.length === 0) { const buf = Buffer.alloc(0, 0); this.putBuffer(buf); return buf; } const buffer = this.inputs[this.rand(this.inputs.length)]; return this.mutate(buffer); } putBuffer(buf) { this.inputs.push(buf); if (this.corpusPath) { const filename = crypto.createHash('sha256').update(buf).digest('hex'); const filepath = path.join(this.corpusPath, filename); fs.writeFileSync(filepath, buf); } } randBool() { return Math.random() >= 0.5; } rand(n) { return Math.floor(Math.random() * Math.floor(n)); } dec2bin(dec) { const bin = dec.toString(2); return '0'.repeat(32 - bin.length) + bin; } // Exp2 generates n with probability 1/2^(n+1). Exp2() { const bin = this.dec2bin(this.rand(2 ** 32)); let count = 0; for (let i = 0; i < 32; i++) { if (bin[i] === '0') { count += 1; } else { break; } } return count; } chooseLen(n) { const x = this.rand(100); if (x < 90) { return this.rand(Math.min(8, n)) + 1; } else if (x < 99) { return this.rand(Math.min(32, n)) + 1; } else { return this.rand(n) + 1; } } toAscii(buf) { let x; for (let i = 0; i < buf.length; i++) { x = buf[i] & 127; if ((x < 0x20 || x > 0x7E) && x !== 0x09 && (x < 0xA || x > 0xD)) { buf[i] = 0x20; } } } mutate(buf) { let res = Buffer.allocUnsafe(buf.length); buf.copy(res, 0, 0, buf.length); const nm = 1 + this.Exp2(); for (let i = 0; i < nm; i++) { const x = this.rand(16); if (x === 0) { // Remove a range of bytes. if (res.length <= 1) { i--; continue; } const pos0 = this.rand(res.length); const pos1 = pos0 + this.chooseLen(res.length - pos0); res.copy(res, pos0, pos1, res.length); res = res.slice(0, res.length - (pos1 - pos0)); } else if (x === 1) { // Insert a range of random bytes. const pos = this.rand(res.length + 1); const n = this.chooseLen(10); res = Buffer.concat([res, Buffer.alloc(n, 0)], res.length + n); res.copy(res, pos + n, pos); for (let k = 0; k < n; k++) { res[pos + k] = this.rand(256); } } else if (x === 2) { // Duplicate a range of bytes. if (res.length <= 1) { i--; continue; } const src = this.rand(res.length); let dst = this.rand(res.length); while (src === dst) { dst = this.rand(res.length); } const n = this.chooseLen(res.length - src); const tmp = Buffer.alloc(n, 0); res.copy(tmp, 0, src); res = Buffer.concat([res, Buffer.alloc(n, 0)]); res.copy(res, dst + n, dst); for (let k = 0; k < n; k++) { res[dst + k] = tmp[k]; } } else if (x === 3) { // Copy a range of bytes. if (res.length <= 1) { i--; continue; } const src = this.rand(res.length); let dst = this.rand(res.length); while (src === dst) { dst = this.rand(res.length); } const n = this.chooseLen(res.length - src); res.copy(res, dst, src, src + n); } else if (x === 4) { // Bit flip. Spooky! if (res.length <= 1) { i--; continue; } const pos = this.rand(res.length); res[pos] ^= 1 << this.rand(8); } else if (x === 5) { // Set a byte to a random value. if (res.length <= 1) { i--; continue; } const pos = this.rand(res.length); res[pos] ^= this.rand(255) + 1; } else if (x === 6) { // Swap 2 bytes. if (res.length <= 1) { i--; continue; } const src = this.rand(res.length); let dst = this.rand(res.length); while (src === dst) { dst = this.rand(res.length); } [res[src], res[dst]] = [res[dst], res[src]]; } else if (x === 7) { // Add/subtract from a byte. if (res.length === 0) { i--; continue; } const pos = this.rand(res.length); const v = this.rand(35) + 1; if (this.randBool()) { res[pos] += v; } else { res[pos] -= v; } } else if (x === 8) { // Add/subtract from a uint16. if (res.length < 2) { i--; continue; } const pos = this.rand(res.length - 1); let v = this.rand(35) + 1; if (this.randBool()) { v = 0 - v; } if (this.randBool()) { res.writeUInt16BE(math_1.uint16(res.readUInt16BE(pos) + v), pos); } else { res.writeUInt16LE(math_1.uint16(res.readUInt16LE(pos) + v), pos); } } else if (x === 9) { // Add/subtract from a uint32. if (res.length < 4) { i--; continue; } const pos = this.rand(res.length - 3); let v = this.rand(35) + 1; if (this.randBool()) { v = 0 - v; } if (this.randBool()) { res.writeUInt32BE(math_1.uint32(res.readUInt32BE(pos) + v), pos); } else { res.writeUInt32LE(math_1.uint32(res.readUInt32LE(pos) + v), pos); } } else if (x === 10) { // Replace a byte with an interesting value. if (res.length === 0) { i--; continue; } const pos = this.rand(res.length); res[pos] = INTERESTING8[this.rand(INTERESTING8.length)]; } else if (x === 11) { // Replace an uint16 with an interesting value. if (res.length < 2) { i--; continue; } const pos = this.rand(res.length - 1); if (this.randBool()) { res.writeUInt16BE(INTERESTING16[this.rand(INTERESTING8.length)], pos); } else { res.writeUInt16LE(INTERESTING16[this.rand(INTERESTING8.length)], pos); } } else if (x === 12) { // Replace an uint32 with an interesting value. if (res.length < 4) { i--; continue; } const pos = this.rand(res.length - 3); if (this.randBool()) { res.writeUInt32BE(INTERESTING32[this.rand(INTERESTING8.length)], pos); } else { res.writeUInt32LE(INTERESTING32[this.rand(INTERESTING8.length)], pos); } } else if (x === 13) { // Replace an ascii digit with another digit. const digits = []; for (let k = 0; k < res.length; k++) { if (res[k] >= 48 && res[k] <= 57) { digits.push(k); } } if (digits.length === 0) { i--; continue; } const pos = this.rand(digits.length); const was = res[digits[pos]]; let now = was; while (now === was) { now = this.rand(10) + 48; // '0' === 48 } res[digits[pos]] = now; } else if (x === 14) { // Splice another input. if (res.length < 4 || this.inputs.length < 2) { i--; continue; } const other = this.inputs[this.rand(this.inputs.length)]; if (other.length < 4) { i--; continue; } // Find common prefix and suffix. let idx0 = 0; while (idx0 < res.length && idx0 < other.length && res[idx0] === other[idx0]) { idx0++; } let idx1 = 0; while (idx1 < res.length && idx1 < other.length && res[res.length - idx1 - 1] === other[other.length - idx1 - 1]) { idx1++; } // If diffing parts are too small, there is no sense in splicing, rely on byte flipping. const diff = Math.min(res.length - idx0 - idx1, other.length - idx0 - idx1); if (diff < 4) { i--; continue; } other.copy(res, idx0, idx0, Math.min(other.length, idx0 + this.rand(diff - 2) + 1)); } else if (x === 15) { // Insert a part of another input. if (res.length < 4 || this.inputs.length < 2) { i--; continue; } const other = this.inputs[this.rand(this.inputs.length)]; if (other.length < 4) { i--; continue; } const pos0 = this.rand(res.length + 1); const pos1 = this.rand(other.length - 2); const n = this.chooseLen(other.length - pos1 - 2) + 2; res = Buffer.concat([res, Buffer.alloc(n, 0)], res.length + n); res.copy(res, pos0 + n, pos0); for (let k = 0; k < n; k++) { res[pos0 + k] = other[pos1 + k]; } } } if (res.length > this.maxInputSize) { res = res.slice(0, this.maxInputSize); } if (this.onlyAscii) { this.toAscii(res); } return res; } } exports.Corpus = Corpus; //# sourceMappingURL=corpus.js.map