예제 #1
0
        /// <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]);
                }
            }
        }
예제 #2
0
        /// <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);
                });
            }
        }
예제 #3
0
        /// <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
                    });
                }
            }
        }
예제 #4
0
        /// <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");
                    }
                }
            }
        }
예제 #5
0
파일: MbbsDll.cs 프로젝트: tuday2/MBBSEmu
        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);
        }
예제 #6
0
        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);
        }
예제 #7
0
        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);
        }
예제 #8
0
        /// <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}");
                    }
                }
            }
        }
예제 #9
0
        /// <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");
                    }
                }
            }
        }
예제 #10
0
 public static void Analyze(NEFile file)
 {
     ImportedFunctionIdentification(file);
     SubroutineIdentification(file);
     ForLoopIdentification(file);
 }
예제 #11
0
        /// <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;
                    }
                });
            });
        }
예제 #12
0
        /// <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;
            }
        }
예제 #13
0
        /// <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;
        }
예제 #14
0
        /// <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;
            }
        }
예제 #15
0
 /// <summary>
 ///     Default Constructor
 /// </summary>
 /// <param name="inputFile"></param>
 public StringRenderer(NEFile inputFile)
 {
     _inputFile = inputFile;
 }
예제 #16
0
        /// <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;
                }
            }
        }
예제 #17
0
 /// <inheritdoc />
 public void Dispose()
 {
     _inputFile = null;
 }
예제 #18
0
 public Disassembler(string inputFile)
 {
     _inputFile = new NEFile(inputFile);
 }
예제 #19
0
        /// <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
                    });
                }
            }
        }