...

Source file src/github.com/sassoftware/relic/lib/authenticode/pedigest.go

Documentation: github.com/sassoftware/relic/lib/authenticode

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package authenticode
    18  
    19  import (
    20  	"bytes"
    21  	"crypto"
    22  	"debug/pe"
    23  	"encoding/binary"
    24  	"errors"
    25  	"hash"
    26  	"io"
    27  	"io/ioutil"
    28  )
    29  
    30  // PE-COFF: https://www.microsoft.com/en-us/download/details.aspx?id=19509
    31  // PE Authenticode: http://msdn.microsoft.com/en-us/windows/hardware/gg463180.aspx
    32  
    33  type PEDigest struct {
    34  	OrigSize   int64
    35  	Imprint    []byte
    36  	PageHashes []byte
    37  	Hash       crypto.Hash
    38  	markers    *peHeaderValues
    39  }
    40  
    41  const dosHeaderSize = 64
    42  
    43  // Calculate a digest (message imprint) over a PE image. Returns a structure
    44  // that can be used to sign the imprint and produce a binary patch to apply the
    45  // signature.
    46  func DigestPE(r io.Reader, hash crypto.Hash, doPageHash bool) (*PEDigest, error) {
    47  	// Read and buffer all the headers
    48  	buf := bytes.NewBuffer(make([]byte, 0, 4096))
    49  	peStart, err := readDosHeader(r, buf)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	if _, err := io.CopyN(buf, r, peStart-dosHeaderSize); err != nil {
    54  		return nil, err
    55  	}
    56  	fh, err := readCoffHeader(r, buf)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	hvals, err := readOptHeader(r, buf, peStart, fh)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	sections, err := readSections(r, buf, fh, hvals)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	digester := setupDigester(hash, buf.Bytes(), hvals, sections, doPageHash)
    69  	// Hash sections
    70  	nextSection := hvals.sizeOfHdr
    71  	for _, sh := range sections {
    72  		if sh.SizeOfRawData == 0 {
    73  			continue
    74  		}
    75  		if int64(sh.PointerToRawData) != nextSection {
    76  			return nil, errors.New("PE sections are out of order")
    77  		}
    78  		if err := digester.section(r, sh); err != nil {
    79  			return nil, err
    80  		}
    81  		nextSection += int64(sh.SizeOfRawData)
    82  	}
    83  	// Hash trailer after the sections and cert table
    84  	origSize, err := readTrailer(r, digester.imageDigest, nextSection, hvals.certStart, hvals.certSize)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	imprint, pagehashes, err := digester.finish()
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return &PEDigest{origSize, imprint, pagehashes, hash, hvals}, nil
    93  }
    94  
    95  type imageHasher struct {
    96  	hashFunc    crypto.Hash
    97  	imageDigest hash.Hash
    98  	pageHashes  []byte
    99  	zeroPage    []byte
   100  	pageBuf     []byte
   101  	doPageHash  bool
   102  	lastPage    uint32
   103  }
   104  
   105  func setupDigester(hash crypto.Hash, header []byte, hvals *peHeaderValues, sections []pe.SectionHeader32, doPageHash bool) *imageHasher {
   106  	imageDigest := hash.New()
   107  	imageDigest.Write(header)
   108  	h := &imageHasher{hashFunc: hash, imageDigest: imageDigest, doPageHash: doPageHash}
   109  	if doPageHash {
   110  		h.zeroPage = make([]byte, hvals.sectionAlign) // full page of zeroes, for padding
   111  		h.pageBuf = make([]byte, hvals.sectionAlign)  // scratch space
   112  		// make space for all the page hashes
   113  		pages := 2
   114  		for _, sh := range sections {
   115  			spage := (sh.SizeOfRawData + uint32(hvals.sectionAlign) - 1) / uint32(hvals.sectionAlign)
   116  			pages += int(spage)
   117  		}
   118  		h.pageHashes = make([]byte, 0, pages*(4+hash.Size()))
   119  		// the first page is the headers padded out to a full page with the
   120  		// signature bits snipped out in the same way as for the regular
   121  		// imprint. the padding is done based on the full size of the
   122  		// header, so the data being hashed is 12 bytes short of a full
   123  		// page
   124  		removed := int(hvals.sizeOfHdr) - len(header)
   125  		h.addPageHash(0, header, removed)
   126  	}
   127  	return h
   128  }
   129  
   130  func (h *imageHasher) section(r io.Reader, sh pe.SectionHeader32) error {
   131  	if !h.doPageHash {
   132  		_, err := io.CopyN(h.imageDigest, r, int64(sh.SizeOfRawData))
   133  		return err
   134  	}
   135  	position := sh.PointerToRawData
   136  	remaining := int(sh.SizeOfRawData)
   137  	for remaining > 0 {
   138  		n := remaining
   139  		if n > len(h.pageBuf) {
   140  			n = len(h.pageBuf)
   141  		}
   142  		buf := h.pageBuf[:n]
   143  		if _, err := io.ReadFull(r, buf); err != nil {
   144  			return err
   145  		}
   146  		h.imageDigest.Write(buf)
   147  		if h.doPageHash {
   148  			h.addPageHash(position, buf, 0)
   149  		}
   150  		position += uint32(n)
   151  		remaining -= n
   152  		h.lastPage = position
   153  	}
   154  	return nil
   155  }
   156  
   157  func (h *imageHasher) finish() ([]byte, []byte, error) {
   158  	sum := h.imageDigest.Sum(nil)
   159  	if h.doPageHash {
   160  		h.addPageHash(h.lastPage, nil, 0)
   161  	}
   162  	return sum, h.pageHashes, nil
   163  }
   164  
   165  func (h *imageHasher) addPageHash(offset uint32, blob []byte, removed int) {
   166  	var obytes [4]byte
   167  	binary.LittleEndian.PutUint32(obytes[:], offset)
   168  	h.pageHashes = append(h.pageHashes, obytes[:]...)
   169  	if len(blob) == 0 {
   170  		// last "page" has a null digest
   171  		h.pageHashes = append(h.pageHashes, make([]byte, h.hashFunc.Size())...)
   172  		return
   173  	}
   174  	d := h.hashFunc.New()
   175  	d.Write(blob)
   176  	needzero := len(h.zeroPage) - len(blob) - removed
   177  	d.Write(h.zeroPage[:needzero])
   178  	h.pageHashes = d.Sum(h.pageHashes)
   179  }
   180  
   181  func readDosHeader(r io.Reader, d io.Writer) (int64, error) {
   182  	dosheader, err := readAndHash(r, d, dosHeaderSize)
   183  	if err != nil {
   184  		return 0, err
   185  	} else if dosheader[0] != 'M' || dosheader[1] != 'Z' {
   186  		return 0, errors.New("not a PE file")
   187  	}
   188  	return int64(binary.LittleEndian.Uint32(dosheader[0x3c:])), nil
   189  }
   190  
   191  func readCoffHeader(r io.Reader, d io.Writer) (*pe.FileHeader, error) {
   192  	if magic, err := readAndHash(r, d, 4); err != nil {
   193  		return nil, err
   194  	} else if magic[0] != 'P' || magic[1] != 'E' || magic[2] != 0 || magic[3] != 0 {
   195  		return nil, errors.New("not a PE file")
   196  	}
   197  
   198  	buf, err := readAndHash(r, d, 20)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	hdr := new(pe.FileHeader)
   203  	if err := binaryReadBytes(buf, hdr); err != nil {
   204  		return nil, err
   205  	}
   206  	return hdr, nil
   207  }
   208  
   209  func readOptHeader(r io.Reader, d io.Writer, peStart int64, fh *pe.FileHeader) (*peHeaderValues, error) {
   210  	hvals := new(peHeaderValues)
   211  	hvals.peStart = peStart
   212  	buf := make([]byte, fh.SizeOfOptionalHeader)
   213  	if _, err := io.ReadFull(r, buf); err != nil {
   214  		return nil, err
   215  	}
   216  	// locate the bits that need to be omitted from hash
   217  	cksumStart := 64
   218  	cksumEnd := cksumStart + 4
   219  	var dd4Start int64
   220  	var dd pe.DataDirectory
   221  	optMagic := binary.LittleEndian.Uint16(buf[:2])
   222  	switch optMagic {
   223  	case 0x10b:
   224  		// PE32
   225  		var opt pe.OptionalHeader32
   226  		if err := binaryReadBytes(buf, &opt); err != nil {
   227  			return nil, err
   228  		}
   229  		if opt.NumberOfRvaAndSizes < 5 {
   230  			return nil, errors.New("PE header did not leave room for signature")
   231  		}
   232  		dd = opt.DataDirectory[4]
   233  		dd4Start = 128
   234  		hvals.sizeOfHdr = int64(opt.SizeOfHeaders)
   235  		hvals.sectionAlign = int(opt.SectionAlignment)
   236  	case 0x20b:
   237  		// PE32+
   238  		var opt pe.OptionalHeader64
   239  		if err := binaryReadBytes(buf, &opt); err != nil {
   240  			return nil, err
   241  		}
   242  		if opt.NumberOfRvaAndSizes < 5 {
   243  			return nil, errors.New("PE header did not leave room for signature")
   244  		}
   245  		dd = opt.DataDirectory[4]
   246  		dd4Start = 144
   247  		hvals.sizeOfHdr = int64(opt.SizeOfHeaders)
   248  		hvals.sectionAlign = int(opt.SectionAlignment)
   249  	default:
   250  		return nil, errors.New("unrecognized optional header magic")
   251  	}
   252  	dd4End := dd4Start + 8
   253  	hvals.certStart = int64(dd.VirtualAddress)
   254  	hvals.certSize = int64(dd.Size)
   255  	hvals.secTblStart = peStart + 24 + int64(fh.SizeOfOptionalHeader)
   256  	d.Write(buf[:cksumStart])
   257  	d.Write(buf[cksumEnd:dd4Start])
   258  	d.Write(buf[dd4End:])
   259  	hvals.posDDCert = peStart + 24 + dd4Start
   260  	return hvals, nil
   261  }
   262  
   263  func readSections(r io.Reader, d io.Writer, fh *pe.FileHeader, hvals *peHeaderValues) ([]pe.SectionHeader32, error) {
   264  	// read and hash section table
   265  	sections := make([]pe.SectionHeader32, fh.NumberOfSections)
   266  	size := int(fh.NumberOfSections) * 40
   267  	secTblEnd := hvals.secTblStart + int64(size)
   268  	if secTblEnd > hvals.sizeOfHdr {
   269  		return nil, errors.New("PE section overlaps section table")
   270  	}
   271  	if buf, err := readAndHash(r, d, size); err != nil {
   272  		return nil, err
   273  	} else if err := binaryReadBytes(buf, sections); err != nil {
   274  		return nil, err
   275  	}
   276  	// hash the padding after the section table
   277  	if _, err := io.CopyN(d, r, hvals.sizeOfHdr-secTblEnd); err != nil {
   278  		return nil, err
   279  	}
   280  	return sections, nil
   281  }
   282  
   283  func readTrailer(r io.Reader, d io.Writer, lastSection, certStart, certSize int64) (int64, error) {
   284  	if certSize == 0 {
   285  		n, err := io.Copy(d, r)
   286  		return lastSection + n, err
   287  	}
   288  	if certStart < lastSection {
   289  		return 0, errors.New("existing signature overlaps with PE sections")
   290  	}
   291  	if _, err := io.CopyN(d, r, certStart-lastSection); err != nil {
   292  		return 0, err
   293  	}
   294  	if _, err := io.CopyN(ioutil.Discard, r, certSize); err != nil {
   295  		return 0, err
   296  	}
   297  	if n, _ := io.Copy(ioutil.Discard, r); n > 0 {
   298  		return 0, errors.New("trailing garbage after existing certificate")
   299  	}
   300  	return certStart, nil
   301  }
   302  
   303  type peHeaderValues struct {
   304  	// start of PE header
   305  	peStart int64
   306  	// file offset to the data directory entry for the cert table, in the optional header
   307  	posDDCert int64
   308  	// file offset to the end of the optional header and the start of the section table
   309  	secTblStart int64
   310  	// size of all headers plus padding
   311  	sizeOfHdr int64
   312  	// section alignment in memory
   313  	sectionAlign int
   314  	// file offset and size of the certificate table
   315  	certStart, certSize int64
   316  }
   317  

View as plain text