diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85affa2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode +*.exe +*.tlscan \ No newline at end of file diff --git a/README.md b/README.md index a62b1d2..c6fc4e9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ # PCIeConfigSpace -Read a .TLScan file containing the PCIe Configuration Space and Generate a Write Mask + +This utility interprets PCIe configuration space data extracted by [TeleScan PE Software](https://www.teledynelecroy.com/protocolanalyzer/pci-express/telescan-pe-software). It serves two main functions: + +1. Analyzing the configuration space +2. Generating a corresponding write mask + +The tool produces two output files compatible with pcileech-fpga: + +1. A shadow configuration space +2. A write mask + +These files can be used to enhance the legitimacy of your DMA card's appearance when utilizing [pcileech-fpga](https://github.com/ufrisk/pcileech-fpga). + +## Usage + +```shell +PS C:\Tools\PCIeConfigSpace> .\TLScan.exe -h +Usage of C:\Tools\PCIeConfigSpace\TLScan.exe: + -i string + The .tlscan scan file of your donor card (default "donor.tlscan") + -overwrite + Automatically overwrite existing file + -so string + The output .coe file containing your formatted config space (default "pcileech_cfgspace_extracted.coe") + -wo string + The output .coe file containing your formatted config space writemask (default "pcileech_cfgspace_writemask_extracted.coe") +``` + +### Resources + +- PCI Local Bus Specification (Revision 2.2) +- PCI Code and ID Assignment Specification (Revision 1.11) +- PCI Express® Base Specification (Revision 3.0) +- [Writemask.it by Simonrak](https://github.com/Simonrak/writemask.it) diff --git a/capabilitiesData.go b/capabilitiesData.go new file mode 100644 index 0000000..baa238e --- /dev/null +++ b/capabilitiesData.go @@ -0,0 +1,59 @@ +package main + +import ( + "bytes" + "encoding/binary" +) + +func GetCapabilityWriteMask(capabilityID uint8) []byte { + capInfo := CAPABILITIES[capabilityID] + return Uint32SliceToBytes(capInfo.WriteMask) +} + +func GetCapabilityID(capabilityBase []byte) byte { + return capabilityBase[0] +} + +func GetCapabilityNextPointer(capabilityBase []byte) byte { + return capabilityBase[1] +} + +func GetCapabilityName(capabilityID uint8) string { + capInfo := CAPABILITIES[capabilityID] + return capInfo.Name +} + +func GetCapabilitySize(capabilityBase []byte) int { + capabilityID := GetCapabilityID(capabilityBase) + capInfo := CAPABILITIES[capabilityID] + + if capInfo.Size == -1 { //MSI 14bytes or 24 bytes + var messageControl uint16 + err := binary.Read(bytes.NewReader(capabilityBase[2:]), binary.LittleEndian, &messageControl) + if err != nil { + return -1 + } + bitArray := Uint16ToBitArray(messageControl) + if !bitArray[8] { // If 64 bit disabled + return 1 + } else if bitArray[8] && bitArray[7] { // If 64 bit enabled and pre-vector masking is true + return 6 + } else { + return 4 + } + + } else if capInfo.Size == -2 { //MSI-X - get TableSize + var messageControl uint16 + err := binary.Read(bytes.NewReader(capabilityBase[2:]), binary.LittleEndian, &messageControl) + if err != nil { + return -1 + } + bitArray := Uint16ToBitArray(messageControl) + bitArrayTableSize := bitArray[6:] + tableSize := BinaryArrayToDecimal(bitArrayTableSize) + 1 + return tableSize / 2 + + } else { + return capInfo.Size + } +} diff --git a/pcieData.go b/pcieData.go new file mode 100644 index 0000000..66b5535 --- /dev/null +++ b/pcieData.go @@ -0,0 +1,29 @@ +package main + +import ( + "bytes" + "encoding/binary" + "fmt" + "unsafe" +) + +func ReadPCIeHeader(data []byte, offset int) (PCIeHeader, error) { + var header PCIeHeader + size := int(unsafe.Sizeof(header)) + + if len(data) < offset+size { + return header, fmt.Errorf("not enough data to read PCIeHeader at offset %d", offset) + } + + err := binary.Read(bytes.NewReader(data[offset:offset+size]), binary.LittleEndian, &header) + if err != nil { + return header, fmt.Errorf("failed to read PCIeHeader: %v", err) + } + + err = Format3BytesLittleEndian(&header.Reserved1, &header.ClassCode) + if err != nil { + return header, fmt.Errorf("failed to format three byte array: %v", err) + } + + return header, nil +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..affb070 --- /dev/null +++ b/structs.go @@ -0,0 +1,136 @@ +package main + +import "encoding/xml" + +type Devices struct { + XMLName xml.Name `xml:"devices"` + Devices []Device `xml:"device"` +} + +type Device struct { + DeviceID string `xml:"device,attr"` + Type string `xml:"type,attr"` + Function string `xml:"function,attr"` + Bus string `xml:"bus,attr"` + Config Config `xml:"config_space"` +} + +type Config struct { + Bytes string `xml:"bytes"` +} + +type PCIeHeader struct { + VendorID uint16 + DeviceID uint16 + Command uint16 + Status uint16 + RevisionID uint8 + ClassCode [3]byte + CacheLineSize uint8 + LatencyTimer uint8 + HeaderType uint8 + BuiltInSelfTest uint8 + BaseRegister0 uint32 + BaseRegister1 uint32 + BaseRegister2 uint32 + BaseRegister3 uint32 + BaseRegister4 uint32 + BaseRegister5 uint32 + CardbusCISPointer uint32 + SubsystemVendorID uint16 + SubsystemID uint16 + ExpansionROMBaseAddress uint32 + CapabilitiesPointer uint8 + Reserved1 [3]byte + Reserved2 uint32 + InterruptLine uint8 + InterruptPin uint8 + MinGrant uint8 + MaxLatency uint8 +} + +type CapabilitiesInfo struct { + Name string + Size int + WriteMask []uint32 +} + +// PCI Code and ID Assignment Specification Revision 1.11 +var CAPABILITIES map[uint8]CapabilitiesInfo = map[uint8]CapabilitiesInfo{ + 0x00: {"NULL", 0, nil}, + 0x01: {"PCI Power Management Interface ", len(Write_protected_bits_PM), Write_protected_bits_PM}, + 0x02: {"Accelerated Graphics Port (DEPRECATED)", 0, nil}, + 0x03: {"Vital Product Data", len(Write_protected_bits_VPD), Write_protected_bits_VPD}, + 0x04: {"Slot Identification", 8, nil}, + 0x05: {"Message Signaled Interrupts", -1, nil}, + 0x06: {"CompactPCI Hot Swap", 8, nil}, + 0x07: {"PCI-X", 0, nil}, + 0x08: {"HyperTransport ", 0, nil}, + 0x09: {"Vendor Specific", len(Write_protected_bits_VSC), Write_protected_bits_VSC}, + 0x0A: {"Debug port", 16, nil}, + 0x0B: {"CompactPCI central resource control ", 0, nil}, + 0x0C: {"PCI Hot-Plug", 8, nil}, + 0x0D: {"PCI Bridge Subsystem Vendor ID", 8, nil}, + 0x0E: {"AGP 8x", 0, nil}, + 0x0F: {"Secure Device", 0, nil}, + 0x10: {"PCI Express", len(Write_protected_bits_PCIE), Write_protected_bits_PCIE}, + 0x11: {"MSI-X", -2, nil}, + 0x12: {"Serial ATA Data/Index Configuration", 8, nil}, + 0x13: {"Advanced Features", 0, nil}, + 0x14: {"Enhanced Allocation", 0, nil}, + 0x15: {"Flattening Portal Bridge", 0, nil}, +} + +// PCI Code and ID Assignment Specification Revision 1.11 +var EXTENDED_CAPABILITIES map[int]CapabilitiesInfo = map[int]CapabilitiesInfo{ + 0x0001: {"advanced error reporting", len(Write_protected_bits_AER), Write_protected_bits_AER}, + 0x0002: {"virtual channel", 0, nil}, + 0x0003: {"device serial number", len(Write_protected_bits_DSN), Write_protected_bits_DSN}, + 0x0004: {"power budgeting", 2, nil}, + 0x0005: {"root complex link declaration", 0, nil}, + 0x0006: {"root complex internal link control", 3, nil}, + 0x0007: {"root complex event collector endpoint association", 3, nil}, + 0x0008: {"multi-function virtual channel", 0, nil}, + 0x0009: {"virtual channel", 0, nil}, + 0x000A: {"root complex register block", len(Write_protected_bits_VSEC), Write_protected_bits_VSEC}, + 0x000B: {"vendor specific", len(Write_protected_bits_PTM), Write_protected_bits_PTM}, + 0x000C: {"configuration access correlation", 3, nil}, + 0x000D: {"access control services", 0, nil}, + 0x000E: {"alternative routing-ID interpretation", 2, nil}, + 0x000F: {"address translation services", 0, nil}, + 0x0010: {"single root IO virtualization", 0, nil}, + 0x0011: {"multi-root IO virtualization", 0, nil}, + 0x0012: {"multicast", 0, nil}, + 0x0013: {"page request interface", 2, nil}, + 0x0014: {"AMD reserved", 0, nil}, + 0x0015: {"resizable BAR", 0, nil}, + 0x0016: {"dynamic power allocation", 4, nil}, + 0x0017: {"TPH requester", len(Write_protected_bits_TPH), Write_protected_bits_TPH}, + 0x0018: {"latency tolerance reporting", 2, nil}, + 0x0019: {"secondary PCI express", 0, nil}, + 0x001A: {"protocol multiplexing", 3, nil}, + 0x001B: {"process address space ID", 2, nil}, + 0x001C: {"LN requester", 2, nil}, + 0x001D: {"downstream port containment", 2, nil}, + 0x001E: {"L1 PM substates", len(Write_protected_bits_L1PM), Write_protected_bits_L1PM}, + 0x001F: {"precision time measurement", 2, nil}, + 0x0020: {"M-PCIe", 0, nil}, + 0x0021: {"FRS queueing", 3, nil}, + 0x0022: {"Readiness time reporting", 2, nil}, + 0x0023: {"designated vendor specific", 0, nil}, + 0x0024: {"VF resizable BAR", 0, nil}, + 0x0025: {"data link feature", 3, nil}, + 0x0026: {"physical layer 16.0 GT/s", 4, nil}, + 0x0027: {"receiver lane margining", 0, nil}, + 0x0028: {"hierarchy ID", 2, nil}, + 0x0029: {"native PCIe enclosure management", 0, nil}, + 0x002A: {"physical layer 32.0 GT/s", 3, nil}, + 0x002B: {"alternate protocol", 0, nil}, + 0x002C: {"system firmware intermediary", 0, nil}, +} + +var PCIeHeaderReadWriteMask [16]uint32 = [16]uint32{ + 0x00000000, 0x470500f9, 0x00000000, 0xffff0040, + 0xf0ffffff, 0xffffffff, 0xf0ffffff, 0xffffffff, + 0xf0ffffff, 0xf0ffffff, 0x00000000, 0x00000000, + 0x01f8ffff, 0x00000000, 0x00000000, 0xff000000} diff --git a/tlscan.go b/tlscan.go new file mode 100644 index 0000000..62f3f55 --- /dev/null +++ b/tlscan.go @@ -0,0 +1,177 @@ +package main + +import ( + "bufio" + "encoding/xml" + "flag" + "fmt" + "io" + "os" + "strings" +) + +type CommandLineArgs struct { + inputTlScanFile *string + shadowOutputFile *string + writeMaskOutputFile *string + overwriteAuto *bool +} + +func createWriteMask(sizeInBytes int) []byte { + var writeMask []byte + for i := 0; i < sizeInBytes; i++ { + writeMask = append(writeMask, 0xFF) + } + + return writeMask +} + +func parseCmdLine() CommandLineArgs { + var results CommandLineArgs + + inputHelp := "The .tlscan scan file of your donor card" + defaultInput := "donor.tlscan" + results.inputTlScanFile = flag.String("i", defaultInput, inputHelp) + + shadowOutputHelp := "The output .coe file containing your formatted config space" + defaultShadowOutput := "pcileech_cfgspace_extracted.coe" + results.shadowOutputFile = flag.String("so", defaultShadowOutput, shadowOutputHelp) + + writemaskOutputHelp := "The output .coe file containing your formatted config space writemask" + defaultWriteMaskOutput := "pcileech_cfgspace_writemask_extracted.coe" + results.writeMaskOutputFile = flag.String("wo", defaultWriteMaskOutput, writemaskOutputHelp) + + overwriteHelp := "Automatically overwrite existing file" + results.overwriteAuto = flag.Bool("overwrite", false, overwriteHelp) + + flag.Parse() + return results +} + +func main() { + cmdArgs := parseCmdLine() + + tlscanFile, err := os.Open(*cmdArgs.inputTlScanFile) + if err != nil { + fmt.Println("Error opening .tlscan file:", err) + return + } + + defer tlscanFile.Close() + + content, err := io.ReadAll(tlscanFile) + if err != nil { + fmt.Println("Error reading contents of .tlscan file:", err) + return + } + + var devices Devices + err = xml.Unmarshal(content, &devices) + if err != nil { + fmt.Println("Error unmarshaling XML:", err) + return + } + + for _, device := range devices.Devices { + cfgSpace, err := HexStringToBytes(RemoveWhitespace(device.Config.Bytes)) + if err != nil { + fmt.Println("Error converting hex string to bytes:", err) + return + } + + writeMaskBytes := createWriteMask(len(cfgSpace)) + + SetPCIeHeaderWriteMask(&writeMaskBytes) + + header, err := ReadPCIeHeader(cfgSpace, 0) + if err != nil { + fmt.Printf("Error Reading PCIe Header: %v\n", err) + return + } + + fmt.Printf("Vendor ID: 0x%04X\n", header.VendorID) + fmt.Printf("Device ID: 0x%04X\n", header.DeviceID) + fmt.Printf("Subsystem Vendor ID: 0x%02X\n", header.SubsystemVendorID) + fmt.Printf("Subsystem ID: 0x%02X\n", header.SubsystemID) + fmt.Printf("Revision: 0x%02X\n", header.RevisionID) + fmt.Printf("Class Code: 0x%X\n", header.ClassCode) + fmt.Printf("BAR0: 0x%X\n", header.BaseRegister0) + fmt.Printf("BAR1: 0x%X\n", header.BaseRegister1) + fmt.Printf("BAR2: 0x%X\n", header.BaseRegister2) + fmt.Printf("BAR3: 0x%X\n", header.BaseRegister3) + fmt.Printf("BAR4: 0x%X\n", header.BaseRegister4) + fmt.Printf("BAR5: 0x%X\n", header.BaseRegister5) + + capIndex := int(header.CapabilitiesPointer) + capPtr := cfgSpace[capIndex:] + fmt.Printf("Capabilities:\n") + for { + capabilityID := GetCapabilityID(capPtr) + dwordSize := GetCapabilitySize(capPtr) + capabilityName := GetCapabilityName(capabilityID) + fmt.Printf("\t%s - %d bytes\n", capabilityName, dwordSize*4) + + capWriteMask := GetCapabilityWriteMask(capabilityID) + + SetWriteMaskOffset(&writeMaskBytes, capIndex, capWriteMask) + + capIndex = int(GetCapabilityNextPointer(capPtr)) + if capIndex == 0 { + break + } + + capPtr = cfgSpace[capIndex:] + } + + cfgSpaceBytesUint32 := byteToUint32BigEndian(cfgSpace) + writeFile(cfgSpaceBytesUint32, *cmdArgs.shadowOutputFile, *cmdArgs.overwriteAuto) + + writeMaskBytesUint32 := byteToUint32LittleEndian(writeMaskBytes) + writeFile(writeMaskBytesUint32, *cmdArgs.writeMaskOutputFile, *cmdArgs.overwriteAuto) + } +} + +func writeFile(slice []uint32, outputFile string, overwrite bool) { + + // Check if file exists + _, err := os.Stat(outputFile) + if err == nil { + if !overwrite { + fmt.Printf("File %s already exists. Overwrite? (y/n): ", outputFile) + reader := bufio.NewReader(os.Stdin) + response, _ := reader.ReadString('\n') + response = strings.TrimSpace(strings.ToLower(response)) + + if response != "y" && response != "yes" { + fmt.Println("Operation cancelled.") + return + } + } + } else if !os.IsNotExist(err) { + fmt.Println("Error checking file:", err) + return + } + + var builder strings.Builder + builder.WriteString("memory_initialization_radix=16;\nmemory_initialization_vector=\n\n") + + for i, value := range slice { + builder.WriteString(fmt.Sprintf("%08x", value)) + + if i == len(slice)-1 { + builder.WriteString("\n") + } else if (i+1)%4 == 0 { + builder.WriteString("\n") + } else { + builder.WriteString(",") + } + } + + err = os.WriteFile(outputFile, []byte(builder.String()), 0644) + if err != nil { + fmt.Printf("Error writing to %s: %s\n", outputFile, err) + return + } + + fmt.Printf("Successfully wrote %s\n", outputFile) +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..4cc33d6 --- /dev/null +++ b/utils.go @@ -0,0 +1,104 @@ +package main + +import ( + "encoding/binary" + "fmt" + "strings" + "unicode" +) + +func RemoveWhitespace(str string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r + }, str) +} + +func Uint32SliceToBytes(input []uint32) []byte { + result := make([]byte, len(input)*4) + for i, v := range input { + binary.LittleEndian.PutUint32(result[i*4:], v) + } + return result +} + +func Uint16ToBitArray(num uint16) [16]bool { + var bitArray [16]bool + for i := 0; i < 16; i++ { + bitArray[15-i] = (num & (1 << i)) != 0 + } + return bitArray +} + +func byteToUint32LittleEndian(b []byte) []uint32 { + // Ensure the byte slice length is a multiple of 4 + if len(b)%4 != 0 { + panic("Byte slice length must be a multiple of 4") + } + + result := make([]uint32, len(b)/4) + + for i := 0; i < len(b); i += 4 { + result[i/4] = binary.LittleEndian.Uint32(b[i : i+4]) + } + + return result +} + +func byteToUint32BigEndian(b []byte) []uint32 { + // Ensure the byte slice length is a multiple of 4 + if len(b)%4 != 0 { + panic("Byte slice length must be a multiple of 4") + } + + result := make([]uint32, len(b)/4) + + for i := 0; i < len(b); i += 4 { + result[i/4] = binary.BigEndian.Uint32(b[i : i+4]) + } + + return result +} + +func BinaryArrayToDecimal(binaryArray []bool) int { + result := 0 + for i, bit := range binaryArray { + if bit { + result += 1 << (len(binaryArray) - 1 - i) + } + } + return result +} + +func HexStringToBytes(hexStr string) ([]byte, error) { + var bytes []byte + length := len(hexStr) + if length%2 != 0 { + return nil, fmt.Errorf("invalid hex string length") + } + for i := 0; i < length; i += 2 { + var b uint8 + _, err := fmt.Sscanf(hexStr[i:i+2], "%2x", &b) + if err != nil { + return nil, err + } + bytes = append(bytes, byte(b)) + } + return bytes, nil +} + +// Read3BytesLittleEndian reads 3 bytes from a slice in little-endian order +func Format3BytesLittleEndian(dataToFormat ...*[3]byte) error { + + for i, data := range dataToFormat { + if len(data) < 3 { + return fmt.Errorf("not enough data to format in %d: need 3 bytes, got %d", i, len(data)) + } + var tempData [3]byte = [3]byte{data[2], data[1], data[0]} + *data = tempData + } + + return nil +} diff --git a/writeMask.go b/writeMask.go new file mode 100644 index 0000000..16a535f --- /dev/null +++ b/writeMask.go @@ -0,0 +1,178 @@ +package main + +import ( + "encoding/binary" +) + +// Write Mask Protected Bits from https://github.com/Simonrak/writemask.it/blob/main/WritemaskerTM.py +var Write_protected_bits_PCIE []uint32 = []uint32{ + 0x00000f00, + 0x00000010, + 0xff7f0f00, + 0x00000000, + 0xcb0d00c0, + 0x00000000, + 0x0000ffff, + 0x00000000, + 0x00000000, + 0x00000000, + 0xff7f0000, + 0x00000000, + 0xbfff2000, +} + +var Write_protected_bits_PM []uint32 = []uint32{ + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_MSI_ENABLED_0 []uint32 = []uint32{ + 0x0000f104, +} + +var Write_protected_bits_MSI_64_bit_1 []uint32 = []uint32{ + 0x0000f104, + 0x03000000, + 0x00000000, + 0xffff0000, +} + +var Write_protected_bits_MSI_Multiple_Message_Capable_1 []uint32 = []uint32{ + 0x0000f104, + 0x03000000, + 0x00000000, + 0xffff0000, + 0x00000000, + 0x01000000, +} + +var Write_protected_bits_MSIX_3 []uint32 = []uint32{ + 0x000000c0, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_MSIX_4 []uint32 = []uint32{ + 0x000000c0, + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_MSIX_5 []uint32 = []uint32{ + 0x000000c0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_MSIX_6 []uint32 = []uint32{ + 0x000000c0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_MSIX_7 []uint32 = []uint32{ + 0x000000c0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_MSIX_8 []uint32 = []uint32{ + 0x000000c0, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_VPD []uint32 = []uint32{ + 0x0000ffff, + 0xffffffff, +} + +var Write_protected_bits_VSC []uint32 = []uint32{ + 0x000000ff, + 0xffffffff, +} + +var Write_protected_bits_TPH []uint32 = []uint32{ + 0x00000000, + 0x00000000, + 0x070c0000, +} + +var Write_protected_bits_VSEC []uint32 = []uint32{ + 0x00000000, + 0x00000000, + 0xffffffff, + 0xffffffff, +} + +var Write_protected_bits_AER []uint32 = []uint32{ + 0x00000000, + 0x31f0ff07, + 0x31f0ff07, + 0x31f0ff07, + 0xc1f10000, + 0xc1f10000, + 0x40050000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_DSN []uint32 = []uint32{ + 0x00000000, + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_LTR []uint32 = []uint32{ + 0x00000000, + 0x00000000, +} + +var Write_protected_bits_L1PM []uint32 = []uint32{ + 0x00000000, + 0x00000000, + 0x3f00ffe3, + 0xfb000000, +} + +var Write_protected_bits_PTM []uint32 = []uint32{ + 0x00000000, + 0x00000000, + 0x00000000, + 0x03ff0000, +} + +func convertToBytes(arr [16]uint32) []byte { + result := make([]byte, len(arr)*4) // Each uint32 is 4 bytes + for i, _ := range arr { + binary.LittleEndian.PutUint32(result[i*4:], arr[i]) + } + return result +} + +func SetPCIeHeaderWriteMask(writeMaskBytes *[]byte) { + pcieWriteMask := convertToBytes(PCIeHeaderReadWriteMask) + copy((*writeMaskBytes)[:], pcieWriteMask) + +} + +func SetWriteMaskOffset(writeMaskBytes *[]byte, offset int, data []byte) { + copy((*writeMaskBytes)[offset:], data) +}