[Misc Series #2] Explanation of pydotNetCLI

GhouLSec
4 min readMay 26, 2021

As promised in the previous blog, I will share my understanding on the dotNet header and how to parse it.

dotNet CLI header is found in .text section of the PE file. We only need to focused on this section to make things work ✌.

Thanks for the help of CFF Explorer and dnSpyto make this happen 🙌.

Calculate dotNet MetaData Directory Starting Offset

Basically this is the formula from RVA2OffSet.

  1. Check is VA of .text is larger or equal to VA of dotNet MetaData Directory
  2. Check is sum of VA and SizeOfRawAddress of .text is larger than VA of dotNet MetaData Directory.
  3. Calculate new offset by adding RawData offset of .text section to difference between dotNet MetaData Directory VA and .text VA

Let’s move on to dotNet Directory!

Some short notes 📜 before moving on:

byte  (size of 1)
word (size of 2)
dword (size of 4)
qword (size of 8)

dotNet Directory

It is quite easy to understand it as the structure is quite straight forward and the offset is exactly the real address.
Its information is ranged between 0x208 to 0x24F.

dotNet Directory Structure

dotNet directory structure (Black highlighted box)
cb                                      dword
MajorRuntimeVersion word
MinorRuntimeVersion word
MetaData RVA dword
MetaData Size dword
Flags dword
EntryPointToken dword
Resources RVA dword
Resources Size dword
StrongNameSignature RVA dword
StringNameSignature Size dword
CodeManagerTable RVA dword
CodeManagerTable Size dword
VTableFixups RVA dword
VTableFixups Size dword
ExportAddressTableJumps RVA dword
ExportAddressTableJumps Size dword
ManagedNativeHeader RVA dword
ManagedNativeHeader Size dword

MetaData/Stream Directory

To calculate the starting offset of MetaData Directory, we need to get the difference between .text RVA and VA (diff_va_rva in the screenshot above). This value will then used to deduct the MetaData RVA in the dotNet directory to get the its real offset.

Rewrite the function in Python

MetaData/Stream Directory Structure

MetaData Header/Streams

The length of MetaData Header ranged from the beginning of the Signature until end of MetaData Streams .

To get the Tables Count, we need to perform AND operation of the Mask Valid value with list of constant value which can be found in dict_mask_tablein set_MaskValid().

Tables Count indicates number of the table appear in the file. e.g. Module count = 0x01 means it only appears for one time, same goes for other.

Example of Standard Resources file in Resources section

The way to calculate its starting offset is similar to MetaData Directory just deduct the Resource RVA to diff_va_rva.

For parsing the resource file in more detailed manner, the following link https://ntcore.com/files/manifestres.htm provided a write-up for that.
This can be found in set_ptr_resource_data_offset() .

Standard Resources File Structure

Standard structure of resource data

Extraction of multiple files in single resource section is not support now. e.g.

Resource contains multiple files, 11 files
Number of Resources, 0xB == 11

However, sometime we did encountered some resource file which is just some random blob that did not comply with the structure of the dotNet resource. For example, a PE file.

Random data in resource section

Please correct me if there is any misinformation.

Hopefully it helps any dotNet malware researcher 😁!

Sample MD5:

98fd49a293efbf30152257f76b843672

References:

--

--