Well, as most of my MSN contacts would know from me bitching constantly, I've been trying to write a PE parser for a while now, but never got very far due to frustration. Anyway, tonight I was browsing the net and happened across a Microsoft document on PE, in finer detail.
Which is about a 95 page word document on the PE file format.
So I scrapped everything I had already (apart from the structures 'cos fuck finding that shit again) and started from scratch. Parsing each section as I read about it in the article. There's still a lot more to parse, but the more important information is done (Headers, sections, IAT...etc) I'll probably keep plugging away at this when I get more time but we'll see.
I'm releasing the source code with this because I haven't really shared a lot lately and this way I can get critiques on my code etc.
If you don't know what PE Parsing is, do some research before trying to understand this, while the code may seem fairly straightforward (and it pretty much is) the structure of a PE file is pretty complex while you're just learning it and yeah...
All of this code was written by me over the last 3 hours (3:30AM now, shit) although I did give props to David for his great C++ reference for listing the IAT.
@Hell_Demon Wesley without a doubt deserves the biggest credits for this, he's one hell of a patient guy when I come crying to him about something not working. Thanks.
@Void the IAT reference <3
Here's the source code (I'll include the project as well in the zip.)
PELibrary.cs
[highlight=java]
/************************************************** ******/
// PE Header Parser //
/************************************************** ******/
// Written by Jason/Cho Chang of MPGH //
// For the members of MPGH, if you see //
// This code being used elsewhere, please //
// Contact me about it. Thanks a lot //
// Special shoutout to Wesley and David <3 //
//__________________________________________________ _____//
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using PEStructs;
namespace PELibrary
{
public class PEParser : IDisposable //this class implements IDisposable to clean up after itself.
{
public void Dispose()
{
if (PEReader != null) { PEReader.Close(); } //if the reader has been instantiated, close it.
if (PEStream != null) { PEStream.Close(); PEStream.Dispose(); } //if the stream has been instantiated, close and dispose.
}
#region Properties
public IMAGE_DOS_HEADER DOSHeader { get; private set; }
public IMAGE_FILE_HEADER FileHeader { get; private set; }
public IMAGE_OPTIONAL_HEADER32 OptionalHeader32 { get; private set; }
public IMAGE_OPTIONAL_HEADER64 OptionalHeader64 { get; private set; }
public IMAGE_NT_HEADER32 NTHeader32 { get; private set; }
public IMAGE_NT_HEADER64 NTHeader64 { get; private set; }
public IMAGE_SECTION_HEADER[] SectionHeaders { get; private set; }
public IMAGE_IMPORT_FILE[] ImportedLibraries { get; private set; }
//personal preference, I like keeping my primitive/reference types seperate in declarations.
public uint SectionTablePtr { get; private set; }
public bool CanRead { get; private set; }
public bool PE32Type { get; private set; } #endregion
public PEParser(string PELocation)
{
try
{
if (File.Exists(PELocation)) //no point continuing if the file doesn't even exist.
{
this.PEStream = new FileStream(PELocation, FileMode.Open, FileAccess.Read); //set our filestream up. Read access so that we can access more files and don't disturb other files accessing them.
this.PEReader = new BinaryReader(PEStream); //create the binary reader to overlay the filestream. Allows for seeking.
this.DOSHeader = readFromStream<IMAGE_DOS_HEADER>(0); //parse the DOS header first.
if (readFromStream<int>(this.DOSHeader.e_lfanew) == 17744) //check that the signature matches. ASCII(17744) == "PE\0\0" which is the correct signature.
{
UInt16 OptHeaderType = readFromStream<UInt16>((uint)(this.DOSHeader.e_lfa new + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)))); //calculate the type of the PE; (PE32 or PE32+)
//calculate the sections and imports.
ParseSections(); //call the sections void.
ParseImports(); //call the imports void.
this.CanRead = true; //got through all the steps no hassles, we can semenize now.
}
}
}
catch (Exception) { } //just a failsafe.
}
private void ParseSections()
{
//first calculate the pointer to the section table. This occurs straight after the optional header (which is after the file header and after the signature)
//rather than using Marshal.SizeOf again for the optional header, we use the value in the File header, because the optional header has some variance in size.
this.SectionTablePtr = (uint)(this.DOSHeader.e_lfanew + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + this.FileHeader.SizeOfOptionalHeader);
int sectionCount = this.FileHeader.NumberOfSections; //self explanatory I hope, the number of sections in the PE.
int sectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); //again, fairly self explanatory; the size of a "IMAGE_SECTION_HEADER" in bytes.
this.SectionHeaders = new IMAGE_SECTION_HEADER[sectionCount]; //instantiate our array now we know how many sections there are.
for (int i = 0; i < sectionCount; i++)
{
//loop through the section count, reading each section from the file into the array. On a sidenote, I overrode the ToString() method of
//IMAGE_SECTION_HEADER so that it will return the name of the section (removed the null terminating bytes ofc)
this.SectionHeaders[i] = readFromStream<IMAGE_SECTION_HEADER>((uint)(this.S ectionTablePtr + (i * sectionSize)));
}
}
private void ParseImports()
{ //PROPS TO DAVID (AKA VOID) FOR WRITING THIS SHIT IN C++, WAS A GOOD REFERENCE WHEN I GOT CONFUSED <3
//first we calculate where the Imports Address Table is located in the PE. Gotta love my thoughtfulness creating an enum for you fags to tell you which directory to look in.
//I use an inline conditional because I cbf with more curly brackets and I'm just 1337 like that. The virtual address is relative to the imagebase, but since this PE isn't loaded
//into memory, the image base == 0, so RVAs are essentially the correct address. Do note that this won't always be the case and virtual addresses != physical addresses in most cases.
uint IATPointer = (this.PE32Type ? this.OptionalHeader32.DataDirectory[(int)DATA_DIRECTORIES.ImportTable].VirtualAddress : this.OptionalHeader64.DataDirectory[(int)DATA_DIRECTORIES.ImportTable].VirtualAddress);
uint szDescriptor = (uint)Marshal.SizeOf(typeof(IMAGE_IMPORT_DESCRIPTO R)); //size of an import descriptor. We need this figure so that we can step through the memory in chunks the size of a descriptor.
uint szThunk = (uint)Marshal.SizeOf(typeof(IMAGE_THUNK_DATA)); //the size of a thunk. we'll be stepping through the thunks of data within each descriptor.
List<IMAGE_IMPORT_FILE> importedFiles = new List<IMAGE_IMPORT_FILE>(); //create a temporary list, 'cos they are easier and I'm a lazy bastard.
IMAGE_IMPORT_DESCRIPTOR desc = readFromStream<IMAGE_IMPORT_DESCRIPTOR>(IATPointer ); //read the first descriptor as a starting point.
int currentImport = 0; //import index
while (desc.OriginalFirstThunk != 0 ) //last descriptor is all 0s, so while its not 0, we keep looping.
{
currentImport++; //increase what import we're up to, so we can tell the readFromStream how many chunks to skip over.
IMAGE_THUNK_DATA pThunk = readFromStream<IMAGE_THUNK_DATA>(desc.OriginalFirs tThunk); //read the first thunk as a starting point.
List<string> funcs = new List<string>(); //create a list to hold all the function names.
int currentFunction = 0; //function index. Same principle as the import index; to get the correct offset for the next shit to be read.
string libName = readFromStream<IMAGE_IMPORT_NAME>(desc.Name).ToStr ing(); //IMAGE_IMPORT_NAME is a structure I made so I could use readFromStream. I overrode ToString() to return the function name
while (pThunk.u1.Function != 0) //while there is another function in this thunk of data.
{
currentFunction++; //update what function we're on.
string funcName = readFromStream<IMAGE_FUNCTION_NAME>(pThunk.u1.Addr essOfData + 2).ToString(); //IMAGE_FUNCTION_NAME is a structure similar to IMAGE_IMPORT_NAME that I made cos im lazy, larger string buffer though.
funcs.Add(funcName); //add the function to this list of functions for the current import.
pThunk = readFromStream<IMAGE_THUNK_DATA>((uint)(desc.Origi nalFirstThunk + (szThunk * currentFunction))); //read the next thunk from memory (we step forward currentFunction * thunksize from the original offset of the IAT)
}
importedFiles.Add(new IMAGE_IMPORT_FILE(libName, funcs.ToArray())); //we've finished with the current descriptor so before the next loop we add the current import to the list with all its functions and keep on going.
desc = readFromStream<IMAGE_IMPORT_DESCRIPTOR>((uint)(IAT Pointer + (currentImport * szDescriptor))); //read the next descriptor from the stream.
}
this.ImportedLibraries = importedFiles.ToArray(); //method over, update the property to reflect the parsed shit.
}
private void ContinueAs32()
{
// simple method to set up the properties in the case of a PE32 file.
// really the only difference between ContinueAs32 and ContinueAs64 is that
// 32 and 64 bit specific structues have to be used in some cases.
this.PE32Type = true;
this.NTHeader32 = readFromStream<IMAGE_NT_HEADER32>(this.DOSHeader.e _lfanew);
this.FileHeader = this.NTHeader32.FileHeader;
this.OptionalHeader32 = this.NTHeader32.OptionalHeader;
}
private void ContinueAs64()
{
// simple method to set up the properties in the case of a PE32+ file.
this.PE32Type = false; //we're not PE32.
this.NTHeader64 = readFromStream<IMAGE_NT_HEADER64>(this.DOSHeader.e _lfanew);
this.FileHeader = this.NTHeader32.FileHeader;
this.OptionalHeader64 = this.NTHeader64.OptionalHeader;
}
public E readFromStream<E>(uint offset)
{
//this is the money maker. I find generic functions delicious. It's a public method
// but for the love of shit don't fuck with this if you don't understand how it works
// and what types it is capable of handling in its current state.
int sizeOfReturn = Marshal.SizeOf(typeof(E)); //size of the return type (E)
byte[] dataBuffer = new byte[sizeOfReturn]; //create a buffer to hold the bytes
this.PEStream.Seek(offset, SeekOrigin.Begin); //seek to the offset from the beginning of the stream
this.PEReader.Read(dataBuffer, 0, dataBuffer.Length); //read into the databuffer.
GCHandle tempHwnd = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned); //now I pin those bytes to unmanaged memory temporarily. Need to pin it so we can keep track of it's address
//I'm lazy as shit so I use this 'unmanaged' solution so that I don't have to write my own byte=>struct method, I can just abuse the Marshal class.
E returnValue = (E)Marshal.PtrToStructure(tempHwnd.AddrOfPinnedObj ect(), typeof(E));
tempHwnd.Free(); //free the pinned memory, we don't want to clog shit up with heaps of pinned memory.
return returnValue; //return.
}
}
}
[/highlight]
Structures.cs
[highlight=java]
using System.Text;
using System.Runtime.InteropServices;
using System;
//ALL THE STRUCTURES NEEDED FOR THIS. SOME ARE MINE, SOME ARE MICROSOFT STANDARD.
namespace PEStructs
{
//The indexing of the data directories. You'll love me later.
public enum DATA_DIRECTORIES
{
ExportTable, ImportTable, ResourceTable, ExceptionTable, CertificateTable, BaseRelocTable, Debug, Architecture, GlobalPtr, TLSTable, LoadConfigTable, BoundImport, IAT, DelayImportDescriptor, CLRRuntimeHeader, Reserved
}
[StructLayout(LayoutKind.Explicit)]
public struct U1
{
[FieldOffset(0)]
public uint ForwarderString;
[FieldOffset(0)]
public uint Function;
[FieldOffset(0)]
public uint Ordinal;
[FieldOffset(0)]
public uint AddressOfData;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_THUNK_DATA
{
public U1 u1;
}
public struct IMAGE_DOS_HEADER
{
public UInt16 e_magic; // Magic number
public UInt16 e_cblp; // Bytes on last page of file
public UInt16 e_cp; // Pages in file
public UInt16 e_crlc; // Relocations
public UInt16 e_cparhdr; // Size of header in paragraphs
public UInt16 e_minalloc; // Minimum extra paragraphs needed
public UInt16 e_maxalloc; // Maximum extra paragraphs needed
public UInt16 e_ss; // Initial (relative) SS value
public UInt16 e_sp; // Initial SP value
public UInt16 e_csum; // Checksum
public UInt16 e_ip; // Initial IP value
public UInt16 e_cs; // Initial (relative) CS value
public UInt16 e_lfarlc; // File address of relocation table
public UInt16 e_ovno; // Overlay number
public UInt16 e_res_0; // Reserved words
public UInt16 e_res_1; // Reserved words
public UInt16 e_res_2; // Reserved words
public UInt16 e_res_3; // Reserved words
public UInt16 e_oemid; // OEM identifier (for e_oeminfo)
public UInt16 e_oeminfo; // OEM information; e_oemid specific
public UInt16 e_res2_0; // Reserved words
public UInt16 e_res2_1; // Reserved words
public UInt16 e_res2_2; // Reserved words
public UInt16 e_res2_3; // Reserved words
public UInt16 e_res2_4; // Reserved words
public UInt16 e_res2_5; // Reserved words
public UInt16 e_res2_6; // Reserved words
public UInt16 e_res2_7; // Reserved words
public UInt16 e_res2_8; // Reserved words
public UInt16 e_res2_9; // Reserved words
public UInt32 e_lfanew; // File address of new exe header
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DATA_DIRECTORY
{
public uint VirtualAddress;
public uint Size;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_OPTIONAL_HEADER32
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt32 BaseOfData;
public UInt32 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatin****temVersion;
public UInt16 MinorOperatin****temVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt32 SizeOfStackReserve;
public UInt32 SizeOfStackCommit;
public UInt32 SizeOfHeapReserve;
public UInt32 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_OPTIONAL_HEADER64
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt64 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatin****temVersion;
public UInt16 MinorOperatin****temVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt64 SizeOfStackReserve;
public UInt64 SizeOfStackCommit;
public UInt64 SizeOfHeapReserve;
public UInt64 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_FILE_HEADER
{
public UInt16 Machine;
public UInt16 NumberOfSections;
public UInt32 TimeDateStamp;
public UInt32 PointerToSymbolTable;
public UInt32 NumberOfSymbols;
public UInt16 SizeOfOptionalHeader;
public UInt16 Characteristics;
};
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_SECTION_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Name; // 0, 8 UTF-8 Encoded string of size 8 bytes and null terminated.
public UInt32 VirtualSize; //8, 4
public UInt32 VirtualAddress; //12, 4
public UInt32 SizeOfRawData; //16, 4
public UInt32 PointerToRawData; //20, 4
public UInt32 PointerToRelocations; //24, 4
public UInt32 PointerToLineNumbers; //28 , 4
public UInt16 NumberOfRelocations; //32, 2
public UInt16 NumberOfLineNumbers; //34, 2
public UInt32 Characteristics; //36, 4
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_NT_HEADER32
{
public int Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_EXPORT_DIRECTORY
{
public UInt32 ExportFlags;
public UInt32 TimeDateStamp;
public UInt16 MajorVersion;
public UInt16 MinorVersion;
public UInt32 NameRVA;
public UInt32 OrdinalBase;
public UInt32 NumberOfEntries;
public UInt32 NumberOfNamePtrs;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_NT_HEADER64
{
public int Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER64 OptionalHeader;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_IMPORT_DESCRIPTOR
{
public uint OriginalFirstThunk;
public uint TimeDateStamp;
public uint ForwarderChain;
public uint Name;
public uint FirstThunkPtr;
}
public struct IMAGE_IMPORT_NAME
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
public byte[] nameBytes;
Cheers, sorry the thread was a bit rushed, it's now 3:42am and I'm sleepy Jason
Posts 1–9 of 9 · Page 1 of 1
Post a Reply
Tags for this Thread
None
<3 Good job <3
Oh dayum, nice Jason. =D
Thanks fellas
OMGGGGG AWESOME ! If only this had existed when I needed it x)
It would have saved me so much time !
Must have taken you forever ._.
Very nice. Must of taken awhile.
I actually printed that whole article out about 8 months ago . My parents where not happy.
Wow I can't believe you actually wrote out the headers structure's lol, any other language they are included.... or you should have just copied them from the windows lib and changed the syntax
Originally Posted by Departure
Wow I can't believe you actually wrote out the headers structure's lol, any other language they are included.... or you should have just copied them from the windows lib and changed the syntax
Who said anything about me writing them out? Most of the structures I used were just pure copy-pasta from sources like MSDN and PInvoke.