1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
31
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
44
45
46 func DigestPE(r io.Reader, hash crypto.Hash, doPageHash bool) (*PEDigest, error) {
47
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
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
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)
111 h.pageBuf = make([]byte, hvals.sectionAlign)
112
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
120
121
122
123
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
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
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
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
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
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
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
305 peStart int64
306
307 posDDCert int64
308
309 secTblStart int64
310
311 sizeOfHdr int64
312
313 sectionAlign int
314
315 certStart, certSize int64
316 }
317
View as plain text