github.com/saferwall/pe@v1.5.2/exports.go (about) 1 // Copyright 2018 Saferwall. All rights reserved. 2 // Use of this source code is governed by Apache v2 license 3 // license that can be found in the LICENSE file. 4 5 package pe 6 7 import ( 8 "encoding/binary" 9 "errors" 10 "fmt" 11 ) 12 13 const ( 14 maxExportedSymbols = 0x2000 15 ) 16 17 var ( 18 ErrExportMaxOrdEntries = "Export directory contains more than max ordinal entries" 19 ErrExportManyRepeatedEntries = "Export directory contains many repeated entries" 20 AnoNullNumberOfFunctions = "Export directory contains zero number of functions" 21 AnoNullAddressOfFunctions = "Export directory contains zero address of functions" 22 ) 23 24 // ImageExportDirectory represents the IMAGE_EXPORT_DIRECTORY structure. 25 // The export directory table contains address information that is used 26 // to resolve imports to the entry points within this image. 27 type ImageExportDirectory struct { 28 // Reserved, must be 0. 29 Characteristics uint32 `json:"characteristics"` 30 31 // The time and date that the export data was created. 32 TimeDateStamp uint32 `json:"time_date_stamp"` 33 34 // The major version number. 35 //The major and minor version numbers can be set by the user. 36 MajorVersion uint16 `json:"major_version"` 37 38 // The minor version number. 39 MinorVersion uint16 `json:"minor_version"` 40 41 // The address of the ASCII string that contains the name of the DLL. 42 // This address is relative to the image base. 43 Name uint32 `json:"name"` 44 45 // The starting ordinal number for exports in this image. This field 46 // specifies the starting ordinal number for the export address table. 47 // It is usually set to 1. 48 Base uint32 `json:"base"` 49 50 // The number of entries in the export address table. 51 NumberOfFunctions uint32 `json:"number_of_functions"` 52 53 // The number of entries in the name pointer table. This is also the number 54 // of entries in the ordinal table. 55 NumberOfNames uint32 `json:"number_of_names"` 56 57 // The address of the export address table, relative to the image base. 58 AddressOfFunctions uint32 `json:"address_of_functions"` 59 60 // The address of the export name pointer table, relative to the image base. 61 // The table size is given by the Number of Name Pointers field. 62 AddressOfNames uint32 `json:"address_of_names"` 63 64 // The address of the ordinal table, relative to the image base. 65 AddressOfNameOrdinals uint32 `json:"address_of_name_ordinals"` 66 } 67 68 // ExportFunction represents an imported function in the export table. 69 type ExportFunction struct { 70 Ordinal uint32 `json:"ordinal"` 71 FunctionRVA uint32 `json:"function_rva"` 72 NameOrdinal uint32 `json:"name_ordinal"` 73 NameRVA uint32 `json:"name_rva"` 74 Name string `json:"name"` 75 Forwarder string `json:"forwarder"` 76 ForwarderRVA uint32 `json:"forwarder_rva"` 77 } 78 79 // Export represent the export table. 80 type Export struct { 81 Functions []ExportFunction `json:"functions"` 82 Struct ImageExportDirectory `json:"struct"` 83 Name string `json:"name"` 84 } 85 86 /* 87 A few notes learned from `Corkami` about parsing export directory: 88 - like many data directories, Exports' size are not necessary, except for forwarding. 89 - Characteristics, TimeDateStamp, MajorVersion and MinorVersion are not necessary. 90 - the export name is not necessary, and can be anything. 91 - AddressOfNames is lexicographically-ordered. 92 - export names can have any value (even null or more than 65536 characters long, 93 with unprintable characters), just null terminated. 94 - an EXE can have exports (no need of relocation nor DLL flag), and can use 95 96 them normally 97 - exports can be not used for execution, but for documenting the internal code 98 - numbers of functions will be different from number of names when the file 99 is exporting some functions by ordinal. 100 */ 101 func (pe *File) parseExportDirectory(rva, size uint32) error { 102 103 // Define some vars. 104 exp := Export{} 105 exportDir := ImageExportDirectory{} 106 errorMsg := fmt.Sprintf("Error parsing export directory at RVA: 0x%x", rva) 107 108 fileOffset := pe.GetOffsetFromRva(rva) 109 exportDirSize := uint32(binary.Size(exportDir)) 110 err := pe.structUnpack(&exportDir, fileOffset, exportDirSize) 111 if err != nil { 112 return errors.New(errorMsg) 113 } 114 exp.Struct = exportDir 115 116 // We keep track of the bytes left in the file and use it to set a upper 117 // bound in the number of items that can be read from the different arrays. 118 lengthUntilEOF := func(rva uint32) uint32 { 119 return pe.size - pe.GetOffsetFromRva(rva) 120 } 121 var length uint32 122 var addressOfNames []byte 123 124 // Some DLLs have null number of functions. 125 if exportDir.NumberOfFunctions == 0 { 126 pe.Anomalies = append(pe.Anomalies, AnoNullNumberOfFunctions) 127 } 128 129 // Some DLLs have null address of functions. 130 if exportDir.AddressOfFunctions == 0 { 131 pe.Anomalies = append(pe.Anomalies, AnoNullAddressOfFunctions) 132 } 133 134 length = min(lengthUntilEOF(exportDir.AddressOfNames), 135 exportDir.NumberOfNames*4) 136 addressOfNames, err = pe.GetData(exportDir.AddressOfNames, length) 137 if err != nil { 138 return errors.New(errorMsg) 139 } 140 141 length = min(lengthUntilEOF(exportDir.AddressOfNameOrdinals), 142 exportDir.NumberOfNames*4) 143 addressOfNameOrdinals, err := pe.GetData(exportDir.AddressOfNameOrdinals, length) 144 if err != nil { 145 return errors.New(errorMsg) 146 } 147 148 length = min(lengthUntilEOF(exportDir.AddressOfFunctions), 149 exportDir.NumberOfFunctions*4) 150 addressOfFunctions, err := pe.GetData(exportDir.AddressOfFunctions, length) 151 if err != nil { 152 return errors.New(errorMsg) 153 } 154 155 exp.Name = pe.getStringAtRVA(exportDir.Name, 0x100000) 156 157 maxFailedEntries := 10 158 var forwarderStr string 159 var forwarderOffset uint32 160 safetyBoundary := pe.size // overly generous upper bound 161 symbolCounts := make(map[uint32]int) 162 parsingFailed := false 163 164 // read the image export directory 165 section := pe.getSectionByRva(exportDir.AddressOfNames) 166 if section != nil { 167 safetyBoundary = (section.Header.VirtualAddress + 168 uint32(len(section.Data(0, 0, pe)))) - exportDir.AddressOfNames 169 } 170 171 numNames := min(exportDir.NumberOfNames, safetyBoundary/4) 172 var symbolAddress uint32 173 for i := uint32(0); i < numNames; i++ { 174 175 defer func() { 176 // recover from panic if one occured. Set err to nil otherwise. 177 if recover() != nil { 178 err = errors.New("array index out of bounds") 179 } 180 }() 181 182 symbolOrdinal := binary.LittleEndian.Uint16(addressOfNameOrdinals[i*2:]) 183 symbolAddress = binary.LittleEndian.Uint32(addressOfFunctions[symbolOrdinal*4:]) 184 if symbolAddress == 0 { 185 continue 186 } 187 188 // If the function's RVA points within the export directory 189 // it will point to a string with the forwarded symbol's string 190 // instead of pointing the the function start address. 191 if symbolAddress >= rva && symbolAddress < rva+size { 192 forwarderStr = pe.getStringAtRVA(symbolAddress, 0x100000) 193 forwarderOffset = pe.GetOffsetFromRva(symbolAddress) 194 } else { 195 forwarderStr = "" 196 fileOffset = 0 197 } 198 199 symbolNameAddress := binary.LittleEndian.Uint32(addressOfNames[i*4:]) 200 if symbolNameAddress == 0 { 201 maxFailedEntries-- 202 if maxFailedEntries <= 0 { 203 parsingFailed = true 204 break 205 } 206 } 207 symbolName := pe.getStringAtRVA(symbolNameAddress, 0x100000) 208 if !IsValidFunctionName(symbolName) { 209 parsingFailed = true 210 break 211 } 212 213 symbolNameOffset := pe.GetOffsetFromRva(symbolNameAddress) 214 if symbolNameOffset == 0 { 215 maxFailedEntries-- 216 if maxFailedEntries <= 0 { 217 parsingFailed = true 218 break 219 } 220 } 221 222 // File 0b1d3d3664915577ab9a32188d29bbf3542b86c7b9ce333e245496c3018819f1 223 // was being parsed as potentially containing millions of exports. 224 // Checking for duplicates addresses the issue. 225 symbolCounts[symbolAddress]++ 226 if symbolCounts[symbolAddress] > 10 { 227 if !stringInSlice(ErrExportManyRepeatedEntries, pe.Anomalies) { 228 pe.Anomalies = append(pe.Anomalies, ErrExportManyRepeatedEntries) 229 } 230 } 231 if len(symbolCounts) > maxExportedSymbols { 232 if !stringInSlice(ErrExportMaxOrdEntries, pe.Anomalies) { 233 pe.Anomalies = append(pe.Anomalies, ErrExportMaxOrdEntries) 234 } 235 } 236 newExport := ExportFunction{ 237 Name: symbolName, 238 NameRVA: symbolNameAddress, 239 NameOrdinal: uint32(symbolOrdinal), 240 Ordinal: exportDir.Base + uint32(symbolOrdinal), 241 FunctionRVA: symbolAddress, 242 Forwarder: forwarderStr, 243 ForwarderRVA: forwarderOffset, 244 } 245 246 exp.Functions = append(exp.Functions, newExport) 247 } 248 249 if parsingFailed { 250 fmt.Printf("RVA AddressOfNames in the export directory points to an "+ 251 "invalid address: 0x%x\n", exportDir.AddressOfNames) 252 } 253 254 maxFailedEntries = 10 255 section = pe.getSectionByRva(exportDir.AddressOfFunctions) 256 257 // Overly generous upper bound 258 safetyBoundary = pe.size 259 if section != nil { 260 safetyBoundary = section.Header.VirtualAddress + 261 uint32(len(section.Data(0, 0, pe))) - exportDir.AddressOfNames 262 } 263 parsingFailed = false 264 ordinals := make(map[uint32]bool) 265 for _, export := range exp.Functions { 266 ordinals[export.Ordinal] = true 267 } 268 numNames = min(exportDir.NumberOfFunctions, safetyBoundary/4) 269 for i := uint32(0); i < numNames; i++ { 270 value := i + exportDir.Base 271 if ordinals[value] { 272 continue 273 } 274 275 if len(addressOfFunctions) >= int(i*4)+4 { 276 symbolAddress = binary.LittleEndian.Uint32(addressOfFunctions[i*4:]) 277 } 278 if symbolAddress == 0 { 279 continue 280 } 281 282 // Checking for forwarder again. 283 if symbolAddress >= rva && symbolAddress < rva+size { 284 forwarderStr = pe.getStringAtRVA(symbolAddress, 0x100000) 285 forwarderOffset = pe.GetOffsetFromRva(symbolAddress) 286 } else { 287 forwarderStr = "" 288 fileOffset = 0 289 } 290 291 // File 0b1d3d3664915577ab9a32188d29bbf3542b86c7b9ce333e245496c3018819f1 292 // was being parsed as potentially containing millions of exports. 293 // Checking for duplicates addresses the issue. 294 symbolCounts[symbolAddress]++ 295 if symbolCounts[symbolAddress] > 10 { 296 if !stringInSlice(ErrExportManyRepeatedEntries, pe.Anomalies) { 297 pe.Anomalies = append(pe.Anomalies, ErrExportManyRepeatedEntries) 298 } 299 } 300 if len(symbolCounts) > maxExportedSymbols { 301 if !stringInSlice(ErrExportMaxOrdEntries, pe.Anomalies) { 302 303 pe.Anomalies = append(pe.Anomalies, ErrExportMaxOrdEntries) 304 } 305 } 306 newExport := ExportFunction{ 307 Ordinal: exportDir.Base + i, 308 FunctionRVA: symbolAddress, 309 Forwarder: forwarderStr, 310 ForwarderRVA: forwarderOffset, 311 } 312 313 exp.Functions = append(exp.Functions, newExport) 314 } 315 316 pe.Export = exp 317 pe.HasExport = true 318 return nil 319 } 320 321 // GetExportFunctionByRVA return an export function given an RVA. 322 func (pe *File) GetExportFunctionByRVA(rva uint32) ExportFunction { 323 for _, exp := range pe.Export.Functions { 324 if exp.FunctionRVA == rva { 325 return exp 326 } 327 } 328 329 return ExportFunction{} 330 }