/// <summary> /// Scans through DATA segments within the specified file extracting NULL terminated strings /// </summary> /// <param name="file"></param> private void ProcessStrings(NEFile file) { //Filter down potential segments foreach (var seg in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Data))) { seg.StringRecords = new List <StringRecord>(); var sbBuffer = new StringBuilder(); for (var i = 0; i < seg.Length; i++) { if (seg.Data[i] == 0x0) { if (sbBuffer.Length > 0) { seg.StringRecords.Add(new StringRecord { Segment = seg.Ordinal, Offset = i - sbBuffer.Length, Length = sbBuffer.Length, Value = sbBuffer.ToString() }); sbBuffer.Clear(); } continue; } sbBuffer.Append((char)seg.Data[i]); } } }
/// <summary> /// Locates offsets for exported functions in the Entry table and labels them /// </summary> /// <param name="file"></param> private void IdentifyEntryPoints(NEFile file) { foreach (var entry in file.EntryTable) { var seg = file.SegmentTable.First(x => x.Ordinal == entry.SegmentNumber); var fnName = file.NonResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal) ?.Name; if (string.IsNullOrEmpty(fnName)) { fnName = file.ResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal)?.Name; } seg.DisassemblyLines.Where(x => x.Disassembly.Offset == entry.Offset) .FirstOrDefault(x => { x.ExportedFunction = new ExportedFunctionRecord() { Name = fnName }; return(true); }); } }
/// <summary> /// Scans through the code and adds comments to any Call /// Labels the destination where the source came from /// </summary> /// <param name="file"></param> private void ResolveCallTargets(NEFile file) { foreach (var segment in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) { //Only processing 3 byte calls foreach (var j in segment.DisassemblyLines.Where(x => x.Disassembly.Bytes[0] == 0xE8 && x.Disassembly.Bytes.Length <= 3)) { ulong target = (ushort)(BitConverter.ToUInt16(j.Disassembly.Bytes, 1) + j.Disassembly.Offset + 3); //Set Target segment.DisassemblyLines.FirstOrDefault(x => x.Disassembly.Offset == target)?.BranchFromRecords.Add(new BranchRecord() { Segment = segment.Ordinal, Offset = j.Disassembly.Offset, BranchType = EnumBranchType.Call, IsRelocation = false }); //Set Origin j.BranchToRecords.Add(new BranchRecord() { Segment = segment.Ordinal, Offset = target, BranchType = EnumBranchType.Call, IsRelocation = false }); } } }
/// <summary> /// This method scans the disassembly for signatures of Turbo C++ FOR loops /// and labels them appropriatley /// </summary> /// <param name="file"></param> private static void ForLoopIdentification(NEFile file) { /* * Borland C++ compiled i++/i-- FOR loops look like: * inc word [var] * cmp word [var], condition (unconditional jump here from before beginning of for loop logic) * conditional jump to beginning of for * * So we'll search for this basic pattern */ _logger.Info($"Identifying FOR Loops"); //Scan the code segments foreach (var segment in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) { //Function Definition Identification Pass foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => x.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icmp && x.BranchFromRecords.Any(y => y.BranchType == EnumBranchType.Unconditional))) { if (MnemonicGroupings.IncrementDecrementGroup.Contains(segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Disassembly.Mnemonic) && segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal + 1).BranchToRecords.Count > 0 && segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal + 1).BranchToRecords.First(x => x.BranchType == EnumBranchType.Conditional) .Offset < disassemblyLine.Disassembly.Offset) { if (MnemonicGroupings.IncrementGroup.Contains(segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Disassembly .Mnemonic)) { segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Comments .Add("[FOR] Increment Value"); } else { segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Comments .Add("[FOR] Decrement Value"); } disassemblyLine.Comments.Add("[FOR] Evaluate Break Condition"); //Label beginning of FOR logic by labeling source of unconditional jump segment.DisassemblyLines .First(x => x.Disassembly.Offset == disassemblyLine.BranchFromRecords .First(y => y.BranchType == EnumBranchType.Unconditional).Offset).Comments .Add("[FOR] Beginning of FOR logic"); segment.DisassemblyLines .First(x => x.Ordinal == disassemblyLine.Ordinal + 1).Comments .Add("[FOR] Branch based on evaluation"); } } } }
public bool Load(string file, string path, IEnumerable <ModulePatch> modulePatches) { var neFile = _fileUtility.FindFile(path, $"{file}.DLL"); var fullNeFilePath = Path.Combine(path, neFile); if (!System.IO.File.Exists(fullNeFilePath)) { _logger.Warn($"Unable to Load {neFile}"); return(false); } var fileData = System.IO.File.ReadAllBytes(fullNeFilePath); if (modulePatches != null) { foreach (var p in modulePatches) { _logger.Info($"Applying Patch: {p.Name} to Absolute Offet {p.AbsoluteOffset}"); var bytesToPatch = p.GetBytes(); Array.Copy(bytesToPatch.ToArray(), 0, fileData, p.AbsoluteOffset, bytesToPatch.Length); } } File = new NEFile(_logger, fullNeFilePath, fileData); return(true); }
public bool Load(string file, string path, IEnumerable <ModulePatch> modulePatches) { var neFile = _fileUtility.FindFile(path, $"{file}.DLL"); var fullNeFilePath = Path.Combine(path, neFile); if (!System.IO.File.Exists(fullNeFilePath)) { _logger.Warn($"Unable to Load {neFile}"); return(false); } var fileData = System.IO.File.ReadAllBytes(fullNeFilePath); var fileCRC32 = BitConverter.ToString(new Crc32().ComputeHash(fileData)).Replace("-", string.Empty); //Absolute Offset Patching //We perform Absolute Patching here as this is the last stop before the data is loaded into the NE file and split into Segments if (modulePatches != null) { foreach (var p in modulePatches.Where(x => (bool)x?.Enabled && x.AbsoluteOffset > 0)) { if (string.Compare(p.CRC32, fileCRC32, StringComparison.InvariantCultureIgnoreCase) != 0) { _logger.Error($"Unable to apply patch {p.Name}: Module CRC32 Mismatch (Expected: {p.CRC32}, Actual: {fileCRC32})"); continue; } _logger.Info($"Applying Patch: {p.Name} to Absolute Offet {p.AbsoluteOffset}"); var bytesToPatch = p.GetBytes(); Array.Copy(bytesToPatch.ToArray(), 0, fileData, p.AbsoluteOffset, bytesToPatch.Length); } } File = new NEFile(_logger, fullNeFilePath, fileData); //Address Patching if (modulePatches != null) { foreach (var p in modulePatches.Where(x => (bool)x?.Enabled && x.Addresses.Count > 0)) { if (string.Compare(p.CRC32, fileCRC32, StringComparison.InvariantCultureIgnoreCase) != 0) { _logger.Error($"Unable to apply patch {p.Name}: Module CRC32 Mismatch (Expected: {p.CRC32}, Actual: {fileCRC32})"); continue; } foreach (var a in p.Addresses) { var bytesToPatch = p.GetBytes(); _logger.Info($"Applying Patch: {p.Name} to {a}"); Array.Copy(bytesToPatch.ToArray(), 0, File.SegmentTable.First(x => x.Ordinal == a.Segment).Data, a.Offset, bytesToPatch.Length); } } } return(true); }
public bool Load(string file, string path) { var neFile = _fileUtility.FindFile(path, $"{file}.DLL"); var fullNeFilePath = Path.Combine(path, neFile); if (!System.IO.File.Exists(fullNeFilePath)) { _logger.Warn($"Unable to Load {neFile}"); return(false); } File = new NEFile(_logger, fullNeFilePath); return(true); }
/// <summary> /// This method scans the disassembled code and identifies subroutines, labeling them /// appropriately. This also allows for much more precise variable/argument tracking /// if we properly know the scope of the routine. /// </summary> /// <param name="file"></param> private static void SubroutineIdentification(NEFile file) { _logger.Info($"Identifying Subroutines"); //Scan the code segments foreach (var segment in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) { ushort subroutineId = 0; var bInSubroutine = false; for (var i = 0; i < segment.DisassemblyLines.Count; i++) { if (bInSubroutine) { segment.DisassemblyLines[i].SubroutineID = subroutineId; } if (segment.DisassemblyLines[i].Disassembly.Mnemonic == ud_mnemonic_code.UD_Ienter || segment.DisassemblyLines[i].BranchFromRecords.Any(x => x.BranchType == EnumBranchType.Call) || segment.DisassemblyLines[i].ExportedFunction != null || //Previous instruction was the end of a subroutine, we must be starting one that's not //referenced anywhere in the code (i > 0 && (segment.DisassemblyLines[i - 1].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iretf || segment.DisassemblyLines[i - 1].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iret))) { subroutineId++; bInSubroutine = true; segment.DisassemblyLines[i].SubroutineID = subroutineId; segment.DisassemblyLines[i].Comments.Insert(0, $"/---- BEGIN SUBROUTINE {subroutineId}"); continue; } if (bInSubroutine && (segment.DisassemblyLines[i].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iret || segment.DisassemblyLines[i].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iretf)) { bInSubroutine = false; segment.DisassemblyLines[i].Comments.Insert(0, $"\\---- END SUBROUTINE {subroutineId}"); } } } }
/// <summary> /// Identification Routine for MBBS/WG Imported Functions /// </summary>s /// <param name="file"></param> private static void ImportedFunctionIdentification(NEFile file) { _logger.Info($"Identifying Imported Functions"); if (!file.ImportedNameTable.Any(nt => ModuleDefinitions.Select(md => md.Name).Contains(nt.Name))) { _logger.Info($"No known Module Definitions found in target file, skipping Imported Function Identification"); return; } var trackedVariables = new List <TrackedVariable>(); //Identify Functions and Label them with the module defition file foreach (var segment in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) { //Function Definition Identification Pass //Loop through each Disassembly Line in the segment that has a BranchType of CallImport or SegAddrImport foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => x.BranchToRecords.Any(y => y.BranchType == EnumBranchType.CallImport || y.BranchType == EnumBranchType.SegAddrImport))) { //Get The Import on the Current Line var currentImport = disassemblyLine.BranchToRecords.First(z => z.BranchType == EnumBranchType.CallImport || z.BranchType == EnumBranchType.SegAddrImport); //Find the module it maps to in the ImportedNameTable var currentModule = ModuleDefinitions.FirstOrDefault(x => x.Name == file.ImportedNameTable.FirstOrDefault(y => y.Ordinal == currentImport.Segment)?.Name); //Usually DOS header stub will trigger this if (currentModule == null) { continue; } //Find the matching export by ordinal in one of the loaded Module JSON files var definition = currentModule.Exports.FirstOrDefault(x => x.Ord == currentImport.Offset); //Didn't have a definition for it? if (definition == null) { continue; } //We'll replace the old external reference with ordinal with the actual function name/sig disassemblyLine.Comments.Add(!string.IsNullOrEmpty(definition.Signature) ? definition.Signature : $"{currentModule.Name}.{definition.Name}"); //Attempt to Resolve the actual Method Signature if we have the definition in the JSON doc for this method if (!string.IsNullOrEmpty(definition.SignatureFormat) && definition.PrecedingInstructions != null && definition.PrecedingInstructions.Count > 0) { var values = new List <object>(); foreach (var pi in definition.PrecedingInstructions) { //Check to see if the expected opcode is in the expected location var i = segment.DisassemblyLines.FirstOrDefault(x => x.Ordinal == disassemblyLine.Ordinal + pi.Offset && x.Disassembly.Mnemonic.ToString().ToUpper().EndsWith(pi.Op)); if (i == null) { break; } //If we know the type, attempt to cast the operand switch (pi.Type) { case "int": values.Add(i.Disassembly.Operands[0].LvalSDWord); break; case "string": if (i.Comments.Any(x => x.Contains("reference"))) { var resolvedStringComment = i.Comments.First(x => x.Contains("reference")); values.Add(resolvedStringComment.Substring( resolvedStringComment.IndexOf('\"'))); } break; case "char": values.Add((char)i.Disassembly.Operands[0].LvalSDWord); break; } } //Only add the resolved signature if we correctly identified all the values we were expecting if (values.Count == definition.PrecedingInstructions.Count) { disassemblyLine.Comments.Add(string.Format($"Resolved Signature: {definition.SignatureFormat}", values.Select(x => x.ToString()).ToArray())); } } //Attempt to resolve a variable this method might be saving as defined in the JSON doc if (definition.ReturnValues != null && definition.ReturnValues.Count > 0) { foreach (var rv in definition.ReturnValues) { var i = segment.DisassemblyLines.FirstOrDefault(x => x.Ordinal == disassemblyLine.Ordinal + rv.Offset && x.Disassembly.Mnemonic.ToString().ToUpper().EndsWith(rv.Op)); if (i == null) { break; } i.Comments.Add($"Return value saved to 0x{i.Disassembly.Operands[0].LvalUWord:X}h"); if (!string.IsNullOrEmpty(rv.Comment)) { i.Comments.Add(rv.Comment); } //Add this to our tracked variables, we'll go back through and re-label all instances after this analysis pass trackedVariables.Add(new TrackedVariable() { Comment = rv.Comment, Segment = segment.Ordinal, Offset = i.Disassembly.Offset, Address = i.Disassembly.Operands[0].LvalUWord }); } } //Finally, append any comments that accompany the function definition if (definition.Comments != null && definition.Comments.Count > 0) { disassemblyLine.Comments.AddRange(definition.Comments); } } //Variable Tracking Labeling Pass foreach (var v in trackedVariables) { foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => x.Disassembly.ToString().Contains($"[0x{v.Address:X}]".ToLower()) && x.Disassembly.Offset != v.Offset)) { disassemblyLine.Comments.Add($"Reference to variable created at {v.Segment:0000}.{v.Offset:X4}h"); } } } }
public static void Analyze(NEFile file) { ImportedFunctionIdentification(file); SubroutineIdentification(file); ForLoopIdentification(file); }
/// <summary> /// Reads the Relocation Table (if present) at the end of a segment and comments about the relocations that /// are being applied. This identifies both internal and external function calls. /// </summary> /// <param name="file"></param> private void ApplyRelocationInfo(NEFile file) { Parallel.ForEach(file.SegmentTable, (segment) => { if (!segment.Flags.Contains(EnumSegmentFlags.Code) && !segment.Flags.Contains(EnumSegmentFlags.HasRelocationInfo)) { return; } Parallel.ForEach(segment.RelocationRecords, (relocationRecord) => { var disAsm = segment.DisassemblyLines.FirstOrDefault(x => x.Disassembly.Offset == relocationRecord.Offset - 1UL); if (disAsm == null) { return; } switch (relocationRecord.Flag) { case EnumRecordsFlag.IMPORTORDINAL | EnumRecordsFlag.ADDITIVE: case EnumRecordsFlag.IMPORTORDINAL: disAsm.BranchToRecords.Add(new BranchRecord { IsRelocation = true, BranchType = disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall ? EnumBranchType.CallImport : EnumBranchType.SegAddrImport, Segment = relocationRecord.TargetTypeValueTuple.Item2, Offset = relocationRecord.TargetTypeValueTuple.Item3 }); break; case EnumRecordsFlag.INTERNALREF | EnumRecordsFlag.ADDITIVE: case EnumRecordsFlag.INTERNALREF: if (disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall) { //Set Target file.SegmentTable .FirstOrDefault(x => x.Ordinal == relocationRecord.TargetTypeValueTuple.Item2) ?.DisassemblyLines .FirstOrDefault(y => y.Disassembly.Offset == relocationRecord.TargetTypeValueTuple.Item4) ?.BranchFromRecords .Add(new BranchRecord() { Segment = segment.Ordinal, Offset = disAsm.Disassembly.Offset, IsRelocation = true, BranchType = EnumBranchType.Call }); //Set Origin disAsm.BranchToRecords.Add(new BranchRecord() { Segment = relocationRecord.TargetTypeValueTuple.Item2, Offset = relocationRecord.TargetTypeValueTuple.Item4, BranchType = EnumBranchType.Call, IsRelocation = true }); } else { disAsm.BranchToRecords.Add(new BranchRecord() { IsRelocation = true, BranchType = EnumBranchType.SegAddr, Segment = relocationRecord.TargetTypeValueTuple.Item2 }); } break; case EnumRecordsFlag.IMPORTNAME: disAsm.BranchToRecords.Add(new BranchRecord { IsRelocation = true, BranchType = EnumBranchType.CallImport, Segment = relocationRecord.TargetTypeValueTuple.Item3 }); break; case EnumRecordsFlag.TARGET_MASK: break; } }); }); }
/// <summary> /// Constructor for MbbsModule /// /// Pass in an empty/blank moduleIdentifier for a Unit Test/Fake Module /// </summary> /// <param name="logger"></param> /// <param name="moduleIdentifier">Will be null in a test</param> /// <param name="path"></param> /// <param name="memoryCore"></param> /// <param name="fileUtility"></param> public MbbsModule(IFileUtility fileUtility, IClock clock, ILogger logger, string moduleIdentifier, string path = "", MemoryCore memoryCore = null) { _fileUtility = fileUtility; _logger = logger; _clock = clock; ModuleIdentifier = moduleIdentifier; ModuleDlls = new List <MbbsDll>(); //Sanitize and setup Path if (string.IsNullOrEmpty(path)) { path = Directory.GetCurrentDirectory(); } if (!Path.EndsInDirectorySeparator(path)) { path += Path.DirectorySeparatorChar; } ModulePath = path; // will be null in tests if (string.IsNullOrEmpty(ModuleIdentifier)) { Mdf = MdfFile.createForTest(); ModuleDlls.Add(new MbbsDll(fileUtility, logger) { File = NEFile.createForTest() }); } else { //Verify MDF File Exists var mdfFile = fileUtility.FindFile(ModulePath, $"{ModuleIdentifier}.MDF"); var fullMdfFilePath = Path.Combine(ModulePath, mdfFile); if (!System.IO.File.Exists(fullMdfFilePath)) { throw new FileNotFoundException($"Unable to locate Module: {fullMdfFilePath}"); } Mdf = new MdfFile(fullMdfFilePath); var moduleDll = new MbbsDll(fileUtility, logger); moduleDll.Load(Mdf.DLLFiles[0].Trim(), ModulePath); ModuleDlls.Add(moduleDll); if (Mdf.Requires.Count > 0) { foreach (var r in Mdf.Requires) { var requiredDll = new MbbsDll(fileUtility, logger); if (requiredDll.Load(r.Trim(), ModulePath)) { requiredDll.SegmentOffset = (ushort)(ModuleDlls.Sum(x => x.File.SegmentTable.Count) + 1); ModuleDlls.Add(requiredDll); } } } if (Mdf.MSGFiles.Count > 0) { Msgs = new List <MsgFile>(Mdf.MSGFiles.Count); foreach (var m in Mdf.MSGFiles) { Msgs.Add(new MsgFile(ModulePath, m)); } } } //Set Initial Values RtkickRoutines = new PointerDictionary <RealTimeRoutine>(); RtihdlrRoutines = new PointerDictionary <RealTimeRoutine>(); TaskRoutines = new PointerDictionary <RealTimeRoutine>(); TextVariables = new Dictionary <string, FarPtr>(); GlobalCommandHandlers = new List <FarPtr>(); ExportedModuleDictionary = new Dictionary <ushort, IExportedModule>(6); ExecutionUnits = new Queue <ExecutionUnit>(2); Memory = memoryCore ?? new MemoryCore(); //Declare PSP Segment var psp = new PSPStruct { NextSegOffset = 0x9FFF, EnvSeg = 0xFFFF }; Memory.AddSegment(0x4000); Memory.SetArray(0x4000, 0, psp.Data); Memory.AllocateVariable("Int21h-PSP", sizeof(ushort)); Memory.SetWord("Int21h-PSP", 0x4000); //Find _INIT_ values if any foreach (var dll in ModuleDlls) { //If it's a Test, setup a fake _INIT_ if (string.IsNullOrEmpty(ModuleIdentifier)) { dll.EntryPoints["_INIT_"] = null; return; } //Setup _INIT_ Entrypoint FarPtr initEntryPointPointer; var initResidentName = dll.File.ResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initResidentName == null) { //This only happens with MajorMUD -- I have no idea why it's a special little snowflake ¯\_(ツ)_/¯ _logger.Warn($"({moduleIdentifier}) Unable to locate _INIT_ in Resident Name Table, checking Non-Resident Name Table..."); var initNonResidentName = dll.File.NonResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initNonResidentName == null) { throw new Exception("Unable to locate _INIT__ entry in Resident Name Table"); } var initEntryPoint = dll.File.EntryTable.First(x => x.Ordinal == initNonResidentName.IndexIntoEntryTable); initEntryPointPointer = new FarPtr((ushort)(initEntryPoint.SegmentNumber + dll.SegmentOffset), initEntryPoint.Offset); } else { var initEntryPoint = dll.File.EntryTable.First(x => x.Ordinal == initResidentName.IndexIntoEntryTable); initEntryPointPointer = new FarPtr((ushort)(initEntryPoint.SegmentNumber + dll.SegmentOffset), initEntryPoint.Offset); } _logger.Debug($"({ModuleIdentifier}) Located _INIT__: {initEntryPointPointer}"); dll.EntryPoints["_INIT_"] = initEntryPointPointer; } }
/// <summary> /// Constructor for MbbsModule /// /// Pass in an empty/blank moduleIdentifier for a Unit Test/Fake Module /// </summary> /// <param name="moduleIdentifier"></param> /// <param name="path"></param> /// <param name="memoryCore"></param> public MbbsModule(string moduleIdentifier, string path = "", MemoryCore memoryCore = null) { ModuleIdentifier = moduleIdentifier; //Sanitize and setup Path if (string.IsNullOrEmpty(path)) { path = Directory.GetCurrentDirectory(); } if (!path.EndsWith(Path.DirectorySeparatorChar)) { path += Path.DirectorySeparatorChar; } ModulePath = path; //Verify MDF File Exists if (!string.IsNullOrEmpty(ModuleIdentifier) && !System.IO.File.Exists($"{ModulePath}{ModuleIdentifier}.MDF")) { throw new FileNotFoundException($"Unable to locate Module: {ModulePath}{ModuleIdentifier}.MDF"); } Mdf = !string.IsNullOrEmpty(ModuleIdentifier) ? new MdfFile($"{ModulePath}{ModuleIdentifier}.MDF") : MdfFile.createForTest(); File = !string.IsNullOrEmpty(ModuleIdentifier) ? new NEFile($"{ModulePath}{Mdf.DLLFiles[0].Trim()}.DLL") : NEFile.createForTest(); if (Mdf.MSGFiles.Count > 0) { Msgs = new List <MsgFile>(Mdf.MSGFiles.Count); foreach (var m in Mdf.MSGFiles) { Msgs.Add(new MsgFile(ModulePath, m)); } } //Set Initial Values EntryPoints = new Dictionary <string, IntPtr16>(); RtkickRoutines = new PointerDictionary <RealTimeRoutine>(); RtihdlrRoutines = new PointerDictionary <RealTimeRoutine>(); TaskRoutines = new PointerDictionary <RealTimeRoutine>(); TextVariables = new Dictionary <string, IntPtr16>(); ExecutionUnits = new Queue <ExecutionUnit>(2); ExportedModuleDictionary = new Dictionary <ushort, IExportedModule>(4); GlobalCommandHandlers = new List <IntPtr16>(); Memory = memoryCore ?? new MemoryCore(); //If it's a Test, setup a fake _INIT_ if (string.IsNullOrEmpty(ModuleIdentifier)) { EntryPoints["_INIT_"] = null; return; } //Setup _INIT_ Entrypoint IntPtr16 initEntryPointPointer; var initResidentName = File.ResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initResidentName == null) { //This only happens with MajorMUD -- I have no idea why it's a special little snowflake ¯\_(ツ)_/¯ _logger.Warn("Unable to locate _INIT_ in Resident Name Table, checking Non-Resident Name Table..."); var initNonResidentName = File.NonResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initNonResidentName == null) { throw new Exception("Unable to locate _INIT__ entry in Resident Name Table"); } var initEntryPoint = File.EntryTable.First(x => x.Ordinal == initNonResidentName.IndexIntoEntryTable); initEntryPointPointer = new IntPtr16(initEntryPoint.SegmentNumber, initEntryPoint.Offset); } else { var initEntryPoint = File.EntryTable.First(x => x.Ordinal == initResidentName.IndexIntoEntryTable); initEntryPointPointer = new IntPtr16(initEntryPoint.SegmentNumber, initEntryPoint.Offset); } _logger.Info($"Located _INIT__: {initEntryPointPointer}"); EntryPoints["_INIT_"] = initEntryPointPointer; }
/// <summary> /// Constructor for MbbsModule /// /// Pass in an empty/blank moduleIdentifier for a Unit Test/Fake Module /// </summary> /// <param name="fileUtility"></param> /// <param name="clock"></param> /// <param name="logger"></param> /// <param name="moduleConfig"></param> /// <param name="memoryCore"></param> public MbbsModule(IFileUtility fileUtility, IClock clock, ILogger logger, ModuleConfiguration moduleConfig, ProtectedModeMemoryCore memoryCore = null) { _fileUtility = fileUtility; _logger = logger; _clock = clock; ModuleConfig = moduleConfig; ModuleIdentifier = moduleConfig.ModuleIdentifier; ModuleDlls = new List <MbbsDll>(); //Sanitize and setup Path if (string.IsNullOrEmpty(moduleConfig.ModulePath)) { moduleConfig.ModulePath = Directory.GetCurrentDirectory(); } if (!Path.EndsInDirectorySeparator(moduleConfig.ModulePath)) { moduleConfig.ModulePath += Path.DirectorySeparatorChar; } ModulePath = moduleConfig.ModulePath; // will be null in tests if (string.IsNullOrEmpty(ModuleIdentifier)) { Mdf = MdfFile.createForTest(); ModuleDlls.Add(new MbbsDll(fileUtility, logger) { File = NEFile.createForTest() }); } else { //Verify Module Path Exists if (!Directory.Exists(ModulePath)) { _logger.Error($"Unable to find the specified directory for the module {ModuleIdentifier.ToUpper()}: {ModulePath}"); _logger.Error("Please verify your Command Line Argument or the path specified in your Module JSON File and try again."); throw new DirectoryNotFoundException($"Unable to locate {ModulePath}"); } //Verify MDF File Exists var mdfFile = fileUtility.FindFile(ModulePath, $"{ModuleIdentifier}.MDF"); var fullMdfFilePath = Path.Combine(ModulePath, mdfFile); if (!File.Exists(fullMdfFilePath)) { _logger.Error($"Unable to locate {fullMdfFilePath}"); _logger.Error($"Please verify your Command Line Argument or the Module JSON File to ensure {ModuleIdentifier} is the correct Module Identifier and that the Module is installed properly."); throw new FileNotFoundException($"Unable to locate Module: {fullMdfFilePath}"); } Mdf = new MdfFile(fullMdfFilePath); LoadModuleDll(Mdf.DLLFiles[0].Trim()); if (Mdf.MSGFiles.Count > 0) { Msgs = new List <MsgFile>(Mdf.MSGFiles.Count); foreach (var m in Mdf.MSGFiles) { Msgs.Add(new MsgFile(_fileUtility, ModulePath, m)); } } } //Set Initial Values RtkickRoutines = new PointerDictionary <RealTimeRoutine>(); RtihdlrRoutines = new PointerDictionary <RealTimeRoutine>(); TaskRoutines = new PointerDictionary <RealTimeRoutine>(); GlobalCommandHandlers = new List <FarPtr>(); ExportedModuleDictionary = new Dictionary <ushort, IExportedModule>(6); ExecutionUnits = new Queue <ExecutionUnit>(2); Memory = memoryCore ?? new ProtectedModeMemoryCore(logger); ProtectedMemory = (ProtectedModeMemoryCore)Memory; //Declare PSP Segment var psp = new PSPStruct { NextSegOffset = 0x9FFF, EnvSeg = 0xFFFF }; ProtectedMemory.AddSegment(0x4000); Memory.SetArray(0x4000, 0, psp.Data); Memory.AllocateVariable("Int21h-PSP", sizeof(ushort)); Memory.SetWord("Int21h-PSP", 0x4000); //Find _INIT_ values if any foreach (var dll in ModuleDlls) { //If it's a Test, setup a fake _INIT_ if (string.IsNullOrEmpty(ModuleIdentifier)) { dll.EntryPoints["_INIT_"] = null; return; } //Setup _INIT_ Entrypoint FarPtr initEntryPointPointer; var initResidentName = dll.File.ResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initResidentName == null) { //This only happens with MajorMUD -- I have no idea why it's a special little snowflake ¯\_(ツ)_/¯ _logger.Warn($"({moduleConfig.ModuleIdentifier}) Unable to locate _INIT_ in Resident Name Table, checking Non-Resident Name Table..."); var initNonResidentName = dll.File.NonResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initNonResidentName == null) { _logger.Error($"Unable to locate _INIT__ entry in Resident Name Table for {dll.File.FileName}"); continue; } var initEntryPoint = dll.File.EntryTable.First(x => x.Ordinal == initNonResidentName.IndexIntoEntryTable); initEntryPointPointer = new FarPtr((ushort)(initEntryPoint.SegmentNumber + dll.SegmentOffset), initEntryPoint.Offset); } else { var initEntryPoint = dll.File.EntryTable.First(x => x.Ordinal == initResidentName.IndexIntoEntryTable); initEntryPointPointer = new FarPtr((ushort)(initEntryPoint.SegmentNumber + dll.SegmentOffset), initEntryPoint.Offset); } _logger.Debug($"({ModuleIdentifier}) Located _INIT__: {initEntryPointPointer}"); dll.EntryPoints["_INIT_"] = initEntryPointPointer; } }
/// <summary> /// Default Constructor /// </summary> /// <param name="inputFile"></param> public StringRenderer(NEFile inputFile) { _inputFile = inputFile; }
/// <summary> /// This looks at the op and operand of the instructions and makes a best guess at the instructions that are referencing string data /// We inspect any instruction that interacts with the DX or DS regstiers, as these hold the data segments and then look at the address /// being referenced by that instruction. If we find a string at the address specified in any of the data segments, we'll return it as a possibility. /// </summary> /// <param name="file"></param> private void ResolveStringReferences(NEFile file) { var flagNext = false; var dataSegmentToUse = 0; foreach (var segment in file.SegmentTable) { if (!segment.Flags.Contains(EnumSegmentFlags.Code) || segment.DisassemblyLines == null || segment.DisassemblyLines.Count == 0) { continue; } foreach (var disassemblyLine in segment.DisassemblyLines) { //mov opcode if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Imov && //Filter out any mov's with relative register math, mostly false positives !disassemblyLine.Disassembly.ToString().Contains("-") && !disassemblyLine.Disassembly.ToString().Contains("+") && !disassemblyLine.Disassembly.ToString().Contains(":")) { //MOV ax, SEG ADDR sets the current Data Segment to use if (disassemblyLine.BranchToRecords.Any(x => x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) { dataSegmentToUse = disassemblyLine.BranchToRecords.First().Segment; } if (dataSegmentToUse > 0) { //mov dx, #### if (disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && disassemblyLine.Disassembly.Operands.Length == 2 && disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) { disassemblyLine.StringReference = file.SegmentTable .First(x => x.Ordinal == dataSegmentToUse).StringRecords.Where(y => y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord).ToList(); continue; } //mov ax, #### if (flagNext && disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_AX && disassemblyLine.Disassembly.Operands.Length == 2 && disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) { flagNext = false; disassemblyLine.StringReference = file.SegmentTable .First(x => x.Ordinal == dataSegmentToUse).StringRecords.Where(y => y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord).ToList(); continue; } //mov dx, ds is usually followed by a mov ax, #### which is a string reference if (disassemblyLine.Disassembly.Operands.Length == 2 && disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && disassemblyLine.Disassembly.Operands[1].Base == ud_type.UD_R_DS) { flagNext = true; continue; } } } if (dataSegmentToUse >= 0) { //push #### following a push ds if (flagNext && disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && disassemblyLine.Disassembly.Operands[0].LvalUWord > 0) { flagNext = false; var potential = new List <StringRecord>(); foreach (var s in file.SegmentTable.Where(x => x.StringRecords != null)) { if (s.StringRecords.Any(x => x.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord)) { potential.Add(s.StringRecords.First(x => x.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord)); } } disassemblyLine.StringReference = potential.Where(x => x.IsPrintable).ToList(); continue; } //push ds followed by a push #### if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && disassemblyLine.Disassembly.Operands.Any(x => x.Base == ud_type.UD_R_DS)) { flagNext = true; continue; } } flagNext = false; } } }
/// <inheritdoc /> public void Dispose() { _inputFile = null; }
public Disassembler(string inputFile) { _inputFile = new NEFile(inputFile); }
/// <summary> /// Scans through the disassembled code and adds comments on any Conditional or Unconditional Jump /// Labels the destination where the source came from /// </summary> /// <param name="file"></param> private void ResolveJumpTargets(NEFile file) { //Setup variables to make if/where clauses much easier to read var jumpShortOps = new[] { 0xEB, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0xE3 }; var jumpNearOps1stByte = new[] { 0xE9, 0x0F }; var jumpNearOps2ndByte = new[] { 0x80, 0x81, 0x82, 0x83, 0x84, 0x5, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F }; foreach (var segment in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) { //Only op+operand <= 3 bytes, skip jmp word ptr because we won't be able to label those foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => MnemonicGroupings.JumpGroup.Contains(x.Disassembly.Mnemonic) && x.Disassembly.Bytes.Length <= 3)) { ulong target = 0; //Jump Short, Relative to next Instruction (8 bit) if (jumpShortOps.Contains(disassemblyLine.Disassembly.Bytes[0])) { target = ToRelativeOffset8(disassemblyLine.Disassembly.Bytes[1], disassemblyLine.Disassembly.Offset, disassemblyLine.Disassembly.Bytes.Length); } //Jump Near, Relative to next Instruction (16 bit) //Check to see if it's a 1 byte unconditinoal or a 2 byte conditional if (jumpNearOps1stByte.Contains(disassemblyLine.Disassembly.Bytes[0]) && (disassemblyLine.Disassembly.Bytes[0] == 0xE9 || jumpNearOps2ndByte.Contains(disassemblyLine.Disassembly.Bytes[1]))) { target = ToRelativeOffset16(BitConverter.ToUInt16(disassemblyLine.Disassembly.Bytes, disassemblyLine.Disassembly.Bytes[0] == 0xE9 ? 1 : 2), disassemblyLine.Disassembly.Offset, disassemblyLine.Disassembly.Bytes.Length); } //Set Target segment.DisassemblyLines.FirstOrDefault(x => x.Disassembly.Offset == target)?.BranchFromRecords .Add(new BranchRecord { Segment = segment.Ordinal, Offset = disassemblyLine.Disassembly.Offset, BranchType = disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp ? EnumBranchType.Unconditional : EnumBranchType.Conditional, IsRelocation = false }); //Set Origin disassemblyLine.BranchToRecords.Add(new BranchRecord { Segment = segment.Ordinal, Offset = target, BranchType = disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp ? EnumBranchType.Unconditional : EnumBranchType.Conditional, IsRelocation = false }); } } }