UNPKG

@bcoders.gr/evm-disassembler

Version:

A comprehensive EVM bytecode disassembler and analyzer with support for multiple EVM versions

388 lines (337 loc) 12.6 kB
/** * Comprehensive bytecode analysis * @module analyzer */ const { decodeBytecode, validateBytecode, extractPushValues, extractAddresses } = require('./decoder'); const { detectMetadata } = require('./metadata'); const { analyzeStackDepth, analyzeControlFlow, analyzeStackPatterns, calculateComplexity } = require('./stack-analyzer'); const { detectFunctions, analyzeFunctionBodies, detectContractPatterns, compareFunctionPatterns } = require('./function-detector'); const { DANGEROUS_OPCODES } = require('./constants'); /** * Perform comprehensive analysis of EVM bytecode * @param {string} bytecode - Hex string of bytecode * @param {Object} options - Analysis options * @returns {Object} Complete analysis results */ function analyzeBytecode(bytecode, options = {}) { const { evmVersion = 'latest', includeMetadata = true, stopAtMetadata = true, performStackAnalysis = true, performFunctionAnalysis = true, performSecurityAnalysis = true } = options; // Validate bytecode const validation = validateBytecode(bytecode); if (!validation.valid) { return { error: validation.error, valid: false }; } // Decode instructions const instructions = decodeBytecode(bytecode, { evmVersion, includeMetadata, stopAtMetadata }); const analysis = { valid: true, bytecodeSize: validation.size, instructionCount: instructions.length, instructions }; // Metadata analysis if (includeMetadata) { const metadata = detectMetadata(bytecode); if (metadata) { analysis.metadata = metadata; } } // Stack analysis if (performStackAnalysis) { const stackAnalysis = analyzeStackDepth(instructions); const controlFlow = analyzeControlFlow(instructions); const patterns = analyzeStackPatterns(instructions); const complexity = calculateComplexity(instructions, stackAnalysis, controlFlow); analysis.stack = stackAnalysis; analysis.controlFlow = controlFlow; analysis.patterns = patterns; analysis.complexity = complexity; } // Function analysis if (performFunctionAnalysis) { const functions = detectFunctions(instructions); const functionBodies = analyzeFunctionBodies(instructions, functions.functions); const contractPatterns = detectContractPatterns(functions.functions); const patternComparisons = compareFunctionPatterns(functions.functions); analysis.functions = { ...functions, ...functionBodies, contractPatterns, patternComparisons }; } // Security analysis if (performSecurityAnalysis) { analysis.security = performSecurityAnalysisInternal(instructions, analysis); } // Extract additional information analysis.pushValues = extractPushValues(instructions); analysis.addresses = extractAddresses(instructions); return analysis; } /** * Perform security-focused analysis * @param {Array} instructions - Decoded instructions * @param {Object} analysis - Current analysis results * @returns {Object} Security analysis results */ function performSecurityAnalysisInternal(instructions, analysis) { const security = { dangerousOpcodes: [], potentialVulnerabilities: [], gasIntensive: [], externalCalls: [], selfdestructs: [], delegatecalls: [] }; // Scan for dangerous opcodes for (let i = 0; i < instructions.length; i++) { const inst = instructions[i]; if (DANGEROUS_OPCODES.includes(inst.opcode)) { security.dangerousOpcodes.push({ pc: inst.pc, opcode: inst.opcode, context: getInstructionContext(instructions, i) }); } // Specific dangerous patterns if (inst.opcode === 'SELFDESTRUCT') { security.selfdestructs.push({ pc: inst.pc, reachable: inst.reachable !== false }); if (inst.reachable !== false) { security.potentialVulnerabilities.push({ type: 'SELFDESTRUCT', severity: 'HIGH', pc: inst.pc, description: 'Contract contains reachable SELFDESTRUCT' }); } } if (inst.opcode === 'DELEGATECALL') { security.delegatecalls.push({ pc: inst.pc, context: getInstructionContext(instructions, i) }); // Check if target is dynamic if (i > 0 && !instructions[i - 1].opcode.startsWith('PUSH')) { security.potentialVulnerabilities.push({ type: 'DYNAMIC_DELEGATECALL', severity: 'HIGH', pc: inst.pc, description: 'DELEGATECALL with dynamic target address' }); } } // External calls if (['CALL', 'CALLCODE', 'DELEGATECALL', 'STATICCALL'].includes(inst.opcode)) { security.externalCalls.push({ pc: inst.pc, type: inst.opcode }); } // Gas intensive operations if (inst.opcode === 'EXP' || inst.opcode === 'SHA3') { security.gasIntensive.push({ pc: inst.pc, opcode: inst.opcode }); } } // Check for reentrancy patterns checkReentrancyPatterns(instructions, security); // Check for integer overflow patterns checkOverflowPatterns(instructions, security); // Check for access control patterns checkAccessControlPatterns(instructions, analysis, security); // Calculate security score security.score = calculateSecurityScore(security); return security; } /** * Get context around an instruction * @param {Array} instructions - All instructions * @param {number} index - Target instruction index * @param {number} contextSize - Number of instructions before/after * @returns {Object} Context information */ function getInstructionContext(instructions, index, contextSize = 3) { const before = []; const after = []; // Get instructions before for (let i = Math.max(0, index - contextSize); i < index; i++) { before.push({ pc: instructions[i].pc, opcode: instructions[i].opcode }); } // Get instructions after for (let i = index + 1; i < Math.min(instructions.length, index + contextSize + 1); i++) { after.push({ pc: instructions[i].pc, opcode: instructions[i].opcode }); } return { before, after }; } /** * Check for reentrancy vulnerability patterns * @param {Array} instructions - Decoded instructions * @param {Object} security - Security analysis object */ function checkReentrancyPatterns(instructions, security) { // Look for pattern: external call followed by state change for (let i = 0; i < instructions.length - 5; i++) { const inst = instructions[i]; if (['CALL', 'CALLCODE'].includes(inst.opcode)) { // Look for SSTORE after the call for (let j = i + 1; j < Math.min(i + 20, instructions.length); j++) { if (instructions[j].opcode === 'SSTORE') { security.potentialVulnerabilities.push({ type: 'POTENTIAL_REENTRANCY', severity: 'MEDIUM', pc: inst.pc, description: 'External call followed by state change (potential reentrancy)' }); break; } } } } } /** * Check for integer overflow patterns * @param {Array} instructions - Decoded instructions * @param {Object} security - Security analysis object */ function checkOverflowPatterns(instructions, security) { // Look for arithmetic operations without safety checks for (let i = 0; i < instructions.length; i++) { const inst = instructions[i]; if (['ADD', 'MUL'].includes(inst.opcode)) { // Check if there's an overflow check nearby let hasCheck = false; // Look for LT/GT comparisons after arithmetic for (let j = i + 1; j < Math.min(i + 5, instructions.length); j++) { if (['LT', 'GT', 'SLT', 'SGT'].includes(instructions[j].opcode)) { hasCheck = true; break; } } if (!hasCheck) { security.potentialVulnerabilities.push({ type: 'UNCHECKED_ARITHMETIC', severity: 'LOW', pc: inst.pc, description: `Unchecked ${inst.opcode} operation (potential overflow)` }); } } } } /** * Check for access control patterns * @param {Array} instructions - Decoded instructions * @param {Object} analysis - Analysis results * @param {Object} security - Security analysis object */ function checkAccessControlPatterns(instructions, analysis, security) { // Check if contract has owner-like patterns const hasOwnerFunction = analysis.functions && analysis.functions.functions.some(f => f.signature === 'owner()'); // Look for CALLER checks before sensitive operations let callerChecks = 0; for (let i = 0; i < instructions.length; i++) { if (instructions[i].opcode === 'CALLER') { // Check if followed by EQ comparison for (let j = i + 1; j < Math.min(i + 5, instructions.length); j++) { if (instructions[j].opcode === 'EQ') { callerChecks++; break; } } } } if (hasOwnerFunction && callerChecks === 0) { security.potentialVulnerabilities.push({ type: 'MISSING_ACCESS_CONTROL', severity: 'MEDIUM', description: 'Contract has owner function but no caller checks detected' }); } } /** * Calculate overall security score * @param {Object} security - Security analysis results * @returns {number} Security score (0-100) */ function calculateSecurityScore(security) { let score = 100; // Deduct points for vulnerabilities for (const vuln of security.potentialVulnerabilities) { switch (vuln.severity) { case 'HIGH': score -= 20; break; case 'MEDIUM': score -= 10; break; case 'LOW': score -= 5; break; } } // Deduct for dangerous opcodes score -= security.dangerousOpcodes.length * 2; // Ensure score doesn't go below 0 return Math.max(0, score); } /** * Generate analysis summary * @param {Object} analysis - Complete analysis results * @returns {Object} Summary object */ function generateSummary(analysis) { const summary = { bytecodeSize: analysis.bytecodeSize, instructionCount: analysis.instructionCount, hasMetadata: !!analysis.metadata, compiler: analysis.metadata?.compiler || 'Unknown', compilerVersion: analysis.metadata?.version || 'Unknown' }; if (analysis.functions) { summary.functionCount = analysis.functions.totalFunctions; summary.knownFunctions = analysis.functions.knownFunctions; summary.detectedStandards = analysis.functions.contractPatterns.detectedStandards; } if (analysis.complexity) { summary.cyclomaticComplexity = analysis.complexity.cyclomaticComplexity; summary.maxStackDepth = analysis.complexity.maxStackDepth; } if (analysis.security) { summary.securityScore = analysis.security.score; summary.vulnerabilityCount = analysis.security.potentialVulnerabilities.length; summary.highSeverityIssues = analysis.security.potentialVulnerabilities .filter(v => v.severity === 'HIGH').length; } return summary; } module.exports = { analyzeBytecode, performSecurityAnalysisInternal, generateSummary };