Recursively scan all executables (PE, ELF and MachO!) in a folder and generate IDA databases in parallel
node batch.js [path]
| const fs = require('fs').promises; | |
| const path = require('path'); | |
| const os = require('os'); | |
| const { spawn } = require('child_process'); | |
| const { EventEmitter } = require('events'); | |
| async function* walk(dir) { | |
| const ignored = new Set('id0,id1,nam,log,til,idb,i64,js,py'.split(',')); | |
| const files = await fs.readdir(dir); | |
| for (file of files) { | |
| const ext = path.extname(file).toLowerCase(); | |
| if (ignored.has(ext)) { | |
| continue; | |
| } | |
| const joint = path.join(dir, file); | |
| try { | |
| const stat = await fs.stat(joint); | |
| if (stat.isDirectory()) { | |
| yield* walk(joint); | |
| } else { | |
| yield joint; | |
| } | |
| } catch(_) { | |
| continue; | |
| } | |
| } | |
| } | |
| async function check64bit(filename) { | |
| const FAT_MAGIC = 0xcafebabe, | |
| FAT_CIGAM = 0xbebafeca, | |
| MH_MAGIC = 0xfeedface, | |
| MH_CIGAM = 0xcefaedfe, | |
| MH_MAGIC_64 = 0xfeedfacf, | |
| MH_CIGAM_64 = 0xcffaedfe; | |
| const IMAGE_FILE_MACHINE_I386 = 0x14c, | |
| IMAGE_FILE_MACHINE_AMD64 = 0x8664, | |
| IMAGE_FILE_MACHINE_ARM = 0x1c0, | |
| IMAGE_FILE_MACHINE_ARM64 = 0xaa64; | |
| const handle = await fs.open(filename); | |
| const buffer = Buffer.allocUnsafe(4); | |
| try { | |
| const { bytesRead } = await handle.read(buffer, 0, 4); | |
| if (bytesRead < 4) throw new RangeError('invalid file size'); | |
| const magic = buffer.readUInt32LE(); | |
| if (new Set([FAT_MAGIC, FAT_CIGAM, MH_MAGIC_64, MH_CIGAM_64]).has(magic)) { | |
| return 64; | |
| } else if (new Set([MH_MAGIC, MH_CIGAM]).has(magic)) { | |
| return 32; | |
| } else if (buffer.slice(0, 2).compare(Buffer.from('MZ')) === 0) { | |
| await handle.read(buffer, 0, 2, 0x3c); | |
| const offset = buffer.readUInt16LE(); | |
| await handle.read(buffer, 0, 4, offset); | |
| if (buffer.compare(Buffer.from('PE\x00\x00')) !== 0) throw new Error('invalid COFF magic'); | |
| await handle.read(buffer, 0, 2, offset + 4); | |
| // little endian only | |
| const cpuMagic = buffer.readUInt16LE(); | |
| if ([IMAGE_FILE_MACHINE_I386, IMAGE_FILE_MACHINE_ARM].includes(cpuMagic)) return 64; | |
| if ([IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_ARM64].includes(cpuMagic)) return 32; | |
| throw new Error('Unknown CPU'); | |
| } else if (buffer.compare(Buffer.from('\x7fELF')) === 0) { | |
| await handle.read(buffer, 0, 1, 4); | |
| const val = buffer.readUInt8(); | |
| if ([1, 2].includes(val)) return val * 32; | |
| throw new Error('invalid EI_CLASS'); | |
| } else { | |
| throw new Error('Unknown executable format'); | |
| } | |
| } finally { | |
| await handle.close(); | |
| } | |
| } | |
| class Parallel extends EventEmitter { | |
| constructor(count) { | |
| super(); | |
| this.max = count || os.cpus().length; | |
| this.pool = new Set(); | |
| process.on('SIGINT', () => { | |
| this.removeAllListeners(); | |
| this.pool.forEach(p => p.kill()); | |
| process.exit(); | |
| }); | |
| } | |
| spawn(cmd, filename) { | |
| const name = path.basename(filename); | |
| const py = path.join(__dirname, 'idc.py'); | |
| const child = spawn(cmd, ['-c', '-A', `-S${py}`, `-L${filename}.log`, `-o${filename}`, filename]); | |
| this.pool.add(child); | |
| child.on('close', (code) => { | |
| this.pool.delete(child); | |
| this.emit('finish', { name, code }); | |
| }); | |
| child.on('error', () => { | |
| }); | |
| return child; | |
| } | |
| async consume(name) { | |
| let bits | |
| try { | |
| bits = await check64bit(name); | |
| } catch(e) { | |
| return; | |
| } | |
| const cmd = bits == 64 ? 'idat64': 'idat'; | |
| const ext = bits == 64 ? 'i64' : 'idb'; | |
| const st = await fs.stat(`${name}.${ext}`).catch(e => {}); | |
| if (st) return; | |
| if (this.pool.size >= this.max) { | |
| await new Promise(resolve => this.once('finish', resolve)).catch(e => console.error(e)); | |
| } | |
| try { | |
| this.spawn(cmd, name); | |
| } catch(e) { | |
| console.log(e); | |
| } | |
| } | |
| } | |
| async function main(cwd) { | |
| const job = new Parallel(); | |
| for await (let name of walk(cwd)) { | |
| await job.consume(name); | |
| } | |
| } | |
| main(process.argv[2] || '.'); |
| import idc | |
| def main(): | |
| # todo: add your analyzer | |
| idc.auto_wait() | |
| idc.qexit(0) | |
| if __name__ == "__main__": | |
| main() |
Forgot to mention that you need to
idatandidat64to$PATH