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
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
) 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.