diff --git a/src/apphost-extract/apphost-extract-v2/Analyzer.cs b/src/apphost-extract/apphost-extract-v2/Analyzer.cs index 5bacd80..1679ca7 100644 --- a/src/apphost-extract/apphost-extract-v2/Analyzer.cs +++ b/src/apphost-extract/apphost-extract-v2/Analyzer.cs @@ -1,5 +1,8 @@ -using System; +using apphost_extract_v2.General; +using apphost_extract_v2.Models; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection.PortableExecutable; @@ -12,8 +15,8 @@ namespace apphost_extract_v2 private FileStream File; public PEHeaders PEHeader; - private readonly byte[] VERSION_SIGNATURE = new byte[] { }; - private const string VERSION_SIGNATURE_MASK = ""; + private readonly byte[] VERSION_SIGNATURE = new byte[] { 0x4C, 0x8D, 0x05, 0x0, 0x0, 0x0, 0x0, 0x48, 0x8D, 0x15, 0x0, 0x0, 0x0, 0x0, 0x48, 0x8D, 0x0D, 0x0, 0x0, 0x0, 0x0 }; + private const string VERSION_SIGNATURE_MASK = "xxx???xxxx???xxxx???x"; public Analyzer(FileStream fs) { @@ -21,23 +24,77 @@ namespace apphost_extract_v2 PEHeader = new PEHeaders(fs); } + public IApphostFile Open() + { + var textSegment = GetSegment(".text"); + var sw = Stopwatch.StartNew(); + var sigscanResults = Util.PatternScan(File, + textSegment.PointerToRawData, textSegment.SizeOfRawData, + VERSION_SIGNATURE, VERSION_SIGNATURE_MASK); + sw.Stop(); + Log.Info($"Scan took {sw.ElapsedMilliseconds}ms | Version String Instructions -> {sigscanResults[0]:X8}"); + if (sigscanResults.Length == 0) + return null; + + var versionOffset = (int)BitConverter.ToUInt32(File.ReadBuffer(sigscanResults[0] + 3, 4)); + var versionStringPtr = AddVirtualOffset(sigscanResults[0] + 7, versionOffset); + var versionString = Encoding.Unicode.GetString( + File.ReadBuffer( + versionStringPtr, 6)); + + Log.Info($"Translated virtual offset -> {versionStringPtr:X8}: {versionString}"); + + switch (versionString) + { + case "3.0": + return new ApphostFile30(File, PEHeader); + + case "3.1": + return new ApphostFile31(File, PEHeader); + + case "5.0": + return new ApphostFile5(File, PEHeader); + default: + return null; + } + } + + private int AddVirtualOffset(int fileAddress, int offset) + { + return VirtualAddressToFileOffset(FileOffsetToVirtualAddress(fileAddress) + offset); + } + + private int FileOffsetToVirtualAddress(int offset) + { + var section = FindSection(offset, true); + return offset + (section.VirtualAddress - section.PointerToRawData); + } + + private int VirtualAddressToFileOffset(int address) + { + var section = FindSection(address, false); + return address - (section.VirtualAddress - section.PointerToRawData); + } + public SectionHeader GetSegment(string name) { var section = PEHeader.SectionHeaders.Where(x => x.Name == name).FirstOrDefault(); return section; } - public ApphostVersion GetVersion() + public SectionHeader FindSection(int address, bool fileOffset) { + foreach (var section in PEHeader.SectionHeaders) + { + if (fileOffset) + { + if (section.PointerToRawData < address && section.PointerToRawData + section.SizeOfRawData > address) return section; + } + else + if (section.VirtualAddress < address && section.VirtualAddress + section.VirtualSize > address) return section; + } + return new SectionHeader(); } - - } - - public enum ApphostVersion - { - NET30, - NET31, - NET5 } } diff --git a/src/apphost-extract/apphost-extract-v2/Models/ApphostFile30.cs b/src/apphost-extract/apphost-extract-v2/Models/ApphostFile30.cs new file mode 100644 index 0000000..49a87b9 --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/ApphostFile30.cs @@ -0,0 +1,21 @@ +using apphost_extract_v2.General; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace apphost_extract_v2.Models +{ + public class ApphostFile30 : IApphostFile + { + public ApphostFile30(FileStream fs, PEHeaders peheader) : base(fs, peheader) { } + + public override void Close() + { + throw new NotImplementedException(); + } + + + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/ApphostFile31.cs b/src/apphost-extract/apphost-extract-v2/Models/ApphostFile31.cs new file mode 100644 index 0000000..422ba2d --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/ApphostFile31.cs @@ -0,0 +1,69 @@ +using apphost_extract_v2.General; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace apphost_extract_v2.Models +{ + public class ApphostFile31 : IApphostFile + { + private const int HEADER_OFFSET_PTR = 0x27600; + private const int HEADER_SIZE = 0xD; + private const int FILE_ENTRY_SIZE = 0x12; + + public ApphostFile31(FileStream fs, PEHeaders peheader) : base(fs, peheader) + { + Header = new AppHostFileHeader(); + var headerAddress = BitConverter.ToInt32(fs.ReadBuffer(HEADER_OFFSET_PTR, 4)); + var headerBuffer = fs.ReadBuffer(headerAddress, HEADER_SIZE); + + Header.Raw = headerBuffer; + Header.Path = Encoding.UTF8.GetString(fs.ReadBuffer(headerAddress + HEADER_SIZE, 0xC)); + + Header.Manifest = ParseManifest(); + + } + + private AppHostManifest ParseManifest() + { + AppHostManifest manifest = new AppHostManifest(); + var embeddedFileCount = BitConverter.ToInt32(Header.Raw, 0x8); + + for(int i = 0; i< embeddedFileCount; i++) + { + manifest.FileEntries.Add(GetNextEntry()); + } + + return manifest; + } + + private AppHostFileEntry GetNextEntry() + { + AppHostFileEntry entry = new AppHostFileEntry(); + byte[] entryBuffer = new byte[FILE_ENTRY_SIZE]; + FileStream.Read(entryBuffer, 0, entryBuffer.Length); + entry.Raw = entryBuffer; + + entry.Offset = BitConverter.ToInt64(entry.Raw, 0); + + //hopefully nobody embeds a file larger than 2GB :D + entry.Size = (int)BitConverter.ToInt64(entry.Raw, 0x8); + + byte[] stringBuffer = new byte[entry.Raw[0x11]]; + FileStream.Read(stringBuffer, 0, stringBuffer.Length); + entry.Name = Encoding.UTF8.GetString(stringBuffer); + + return entry; + } + + + + public override void Close() + { + FileStream.Close(); + } + + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/ApphostFile5.cs b/src/apphost-extract/apphost-extract-v2/Models/ApphostFile5.cs new file mode 100644 index 0000000..a3a14be --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/ApphostFile5.cs @@ -0,0 +1,20 @@ +using apphost_extract_v2.General; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace apphost_extract_v2.Models +{ + public class ApphostFile5 : IApphostFile + { + public ApphostFile5(FileStream fs, PEHeaders peheader) : base(fs, peheader) { } + + public override void Close() + { + throw new NotImplementedException(); + } + + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/General/AppHostFileEntry.cs b/src/apphost-extract/apphost-extract-v2/Models/General/AppHostFileEntry.cs new file mode 100644 index 0000000..cd28d83 --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/General/AppHostFileEntry.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace apphost_extract_v2.General +{ + public class AppHostFileEntry + { + public long Offset { get; set; } + public int Size { get; set; } + public string Name { get; set; } + + public byte[] Raw; + + + //public AppHostFileEntry() + //{ + // FileStream = File; + // byte[] entryBuffer = new byte[FILE_ENTRY_SIZE]; + // File.Read(entryBuffer, 0, entryBuffer.Length); + // Raw = entryBuffer; + + // Offset = BitConverter.ToInt64(Raw, 0); + + // //hopefully nobody embeds a file larger than 2GB :D + // Size = (int)BitConverter.ToInt64(Raw, 0x8); + + // byte[] stringBuffer = new byte[Raw[0x11]]; + // File.Read(stringBuffer, 0, stringBuffer.Length); + // Name = Encoding.UTF8.GetString(stringBuffer); + //} + + //public byte[] Read() + //{ + // //jumps to the offsets and reads the bytes + // byte[] buffer = new byte[Size]; + // FileStream.Seek(Offset, SeekOrigin.Begin); + // FileStream.Read(buffer, 0, Size); + // return buffer; + //} + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/General/AppHostFileHeader.cs b/src/apphost-extract/apphost-extract-v2/Models/General/AppHostFileHeader.cs new file mode 100644 index 0000000..f1731cb --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/General/AppHostFileHeader.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace apphost_extract_v2.General +{ + public class AppHostFileHeader + { + public byte[] Raw; + + public string Path { get; set; } + + public AppHostManifest Manifest { get; set; } + + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/General/AppHostManifest.cs b/src/apphost-extract/apphost-extract-v2/Models/General/AppHostManifest.cs new file mode 100644 index 0000000..bdc32e6 --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/General/AppHostManifest.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace apphost_extract_v2.General +{ + public class AppHostManifest + { + public List FileEntries { get; set; } + + public AppHostManifest() + { + FileEntries = new List(); + //List entries = new List(); + + //for (int i = 0; i < embeddedFileCount; i++) + //{ + // AppHostFileEntry entry = new AppHostFileEntry(File); + // entries.Add(entry); + //} + //FileEntries = entries.AsReadOnly(); + + //Log.Info("Manifest parsed successfully!"); + } + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/General/IApphostFile.cs b/src/apphost-extract/apphost-extract-v2/Models/General/IApphostFile.cs new file mode 100644 index 0000000..8ea0322 --- /dev/null +++ b/src/apphost-extract/apphost-extract-v2/Models/General/IApphostFile.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace apphost_extract_v2.General +{ + public abstract class IApphostFile + { + internal FileStream FileStream; + public PEHeaders PEHeader; + public AppHostFileHeader Header; + + public IApphostFile(FileStream fs, PEHeaders peheader) + { + FileStream = fs; + PEHeader = peheader; + } + + public void ExtractAll(string outputDir) + { + Directory.CreateDirectory(outputDir); + foreach (var fileEntry in Header.Manifest.FileEntries) + { + try + { + var bytes = FileStream.ReadBuffer(fileEntry.Offset, fileEntry.Size); + var name = fileEntry.Name; + var filePath = Path.Combine(outputDir, name); + File.WriteAllBytes(filePath, bytes); + + Log.Critical($"Extracted {name}"); + + } + catch (Exception ex) + { + Log.Error($"Could not extract {fileEntry.Name}: {ex.Message}"); + } + + } + } + + public abstract void Close(); + } +} diff --git a/src/apphost-extract/apphost-extract-v2/Models/IApphostFile.cs b/src/apphost-extract/apphost-extract-v2/Models/IApphostFile.cs deleted file mode 100644 index 7dd77c1..0000000 --- a/src/apphost-extract/apphost-extract-v2/Models/IApphostFile.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace apphost_extract_v2.Models -{ - public interface IApphostFile - { - - } -} diff --git a/src/apphost-extract/apphost-extract-v2/Program.cs b/src/apphost-extract/apphost-extract-v2/Program.cs index 052b3c3..e00ca1d 100644 --- a/src/apphost-extract/apphost-extract-v2/Program.cs +++ b/src/apphost-extract/apphost-extract-v2/Program.cs @@ -10,20 +10,23 @@ namespace apphost_extract_v2 { static void Main(string[] args) { - var file = "net31-fd.exe"; + var file = args[0]; var fs = new FileStream(file, FileMode.Open, FileAccess.Read); + var anal = new Analyzer(fs); + var af = anal.Open(); + var path = new FileInfo(file); + var directory = Path.Combine(path.DirectoryName, path.Name.Remove(path.Name.Length - path.Extension.Length) + "_extracted"); + af.ExtractAll(directory); + Console.ReadLine(); + //RunTest("net30-sc.exe"); + //RunTest("net5-sc.exe"); + //Log.Info("Scanning for pattern..."); + //Stopwatch sw = Stopwatch.StartNew(); + //var res = Util.PatternScan(fs, 0, (int)fs.Length, pattern, mask); + //sw.Stop(); + //Log.Info("Found pattern at " + res[0].ToString("X8") + $" in {sw.ElapsedMilliseconds}ms"); - var d = new Analyzer(fs).GetTextSectionVA(); - var pattern = new byte[] { 0x4c, 0x8D, 0x5, 0xE2, 0x8A, 0x00, 0x00 }; - string mask = "xxxxxxx"; - Log.Info("Scanning for pattern..."); - Stopwatch sw = Stopwatch.StartNew(); - var res = Util.PatternScan(fs, 0, (int)fs.Length, pattern, mask); - sw.Stop(); - Log.Info("Found pattern at " + res[0].ToString("X8") + $" in {sw.ElapsedMilliseconds}ms"); - - /* Log.Info("apphost-extract by VollRagm\n", ConsoleColor.Yellow); var path = GetPath(args); @@ -43,6 +46,14 @@ namespace apphost_extract_v2 Console.ReadLine();*/ } + //static void RunTest(string file) + //{ + // Log.Info("Running test on " + file + ", scanning for version sig..."); + + + // var d = new Analyzer(fs).GetVersion(); + //} + static FileInfo GetPath(string[] args) { try diff --git a/src/apphost-extract/apphost-extract-v2/Util.cs b/src/apphost-extract/apphost-extract-v2/Util.cs index 4a04083..1ce1340 100644 --- a/src/apphost-extract/apphost-extract-v2/Util.cs +++ b/src/apphost-extract/apphost-extract-v2/Util.cs @@ -9,9 +9,7 @@ namespace apphost_extract_v2 { public static int[] PatternScan(FileStream fs, int start, int length, byte[] pattern, string mask) { - byte[] scanBuffer = new byte[length]; - fs.Seek(start, SeekOrigin.Begin); - fs.Read(scanBuffer, 0, length); + byte[] scanBuffer = fs.ReadBuffer(start, length); List scanResults = new List(); @@ -39,5 +37,13 @@ namespace apphost_extract_v2 return true; } + + public static byte[] ReadBuffer(this FileStream fs, long start, int length) + { + byte[] buff = new byte[length]; + fs.Seek(start, SeekOrigin.Begin); + fs.Read(buff, 0, length); + return buff; + } } }