diff --git a/format/all/all.go b/format/all/all.go index b62cb687f2..2b06ce7b25 100644 --- a/format/all/all.go +++ b/format/all/all.go @@ -40,6 +40,7 @@ import ( _ "github.com/wader/fq/format/ogg" _ "github.com/wader/fq/format/opus" _ "github.com/wader/fq/format/pcap" + _ "github.com/wader/fq/format/pe" _ "github.com/wader/fq/format/png" _ "github.com/wader/fq/format/postgres" _ "github.com/wader/fq/format/prores" diff --git a/format/format.go b/format/format.go index 037b457905..8053abe10f 100644 --- a/format/format.go +++ b/format/format.go @@ -145,6 +145,9 @@ var ( Opus_Packet = &decode.Group{Name: "opus_packet"} PCAP = &decode.Group{Name: "pcap"} PCAPNG = &decode.Group{Name: "pcapng"} + PE = &decode.Group{Name: "pe"} + PE_COFF = &decode.Group{Name: "pe_coff"} + PE_MSDOS_Stub = &decode.Group{Name: "pe_msdos_stub"} Pg_BTree = &decode.Group{Name: "pg_btree"} Pg_Control = &decode.Group{Name: "pg_control"} Pg_Heap = &decode.Group{Name: "pg_heap"} diff --git a/format/pe/pe.go b/format/pe/pe.go new file mode 100644 index 0000000000..5870cad962 --- /dev/null +++ b/format/pe/pe.go @@ -0,0 +1,37 @@ +package pe + +// https://osandamalith.com/2020/07/19/exploring-the-ms-dos-stub/ + +import ( + "github.com/wader/fq/format" + "github.com/wader/fq/pkg/decode" + "github.com/wader/fq/pkg/interp" +) + +// TODO: probe? +// TODO: not pe_ prefix for format names? + +var peMSDosStubGroup decode.Group +var peCOFFGroup decode.Group + +func init() { + interp.RegisterFormat( + format.PE, + &decode.Format{ + Description: "Portable Executable", + Groups: []*decode.Group{format.Probe}, + Dependencies: []decode.Dependency{ + {Groups: []*decode.Group{format.PE_MSDOS_Stub}, Out: &peMSDosStubGroup}, + {Groups: []*decode.Group{format.PE_COFF}, Out: &peCOFFGroup}, + }, + DecodeFn: peDecode, + }) +} + +func peDecode(d *decode.D) any { + + d.FieldFormat("ms_dos_stub", &peMSDosStubGroup, nil) + d.FieldFormat("coff", &peCOFFGroup, nil) + + return nil +} diff --git a/format/pe/pe_coff.go b/format/pe/pe_coff.go new file mode 100644 index 0000000000..22271d406f --- /dev/null +++ b/format/pe/pe_coff.go @@ -0,0 +1,428 @@ +package pe + +// string table: +// .coff.pointer_to_symbol_table as $off | .coff.number_of_symbols as $n | ($off+($n*18)) as $o | (tobytes[$o:$o+4] | explode | reverse |tobytes | tonumber) as $s | tobytes[$o:$o+$s] | dd + +// https://osandamalith.com/2020/07/19/exploring-the-ms-dos-stub/ +// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format +// https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg + +import ( + "encoding/binary" + "strconv" + "strings" + "time" + + "github.com/wader/fq/format" + "github.com/wader/fq/pkg/decode" + "github.com/wader/fq/pkg/interp" + "github.com/wader/fq/pkg/scalar" +) + +// is used to adjust pointer that are from the beginning of the exe +// TODO: decode arg? is free standing COFF a tring? +const msDOSStubByteSize = 0x80 + +// TODO: probe? + +func init() { + interp.RegisterFormat( + format.PE_COFF, + &decode.Format{ + Description: "Common Object File Format", + DecodeFn: peCoffStubDecode, + }) +} + +const ( + peFormat32 = 0x10b + peFormat32Plus = 0x20b +) + +var peFormatNames = scalar.UintMapSymStr{ + peFormat32: "pe32", + peFormat32Plus: "pe32+", +} + +const ( + MachineTypeUNKNOWN = 0x0 + MachineTypeALPHA = 0x184 + MachineTypeALPHA64 = 0x284 + MachineTypeAM33 = 0x1d3 + MachineTypeAMD64 = 0x8664 + MachineTypeARM = 0x1c0 + MachineTypeARM64 = 0xaa64 + MachineTypeARMNT = 0x1c4 + MachineTypeAXP64 = 0x284 + MachineTypeEBC = 0xebc + MachineTypeI386 = 0x14c + MachineTypeIA64 = 0x200 + MachineTypeLOONGARCH32 = 0x6232 + MachineTypeLOONGARCH64 = 0x6264 + MachineTypeM32R = 0x9041 + MachineTypeMIPS16 = 0x266 + MachineTypeMIPSFPU = 0x366 + MachineTypeMIPSFPU16 = 0x466 + MachineTypePOWERPC = 0x1f0 + MachineTypePOWERPCFP = 0x1f1 + MachineTypeR4000 = 0x166 + MachineTypeRISCV32 = 0x5032 + MachineTypeRISCV64 = 0x5064 + MachineTypeRISCV128 = 0x5128 + MachineTypeSH3 = 0x1a2 + MachineTypeSH3DSP = 0x1a3 + MachineTypeSH4 = 0x1a6 + MachineTypeSH5 = 0x1a8 + MachineTypeTHUMB = 0x1c2 + MachineTypeWCEMIPSV2 = 0x169 +) + +var MachineTypeNames = scalar.UintMap{ + MachineTypeUNKNOWN: {Sym: "UNKNOWN", Description: "The content of this field is assumed to be applicable to any machine type"}, + MachineTypeALPHA: {Sym: "ALPHA", Description: "Alpha AXP, 32-bit address space"}, + MachineTypeALPHA64: {Sym: "ALPHA64", Description: "Alpha 64, 64-bit address space"}, + MachineTypeAM33: {Sym: "AM33", Description: "Matsushita AM33"}, + MachineTypeAMD64: {Sym: "AMD64", Description: "x64"}, + MachineTypeARM: {Sym: "ARM", Description: "ARM little endian"}, + MachineTypeARM64: {Sym: "ARM64", Description: "ARM64 little endian"}, + MachineTypeARMNT: {Sym: "ARMNT", Description: "ARM Thumb-2 little endian"}, + //MachineTypeAXP64: {Sym: "AXP64", Description: "AXP 64 (Same as Alpha 64)"}, + MachineTypeEBC: {Sym: "EBC", Description: "EFI byte code"}, + MachineTypeI386: {Sym: "I386", Description: "Intel 386 or later processors and compatible processors"}, + MachineTypeIA64: {Sym: "IA64", Description: "Intel Itanium processor family"}, + MachineTypeLOONGARCH32: {Sym: "LOONGARCH32", Description: "LoongArch 32-bit processor family"}, + MachineTypeLOONGARCH64: {Sym: "LOONGARCH64", Description: "LoongArch 64-bit processor family"}, + MachineTypeM32R: {Sym: "M32R", Description: "Mitsubishi M32R little endian"}, + MachineTypeMIPS16: {Sym: "MIPS16", Description: "MIPS16"}, + MachineTypeMIPSFPU: {Sym: "MIPSFPU", Description: "MIPS with FPU"}, + MachineTypeMIPSFPU16: {Sym: "MIPSFPU16", Description: "MIPS16 with FPU"}, + MachineTypePOWERPC: {Sym: "POWERPC", Description: "Power PC little endian"}, + MachineTypePOWERPCFP: {Sym: "POWERPCFP", Description: "Power PC with floating point support"}, + MachineTypeR4000: {Sym: "R4000", Description: "MIPS little endian"}, + MachineTypeRISCV32: {Sym: "RISCV32", Description: "RISC-V 32-bit address space"}, + MachineTypeRISCV64: {Sym: "RISCV64", Description: "RISC-V 64-bit address space"}, + MachineTypeRISCV128: {Sym: "RISCV128", Description: "RISC-V 128-bit address space"}, + MachineTypeSH3: {Sym: "SH3", Description: "Hitachi SH3"}, + MachineTypeSH3DSP: {Sym: "SH3DSP", Description: "Hitachi SH3 DSP"}, + MachineTypeSH4: {Sym: "SH4", Description: "Hitachi SH4"}, + MachineTypeSH5: {Sym: "SH5", Description: "Hitachi SH5"}, + MachineTypeTHUMB: {Sym: "THUMB", Description: "Thumb"}, + MachineTypeWCEMIPSV2: {Sym: "WCEMIPSV2", Description: "MIPS little-endian WCE v2"}, +} + +const ( + SubSystemUNKNOWN = 0 + SubSystemNATIVE = 1 + SubSystemWINDOWS_GUI = 2 + SubSystemWINDOWS_CUI = 3 + SubSystemOS2_CUI = 5 + SubSystemPOSIX_CUI = 7 + SubSystemNATIVE_WINDOWS = 8 + SubSystemWINDOWS_CE_GUI = 9 + SubSystemEFI_APPLICATION = 10 + SubSystemEFI_BOOT_SERVICE_DRIVER = 11 + SubSystemEFI_RUNTIME_DRIVER = 12 + SubSystemEFI_ROM = 13 + SubSystemXBOX = 14 + SubSystemWINDOWS_BOOT_APPLICATION = 16 +) + +var subSystemNames = scalar.UintMap{ + SubSystemUNKNOWN: {Sym: "UNKNOWN", Description: "An unknown subsystem"}, + SubSystemNATIVE: {Sym: "NATIVE", Description: "Device drivers and native Windows processes"}, + SubSystemWINDOWS_GUI: {Sym: "WINDOWS_GUI", Description: "The Windows graphical user interface (GUI) subsystem"}, + SubSystemWINDOWS_CUI: {Sym: "WINDOWS_CUI", Description: "The Windows character subsystem"}, + SubSystemOS2_CUI: {Sym: "OS2_CUI", Description: "The OS/2 character subsystem"}, + SubSystemPOSIX_CUI: {Sym: "POSIX_CUI", Description: "The Posix character subsystem"}, + SubSystemNATIVE_WINDOWS: {Sym: "NATIVE_WINDOWS", Description: "Native Win9x driver"}, + SubSystemWINDOWS_CE_GUI: {Sym: "WINDOWS_CE_GUI", Description: "Windows CE"}, + SubSystemEFI_APPLICATION: {Sym: "EFI_APPLICATION", Description: "An Extensible Firmware Interface (EFI) application"}, + SubSystemEFI_BOOT_SERVICE_DRIVER: {Sym: "EFI_BOOT_SERVICE_DRIVER", Description: "An EFI driver with boot services"}, + SubSystemEFI_RUNTIME_DRIVER: {Sym: "EFI_RUNTIME_DRIVER", Description: "An EFI driver with run-time services"}, + SubSystemEFI_ROM: {Sym: "EFI_ROM", Description: "An EFI ROM image"}, + SubSystemXBOX: {Sym: "XBOX", Description: "XBOX"}, + SubSystemWINDOWS_BOOT_APPLICATION: {Sym: "WINDOWS_BOOT_APPLICATION", Description: "Windows boot application."}, +} + +// type stringTable []string + +// func (m stringTable) MapStr(s scalar.Str) (scalar.Str, error) { +// if s.Actual == "" || s.Actual[0] != '/' { +// return s, nil +// } +// un, err := strconv.ParseUint(s.Actual[1:], 10, 64) +// if err != nil { +// // ignore error +// //nolint: nilerr +// return s, nil +// } +// n := int(un) +// if n >= len(m) { +// return s, nil +// } + +// s.Sym = m[n] + +// return s, nil +// } + +func strIndexNull(idx int, s string) string { + if idx > len(s) { + return "" + } + i := strings.IndexByte(s[idx:], 0) + if i == -1 { + return s + } + return s[idx : idx+i] +} + +type stringTable string + +func (m stringTable) MapStr(s scalar.Str) (scalar.Str, error) { + if s.Actual[0] == '/' { + // /### section name + + s.Actual = strings.TrimRight(s.Actual, "\x00") + + un, err := strconv.ParseUint(s.Actual[1:], 10, 64) + if err != nil { + // ignore error + //nolint: nilerr + return s, nil + } + n := int(un) - 4 + + s.Sym = strIndexNull(n, string(m)) + + return s, nil + } else if s.Actual[0:4] == "\x00\x00\x00\x00" { + // \0\0\0\0LE32 symbol name + n := binary.LittleEndian.Uint32([]byte(s.Actual)[4:8]) - 4 + s.Sym = strIndexNull(int(n), string(m)) + } else { + // right null padded + s.Actual = strings.TrimRight(s.Actual, "\x00") + } + + return s, nil +} + +func peCoffStubDecode(d *decode.D) any { + d.Endian = decode.LittleEndian + + d.FieldRawLen("signature", 4*8, d.AssertBitBuf([]byte("PE\x00\x00"))) + d.FieldU16("machine", MachineTypeNames, scalar.UintHex) + numberOfSections := d.FieldU16("number_of_sections") + d.FieldU32("time_date_stamp", scalar.UintActualUnixTime(time.RFC3339)) + pointerToSymbolTable := d.FieldU32("pointer_to_symbol_table", scalar.UintHex) + numberOfSymbols := d.FieldU32("number_of_symbols") + sizeOfOptionalHeader := d.FieldU16("size_of_optional_header") + d.FieldStruct("characteristics", func(d *decode.D) { + // TODO: wrong byte order + d.FieldBool("bytes_reversed_hi") // 0x8000 // Big endian: the MSB precedes the LSB in memory. This flag is deprecated and should be zero. + d.FieldBool("up_system_only") // 0x4000 // The file should be run only on a uniprocessor machine. + d.FieldBool("dll") // 0x2000 // The image file is a dynamic-link library (DLL). Such files are considered executable files for almost all purposes, although they cannot be directly run. + d.FieldBool("system") // 0x1000 // The image file is a system file, not a user program. + d.FieldBool("net_run_from_swap") // 0x0800 // If the image is on network media, fully load it and copy it to the swap file. + d.FieldBool("removable_run_from_swap") // 0x0400 // If the image is on removable media, fully load it and copy it to the swap file. + d.FieldBool("debug_stripped") // 0x0200 // Debugging information is removed from the image file. + d.FieldBool("32bit_machine") // 0x0100 // Machine is based on a 32-bit-word architecture. + d.FieldBool("bytes_reversed_lo") // 0x0080 // Little endian: the least significant bit (LSB) precedes the most significant bit (MSB) in memory. This flag is deprecated and should be zero. + d.FieldBool("reserved") // 0x0040 // This flag is reserved for future use. + d.FieldBool("large_address_aware") // 0x0020 // Application can handle > 2-GB addresses. + d.FieldBool("aggressive_ws_trim") // 0x0010 // Obsolete. Aggressively trim working set. This flag is deprecated for Windows 2000 and later and must be zero. + d.FieldBool("local_syms_stripped") // 0x0008 // COFF symbol table entries for local symbols have been removed. This flag is deprecated and should be zero. + d.FieldBool("line_nums_stripped") // 0x0004 // COFF line numbers have been removed. This flag is deprecated and should be zero. + d.FieldBool("executable_image") // 0x0002 // Image only. This indicates that the image file is valid and can be run. If this flag is not set, it indicates a linker error. + d.FieldBool("relocs_stripped") // 0x0001 // Image only, Windows CE, and Microsoft Windows NT and later. This indicates that the file does not contain base relocations and must therefore be loaded at its preferred base address. If the base address is not available, the loader reports an error. The default behavior of the linker is to strip base relocations from executable (EXE) files. + }) + + if pointerToSymbolTable != 0 { + pointerToSymbolTable -= msDOSStubByteSize + } + stringTablePos := (int64(pointerToSymbolTable) + int64(numberOfSymbols)*18) * 8 + + var stringTableMapper stringTable + if stringTablePos < d.Len()+4*8 { + d.SeekAbs(stringTablePos, func(d *decode.D) { + stringTableSize := d.U32() - 4 + if stringTableSize*8 > uint64(d.BitsLeft()) { + return + } + stringTableMapper = stringTable(d.UTF8(int(stringTableSize))) + // d.FramedFn(int64(stringTableSize)*8, func(d *decode.D) { + // for !d.End() { + // stringTable = append(stringTable, d.UTF8Null()) + // } + // }) + }) + } + + // how to know if image only? windows specific? + if sizeOfOptionalHeader > 0 { + d.FieldStruct("optional_header", func(d *decode.D) { + d.FramedFn(int64(sizeOfOptionalHeader)*8, func(d *decode.D) { + peFormat := d.FieldU16("format", peFormatNames, scalar.UintHex) + d.FieldU8("major_linker_version") + d.FieldU8("minor_linker_version") + d.FieldU32("size_of_code") + d.FieldU32("size_of_initialized_data") + d.FieldU32("size_of_uninitialized_data") + d.FieldU32("address_of_entry_point", scalar.UintHex) + d.FieldU32("base_of_code", scalar.UintHex) + addrSize := 64 + if peFormat == peFormat32 { + d.FieldU32("base_of_data", scalar.UintHex) + addrSize = 32 + } + + d.FieldU("image_base", addrSize, scalar.UintHex) + d.FieldU32("section_alignment") + d.FieldU32("file_alignment") + d.FieldU16("major_os_version") + d.FieldU16("minor_os_version") + d.FieldU16("major_image_version") + d.FieldU16("minor_image_version") + d.FieldU16("major_subsystem_version") + d.FieldU16("minor_subsystem_version") + d.FieldU32("win32_version") + d.FieldU32("size_of_image") + d.FieldU32("size_of_headers") + d.FieldU32("chunk_sum", scalar.UintHex) + d.FieldU16("subsystem", subSystemNames) + d.FieldStruct("dll_characteristics", func(d *decode.D) { + d.FieldBool("force_integrity") // Code Integrity checks are enforced. + d.FieldBool("dynamic_base") // DLL can be relocated at load time. + d.FieldBool("high_entropy_va") // Image can handle a high entropy 64-bit virtual address space. + d.FieldBool("reserved0") // ?? + d.FieldBool("reserved1") + d.FieldBool("reserved2") + d.FieldBool("reserved3") + d.FieldBool("reserved4") + + d.FieldBool("terminal_server_aware") // Terminal Server aware. + d.FieldBool("guard_cf") // Image supports Control Flow Guard. + d.FieldBool("wdm_driver") // A WDM driver. + d.FieldBool("appcontainer") // Image must execute in an AppContainer. + d.FieldBool("no_bind") // Do not bind the image. + d.FieldBool("no_seh") // Does not use structured exception (SE) handling. No SE handler may be called in this image. + d.FieldBool("no_isolation") // Isolation aware, but do not isolate the image. + d.FieldBool("nx_compat") // Image is NX compatible. + }) + d.FieldU("size_of_track_reserve", addrSize) + d.FieldU("size_of_stack_commit", addrSize) + d.FieldU("size_of_heap_reserve", addrSize) + d.FieldU("size_of_heap_commit", addrSize) + d.FieldU32("loader_flags") + d.FieldU32("number_of_rva_and_sizes") + + d.FieldU32("export_table_address", scalar.UintHex) //The export table address and size. For more information see .edata Section (Image Only). + d.FieldU32("export_table_size") + d.FieldU32("import_table_address", scalar.UintHex) //The import table address and size. For more information, see The .idata Section. + d.FieldU32("import_table_size") + d.FieldU32("resource_table_address", scalar.UintHex) //The resource table address and size. For more information, see The .rsrc Section. + d.FieldU32("resource_table_size") + d.FieldU32("exception_table_address", scalar.UintHex) //The exception table address and size. For more information, see The .pdata Section. + d.FieldU32("exception_table_size") + d.FieldU32("certificate_table_address", scalar.UintHex) //The attribute certificate table address and size. For more information, see The Attribute Certificate Table (Image Only). + d.FieldU32("certificate_table_size") + d.FieldU32("base_relocation_table_address", scalar.UintHex) //The base relocation table address and size. For more information, see The .reloc Section (Image Only). + d.FieldU32("base_relocation_table_size") + d.FieldU32("debug_address", scalar.UintHex) //The debug data starting address and size. For more information, see The .debug Section. + d.FieldU32("debug_size") + d.FieldU64("architecture") //Reserved, must be 0 + d.FieldU64("global_ptr", scalar.UintHex) //The RVA of the value to be stored in the global pointer register. The size member of this structure must be set to zero. + d.FieldU32("tls_table_address", scalar.UintHex) //The thread local storage (TLS) table address and size. For more information, see The .tls Section. + d.FieldU32("tls_table_size") + d.FieldU32("load_config_table_address", scalar.UintHex) //The load configuration table address and size. For more information, see The Load Configuration Structure (Image Only). + d.FieldU32("load_config_table_size") + d.FieldU32("bound_import_address", scalar.UintHex) //The bound import table address and size. + d.FieldU32("bound_import_size") + d.FieldU32("iat_address", scalar.UintHex) //The import address table address and size. For more information, see Import Address Table. + d.FieldU32("iat_size") + d.FieldU32("delay_import_descriptor_address", scalar.UintHex) //The delay import descriptor address and size. For more information, see Delay-Load Import Tables (Image Only). + d.FieldU32("delay_import_descriptor_size") + d.FieldU32("clr_runtime_header_address", scalar.UintHex) //The CLR runtime header address and size. For more information, see The .cormeta Section (Object Only). + d.FieldU32("clr_runtime_header_size") + d.FieldU64("reserved") //must be zero + + // TODO: where? + /*numberOfRvaAndSizes :=*/ + /* + d.FieldArray("data_directories", func(d *decode.D) { + for i := 0; i < int(numberOfRvaAndSizes); i++ { + d.FieldStruct("data_directory", func(d *decode.D) { + d.FieldU32("virtual_address", scalar.UintHex) + d.FieldU32("size") + }) + } + }) + */ + + d.FieldRawLen("unknown", d.BitsLeft()) + }) + }) + } + + // TODO: section_alignment? + + d.FieldArray("sections", func(d *decode.D) { + for i := uint64(0); i < numberOfSections; i++ { + d.FieldStruct("section", func(d *decode.D) { + d.FieldUTF8("name", 8, stringTableMapper) // An 8-byte, null-padded UTF-8 encoded string. If the string is exactly 8 characters long, there is no terminating null. For longer names, this field contains a slash (/) that is followed by an ASCII representation of a decimal number that is an offset into the string table. Executable images do not use a string table and do not support section names longer than 8 characters. Long names in object files are truncated if they are emitted to an executable file. + d.FieldU32("virtual_size") // The total size of the section when loaded into memory. If this value is greater than SizeOfRawData, the section is zero-padded. This field is valid only for executable images and should be set to zero for object files. + d.FieldU32("virtual_address", scalar.UintHex) // For executable images, the address of the first byte of the section relative to the image base when the section is loaded into memory. For object files, this field is the address of the first byte before relocation is applied; for simplicity, compilers should set this to zero. Otherwise, it is an arbitrary value that is subtracted from offsets during relocation. + sizeOfRawData := d.FieldU32("size_of_raw_data") // The size of the section (for object files) or the size of the initialized data on disk (for image files). For executable images, this must be a multiple of FileAlignment from the optional header. If this is less than VirtualSize, the remainder of the section is zero-filled. Because the SizeOfRawData field is rounded but the VirtualSize field is not, it is possible for SizeOfRawData to be greater than VirtualSize as well. When a section contains only uninitialized data, this field should be zero. + d.FieldU32("pointer_to_raw_data", scalar.UintHex) // The file pointer to the first page of the section within the COFF file. For executable images, this must be a multiple of FileAlignment from the optional header. For object files, the value should be aligned on a 4-byte boundary for best performance. When a section contains only uninitialized data, this field should be zero. + d.FieldU32("Pointer_to_relocations", scalar.UintHex) // The file pointer to the beginning of relocation entries for the section. This is set to zero for executable images or if there are no relocations. + d.FieldU32("Pointer_to_line_numbers", scalar.UintHex) // The file pointer to the beginning of line-number entries for the section. This is set to zero if there are no COFF line numbers. This value should be zero for an image because COFF debugging information is deprecated. + d.FieldU16("number_of_relocations") // The number of relocation entries for the section. This is set to zero for executable images. + d.FieldU16("number_of_line_numbers") // The number of line-number entries for the section. This value should be zero for an image because COFF debugging information is deprecated. + d.FieldU32("characteristics") // The flags that describe the characteristics of the section. For more information, see Section Flags. + + _ = sizeOfRawData + }) + } + }) + + // var stringTableMapperPos int64 + + // TODO: if pointerToSymbolTable != 0? + + if pointerToSymbolTable != 0 { + + d.FieldArray("symbol_table", func(d *decode.D) { + d.SeekAbs(int64(pointerToSymbolTable*8), func(d *decode.D) { + for i := uint64(0); i < numberOfSymbols; i++ { + d.FieldStruct("symbol", func(d *decode.D) { + // TODO: name + d.FieldUTF8("name", 8, stringTableMapper) // The name of the symbol, represented by a union of three structures. An array of 8 bytes is used if the name is not more than 8 bytes long. For more information, see Symbol Name Representation. + d.FieldU32("value") // The value that is associated with the symbol. The interpretation of this field depends on SectionNumber and StorageClass. A typical meaning is the relocatable address. + d.FieldU16("section_number") // The signed integer that identifies the section, using a one-based index into the section table. Some values have special meaning, as defined in section 5.4.2, "Section Number Values." + d.FieldU16("type") // A number that represents type. Microsoft tools set this field to 0x20 (function) or 0x0 (not a function). For more information, see Type Representation. + d.FieldU8("storage_class") // An enumerated value that represents storage class. For more information, see Storage Class. + d.FieldU8("number_of_aux_symbols") // The number of auxiliary symbol table entries that follow this record. + }) + } + // stringTablePos = d.Pos() + }) + }) + + d.SeekAbs(stringTablePos, func(d *decode.D) { + // TODO: if pos != 0? + d.FieldStruct("string_table", func(d *decode.D) { + stringTableSize := d.FieldU32("size") - 4 + d.FramedFn(int64(stringTableSize*8), func(d *decode.D) { + d.FieldArray("entries", func(d *decode.D) { + for !d.End() { + d.FieldUTF8Null("entry") + } + }) + }) + }) + }) + } + + return nil +} diff --git a/format/pe/pe_msdos_stub.go b/format/pe/pe_msdos_stub.go new file mode 100644 index 0000000000..2cb85f174d --- /dev/null +++ b/format/pe/pe_msdos_stub.go @@ -0,0 +1,56 @@ +package pe + +// https://osandamalith.com/2020/07/19/exploring-the-ms-dos-stub/ + +import ( + "github.com/wader/fq/format" + "github.com/wader/fq/pkg/decode" + "github.com/wader/fq/pkg/interp" + "github.com/wader/fq/pkg/scalar" +) + +// TODO: probe? + +func init() { + interp.RegisterFormat( + format.PE_MSDOS_Stub, + &decode.Format{ + Description: "MS-DOS Stub", + DecodeFn: msDosStubDecode, + }) +} + +func msDosStubDecode(d *decode.D) any { + d.Endian = decode.LittleEndian + + d.FieldU16("e_magic", scalar.UintDescription("Magic number"), d.UintAssert(0x5a4d), scalar.UintHex) + d.FieldU16("e_cblp", scalar.UintDescription("Bytes on last page of file")) + d.FieldU16("e_cp", scalar.UintDescription("Pages in file")) + d.FieldU16("e_crlc", scalar.UintDescription("Relocations")) + d.FieldU16("e_cparhdr", scalar.UintDescription("Size of header in paragraphs")) + d.FieldU16("e_minalloc", scalar.UintDescription("Minimum extra paragraphs needed")) + d.FieldU16("e_maxalloc", scalar.UintDescription("Maximum extra paragraphs needed")) + d.FieldU16("e_ss", scalar.UintDescription("Initial (relative) SS value")) + d.FieldU16("e_sp", scalar.UintDescription("Initial SP value")) + d.FieldU16("e_csum", scalar.UintDescription("Checksum")) + d.FieldU16("e_ip", scalar.UintDescription("Initial IP value")) + d.FieldU16("e_cs", scalar.UintDescription("Initial (relative) CS value")) + d.FieldU16("e_lfarlc", scalar.UintDescription("File address of relocation table")) + d.FieldU16("e_ovno", scalar.UintDescription("Overlay number")) + d.FieldRawLen("e_res", 4*16, scalar.BitBufDescription("Reserved words")) + d.FieldU16("e_oemid", scalar.UintDescription("OEM identifier (for e_oeminfo)")) + d.FieldU16("e_oeminfo", scalar.UintDescription("OEM information; e_oemid specific")) + d.FieldRawLen("e_res2", 10*16, scalar.BitBufDescription("Reserved words")) + lfanew := d.FieldU32("e_lfanew", scalar.UintDescription("File address of new exe header")) + + // TODO: x86 format in the future + d.FieldRawLen("stub", 64*8, scalar.BitBufDescription("Sub program")) + + subEndPos := d.Pos() + + // TODO: is not padding i guess? + padding := lfanew*8 - uint64(subEndPos) + d.FieldRawLen("padding", int64(padding)) + + return nil +}