I have extensive experience with parsing the PE on Windows, mainly for use in function interception. Here are the steps you should follow to achieve your goal.
The first step is to find the base address of the image loaded into memory. This step will be different depending on if the executable has or hasn't been mapped into memory, but the basic idea will be the same. Assuming that you are interested in doing this for a file on disk, then you may do this with the file mapping API, if you would prefer to implement this on an executable loaded into memory as a running process, you can achieve the equivalent by using the the tool help snapshot API. The base address will be the same as the field hModule
in the MODULEENTRY32
data retrieved from the snapshot. For more information about what a module handle is, see here.
Once you have completed the first step, the structure at the base address is the IMAGE_DOS_HEADER
, while this is not documented on MSDN, it has two very important but cryptic fields. The two fields you will need to know are the e_magic
field and the e_lfanew
field. The e_magic
field contains a double word for 32-bit or quadruple for 64-bit that allows you to test if the file being read or your implementation is correctly formatted with the correct value being defined as IMAGE_DOS_SIGNATURE
, which is the ASCII-Z string of "MZ\0". The e_lfanew
field contains a relative virtual address which you need to add to the image base you found in step one to calculate the virtual address of the IMAGE_NT_HEADERS
structure.
The third step will be to check the first member of the IMAGE_NT_HEADERS
to see if it is an actual PE file, this will be defined by the Signature
field of the structure, and the defined constant to test for will be IMAGE_NT_SIGNATURE
. This is not typically nessecary, since most Windows versions will be using the PE format of executable file, but it's good practice in order to ensure your code is a bit more robust.
Once everything checks out and you have performed steps 1-3, step four will be when you can finally calculate the address of the IMAGE_SECTION_HEADER
structures. The IMAGE_SECTION_HEADER structures are stored as an array in the file, so to obtain the size of the array, you will need to use the NumberOfSections
member in the IMAGE_FILE_HEADER
structure which is nested in the IMAGE_NT_HEADERS
structure mentioned above.Once you have that value, you may find the virtual address of the first IMAGE_SECTION_HEADER
by adding the size of the Signature
member of the IMAGE_NT_HEADERS
structure, the size of the FileHeader
member in the IMAGE_NT_HEADERS
structure, and finally the SizeOfOptionalHeader
value in the IMAGE_FILE_HEADER
structure. The reason you can't simply do a sizeof(
IMAGE_OPTIONAL_HEADER
)
for the last value in the formula listed above is because a file on disk will not have the IMAGE_OPTIONAL_HEADER
, so to obtain the proper size dynamically, you should do so through the structure member I mentioned earlier.
Now you may simply copy the array of IMAGE_SECTION_HEADER
structures from memory any way you please. Just remember, that these structures are contigous in memory, so all you need to do is multiply the size of each structure by the number of sections to find the total size of the entire array in memory. After you have calculate that value, it will be trivial to collect the data.
For more resources on the PE executable format, see this wonderful article written by Matt Pietrek, Peering Inside the PE. You may also take a look at the official specification here.