001/**
002 * Copyright 2012 Emmanuel Bourg
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package net.jsign.pe;
018
019import java.io.Closeable;
020import java.io.File;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.PrintWriter;
024import java.nio.ByteBuffer;
025import java.nio.ByteOrder;
026import java.nio.channels.SeekableByteChannel;
027import java.nio.file.Files;
028import java.nio.file.StandardOpenOption;
029import java.security.MessageDigest;
030import java.util.ArrayList;
031import java.util.Date;
032import java.util.List;
033
034import org.bouncycastle.asn1.ASN1Encodable;
035import org.bouncycastle.asn1.ASN1Object;
036import org.bouncycastle.asn1.DERNull;
037import org.bouncycastle.asn1.cms.Attribute;
038import org.bouncycastle.asn1.cms.AttributeTable;
039import org.bouncycastle.asn1.cms.ContentInfo;
040import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
041import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
042import org.bouncycastle.asn1.x509.DigestInfo;
043import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
044import org.bouncycastle.cert.X509CertificateHolder;
045import org.bouncycastle.cms.CMSProcessable;
046import org.bouncycastle.cms.CMSSignedData;
047import org.bouncycastle.cms.SignerInformation;
048
049import net.jsign.DigestAlgorithm;
050import net.jsign.Signable;
051import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
052import net.jsign.asn1.authenticode.SpcAttributeTypeAndOptionalValue;
053import net.jsign.asn1.authenticode.SpcIndirectDataContent;
054import net.jsign.asn1.authenticode.SpcPeImageData;
055
056import static net.jsign.ChannelUtils.*;
057
058/**
059 * Portable Executable File.
060 * 
061 * This class is thread safe.
062 * 
063 * @see <a href="https://docs.microsoft.com/en-us/windows/win32/debug/pe-format">Microsoft PE and COFF Specification </a>
064 * 
065 * @author Emmanuel Bourg
066 * @since 1.0
067 */
068public class PEFile implements Signable, Closeable {
069
070    /** The position of the PE header in the file */
071    private final long peHeaderOffset;
072
073    private File file;
074    final SeekableByteChannel channel;
075
076    /** Reusable buffer for reading bytes, words, dwords and qwords from the file */
077    private final ByteBuffer valueBuffer = ByteBuffer.allocate(8);
078    {
079        valueBuffer.order(ByteOrder.LITTLE_ENDIAN);
080    }
081
082    /**
083     * Tells if the specified file is a Portable Executable file.
084     *
085     * @param file the file to check
086     * @return <code>true</code> if the file is a Portable Executable, <code>false</code> otherwise
087     * @throws IOException if an I/O error occurs
088     * @since 3.0
089     */
090    public static boolean isPEFile(File file) throws IOException {
091        if (!file.exists() || !file.isFile()) {
092            return false;
093        }
094        
095        try {
096            PEFile peFile = new PEFile(file);
097            peFile.close();
098            return true;
099        } catch (IOException e) {
100            if (e.getMessage().contains("DOS header signature not found") || e.getMessage().contains("PE signature not found")) {
101                return false;
102            } else {
103                throw e;
104            }
105        }
106    }
107
108    /**
109     * Create a PEFile from the specified file.
110     *
111     * @param file the file to open
112     * @throws IOException if an I/O error occurs
113     */
114    public PEFile(File file) throws IOException {
115        this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE));
116        this.file = file;
117    }
118
119    /**
120     * Create a PEFile from the specified channel.
121     *
122     * @param channel the channel to read the file from
123     * @throws IOException if an I/O error occurs
124     * @since 2.0
125     */
126    public PEFile(SeekableByteChannel channel) throws IOException {
127        this.channel = channel;
128        
129        try {
130            // DOS Header
131            read(0, 0, 2);
132            if (valueBuffer.get() != 'M' || valueBuffer.get() != 'Z') {
133                throw new IOException("DOS header signature not found");
134            }
135
136            // PE Header
137            read(0x3C, 0, 4);
138            peHeaderOffset = valueBuffer.getInt() & 0xFFFFFFFFL;
139            read(peHeaderOffset, 0, 4);
140            if (valueBuffer.get() != 'P' || valueBuffer.get() != 'E' || valueBuffer.get() != 0 || valueBuffer.get() != 0) {
141                throw new IOException("PE signature not found as expected at offset 0x" + Long.toHexString(peHeaderOffset));
142            }
143
144        } catch (IOException e) {
145            channel.close();
146            throw e;
147        }
148    }
149
150    public void save() {
151    }
152
153    /**
154     * Closes the file
155     *
156     * @throws IOException if an I/O error occurs
157     */
158    public synchronized void close() throws IOException {
159        channel.close();
160    }
161
162    synchronized int read(byte[] buffer, long base, int offset) {
163        try {
164            channel.position(base + offset);
165            return channel.read(ByteBuffer.wrap(buffer));
166        } catch (IOException e) {
167            throw new RuntimeException(e);
168        }
169    }
170
171    private void read(long base, int offset, int length) {
172        try {
173            valueBuffer.limit(length);
174            valueBuffer.clear();
175            channel.position(base + offset);
176            channel.read(valueBuffer);
177            valueBuffer.rewind();
178        } catch (IOException e) {
179            throw new RuntimeException(e);
180        }
181    }
182
183    synchronized int read(long base, int offset) {
184        read(base, offset, 1);
185        return valueBuffer.get();
186    }
187
188    synchronized int readWord(long base, int offset) {
189        read(base, offset, 2);
190        return valueBuffer.getShort() & 0xFFFF;
191    }
192
193    synchronized long readDWord(long base, int offset) {
194        read(base, offset, 4);
195        return valueBuffer.getInt() & 0xFFFFFFFFL;
196    }
197
198    synchronized long readQWord(long base, int offset) {
199        read(base, offset, 8);
200        return valueBuffer.getLong();
201    }
202
203    synchronized void write(long base, byte[] data) {
204        try {
205            channel.position(base);
206            channel.write(ByteBuffer.wrap(data));
207        } catch (IOException e) {
208            throw new RuntimeException(e);
209        }
210    }
211
212    public MachineType getMachineType() {
213        return MachineType.valueOf(readWord(peHeaderOffset, 4));
214    }
215
216    /**
217     * The number of sections. This indicates the size of the section table,
218     * which immediately follows the headers.
219     * 
220     * @return the number of sections
221     */
222    public int getNumberOfSections() {
223        return readWord(peHeaderOffset, 6);
224    }
225
226    /**
227     * The low 32 bits of the number of seconds since 00:00 January 1, 1970
228     * (a C runtime time_t value), that indicates when the file was created.
229     * 
230     * @return the PE file creation date
231     */
232    public Date getTimeDateStamp() {
233        return new Date(1000 * readDWord(peHeaderOffset, 8));
234    }
235
236    /**
237     * The file offset of the COFF symbol table, or zero if no COFF symbol table
238     * is present. This value should be zero for an image because COFF debugging
239     * information is deprecated.
240     * 
241     * @return the offset of the COFF symbol table
242     */
243    public long getPointerToSymbolTable() {
244        return readDWord(peHeaderOffset, 12);
245    }
246
247    /**
248     * The number of entries in the symbol table. This data can be used to
249     * locate the string table, which immediately follows the symbol table.
250     * This value should be zero for an image because COFF debugging
251     * information is deprecated.
252     * 
253     * @return the number of entries in the symbol table
254     */
255    public long getNumberOfSymbols() {
256        return readDWord(peHeaderOffset, 16);
257    }
258
259    /**
260     * The size of the optional header, which is required for executable files
261     * but not for object files. This value should be zero for an object file.
262     * 
263     * @return the size of the optional header
264     */
265    public int getSizeOfOptionalHeader() {
266        return readWord(peHeaderOffset, 20);
267    }
268
269    /**
270     * The flags that indicate the attributes of the file. 
271     * 
272     * @return the characteristics flag
273     */
274    public int getCharacteristics() {
275        return readWord(peHeaderOffset, 22);
276    }
277    
278    public PEFormat getFormat() {
279        return PEFormat.valueOf(readWord(peHeaderOffset, 24));
280    }
281
282    /**
283     * The linker major version number.
284     * 
285     * @return the linker major version number
286     */
287    public int getMajorLinkerVersion() {
288        return read(peHeaderOffset, 26);
289    }
290
291    /**
292     * The linker minor version number.
293     * 
294     * @return the linker minor version number
295     */
296    public int getMinorLinkerVersion() {
297        return read(peHeaderOffset, 27);
298    }
299
300    /**
301     * The size of the code (text) section, or the sum of all code sections
302     * if there are multiple sections.
303     * 
304     * @return the size of the code (text) section
305     */
306    public long getSizeOfCode() {
307        return readDWord(peHeaderOffset, 28);
308    }
309
310    /**
311     * The size of the initialized data section, or the sum of all such
312     * sections if there are multiple data sections.
313     * 
314     * @return the size of the initialized data section
315     */
316    public long getSizeOfInitializedData() {
317        return readDWord(peHeaderOffset, 32);
318    }
319
320    /**
321     * The size of the uninitialized data section (BSS), or the sum of all such
322     * sections if there are multiple BSS sections.
323     * 
324     * @return the size of the uninitialized data section (BSS)
325     */
326    public long getSizeOfUninitializedData() {
327        return readDWord(peHeaderOffset, 36);
328    }
329
330    /**
331     * The address of the entry point relative to the image base when the
332     * executable file is loaded into memory. For program images, this is the
333     * starting address. For device drivers, this is the address of the
334     * initialization function. An entry point is optional for DLLs. When no
335     * entry point is present, this field must be zero.
336     * 
337     * @return the address of the entry point
338     */
339    public long getAddressOfEntryPoint() {
340        return readDWord(peHeaderOffset, 40);
341    }
342
343    /**
344     * The address that is relative to the image base of the beginning-of-code 
345     * section when it is loaded into memory.
346     * 
347     * @return the code base address
348     */
349    public long getBaseOfCode() {
350        return readDWord(peHeaderOffset, 44);
351    }
352
353    /**
354     * The address that is relative to the image base of the beginning-of-data 
355     * section when it is loaded into memory (PE32 only).
356     * 
357     * @return the data base address
358     */
359    public long getBaseOfData() {
360        if (PEFormat.PE32.equals(getFormat())) {
361            return readDWord(peHeaderOffset, 48);
362        } else {
363            return 0;
364        }
365    }
366
367    /**
368     * The preferred address of the first byte of image when loaded into memory;
369     * must be a multiple of 64 K. The default for DLLs is 0x10000000. The default
370     * for Windows CE EXEs is 0x00010000. The default for Windows NT, Windows 2000,
371     * Windows XP, Windows 95, Windows 98, and Windows Me is 0x00400000.
372     * 
373     * @return the image base address
374     */
375    public long getImageBase() {
376        if (PEFormat.PE32.equals(getFormat())) {
377            return readDWord(peHeaderOffset, 52);
378        } else {
379            return readQWord(peHeaderOffset, 48);
380        }
381    }
382
383    /**
384     * The alignment (in bytes) of sections when they are loaded into memory.
385     * It must be greater than or equal to FileAlignment. The default is the
386     * page size for the architecture.
387     * 
388     * @return the size of the sections memory alignment (in bytes)
389     */
390    public long getSectionAlignment() {
391        return readDWord(peHeaderOffset, 56);
392    }
393
394    /**
395     * The alignment factor (in bytes) that is used to align the raw data of
396     * sections in the image file. The value should be a power of 2 between
397     * 512 and 64 K, inclusive. The default is 512. If the SectionAlignment
398     * is less than the architecture?s page size, then FileAlignment must
399     * match SectionAlignment.
400     * 
401     * @return the alignment factor (in bytes)
402     */
403    public long getFileAlignment() {
404        return readDWord(peHeaderOffset, 60);
405    }
406
407    /**
408     * The major version number of the required operating system.
409     * 
410     * @return the major version number of the required operating system
411     */
412    public int getMajorOperatingSystemVersion() {
413        return readWord(peHeaderOffset, 64);
414    }
415
416    /**
417     * The minor version number of the required operating system.
418     * 
419     * @return the minor version number of the required operating system
420     */
421    public int getMinorOperatingSystemVersion() {
422        return readWord(peHeaderOffset, 66);
423    }
424
425    /**
426     * The major version number of the image.
427     * 
428     * @return the major version number of the image
429     */
430    public int getMajorImageVersion() {
431        return readWord(peHeaderOffset, 68);
432    }
433
434    /**
435     * The minor version number of the image.
436     * 
437     * @return the minor version number of the image
438     */
439    public int getMinorImageVersion() {
440        return readWord(peHeaderOffset, 70);
441    }
442
443    /**
444     * The major version number of the subsystem.
445     * 
446     * @return the major version number of the subsystem
447     */
448    public int getMajorSubsystemVersion() {
449        return readWord(peHeaderOffset, 72);
450    }
451
452    /**
453     * The minor version number of the subsystem.
454     * 
455     * @return the minor version number of the subsystem
456     */
457    public int getMinorSubsystemVersion() {
458        return readWord(peHeaderOffset, 74);
459    }
460
461    /**
462     * Reserved, must be zero.
463     * 
464     * @return zero
465     */
466    public long getWin32VersionValue() {
467        return readDWord(peHeaderOffset, 76);
468    }
469
470    /**
471     * The size (in bytes) of the image, including all headers, as the image
472     * is loaded in memory. It must be a multiple of SectionAlignment.
473     * 
474     * @return the size of the image (in bytes)
475     */
476    public long getSizeOfImage() {
477        return readDWord(peHeaderOffset, 80);
478    }
479
480    /**
481     * The combined size of an MS DOS stub, PE header, and section headers
482     * rounded up to a multiple of FileAlignment.
483     * 
484     * @return the combined size of the headers
485     */
486    public long getSizeOfHeaders() {
487        return readDWord(peHeaderOffset, 84);
488    }
489
490    /**
491     * The image file checksum.
492     * 
493     * @return the checksum of the image
494     */
495    public long getCheckSum() {
496        return readDWord(peHeaderOffset, 88);
497    }
498
499    /**
500     * Compute the checksum of the image file. The algorithm for computing
501     * the checksum is incorporated into IMAGHELP.DLL.
502     * 
503     * @return the checksum of the image
504     */
505    public synchronized long computeChecksum() {
506        PEImageChecksum checksum = new PEImageChecksum(peHeaderOffset + 88);
507        
508        ByteBuffer b = ByteBuffer.allocate(64 * 1024);
509        
510        try {
511            channel.position(0);
512            
513            int len;
514            while ((len = channel.read(b)) > 0) {
515                b.flip();
516                checksum.update(b.array(), 0, len);
517            }
518        } catch (IOException e) {
519            throw new RuntimeException(e);
520        }
521        
522        return checksum.getValue();
523    }
524
525    public synchronized void updateChecksum() {
526        ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
527        buffer.putInt((int) computeChecksum());
528        buffer.flip();
529
530        try {
531            channel.position(peHeaderOffset + 88);
532            channel.write(buffer);
533        } catch (IOException e) {
534            throw new RuntimeException(e);
535        }
536    }
537
538    /**
539     * The subsystem that is required to run this image.
540     * 
541     * @return the required subsystem
542     */
543    public Subsystem getSubsystem() {
544        return Subsystem.valueOf(readWord(peHeaderOffset, 92));
545    }
546
547    public int getDllCharacteristics() {
548        return readWord(peHeaderOffset, 94);
549    }
550
551    /**
552     * The size of the stack to reserve. Only SizeOfStackCommit is committed;
553     * the rest is made available one page at a time until the reserve size is reached.
554     * 
555     * @return the size of the stack to reserve
556     */
557    public long getSizeOfStackReserve() {
558        if (PEFormat.PE32.equals(getFormat())) {
559            return readDWord(peHeaderOffset, 96);
560        } else {
561            return readQWord(peHeaderOffset, 96);
562        }
563    }
564
565    /**
566     * The size of the stack to commit.
567     * 
568     * @return the size of the stack to commit
569     */
570    public long getSizeOfStackCommit() {
571        if (PEFormat.PE32.equals(getFormat())) {
572            return readDWord(peHeaderOffset, 100);
573        } else {
574            return readQWord(peHeaderOffset, 104);
575        }
576    }
577
578    /**
579     * The size of the local heap space to reserve. Only SizeOfHeapCommit is
580     * committed; the rest is made available one page at a time until the
581     * reserve size is reached.
582     * 
583     * @return the size of the local heap space to reserve
584     */
585    public long getSizeOfHeapReserve() {
586        if (PEFormat.PE32.equals(getFormat())) {
587            return readDWord(peHeaderOffset, 104);
588        } else {
589            return readQWord(peHeaderOffset, 112);
590        }
591    }
592
593    /**
594     * The size of the local heap space to commit.
595     * 
596     * @return the size of the local heap space to commit
597     */
598    public long getSizeOfHeapCommit() {
599        if (PEFormat.PE32.equals(getFormat())) {
600            return readDWord(peHeaderOffset, 108);
601        } else {
602            return readQWord(peHeaderOffset, 120);
603        }
604    }
605
606    /**
607     * Reserved, must be zero.
608     * 
609     * @return zero
610     */
611    public long getLoaderFlags() {
612        return readDWord(peHeaderOffset, PEFormat.PE32.equals(getFormat()) ? 112 : 128);
613    }
614
615    /**
616     * The number of data-directory entries in the remainder of the optional
617     * header. Each describes a location and size.
618     * 
619     * @return the number of data-directory entries
620     */
621    public int getNumberOfRvaAndSizes() {
622        return (int) readDWord(peHeaderOffset, PEFormat.PE32.equals(getFormat()) ? 116 : 132);
623    }
624
625    int getDataDirectoryOffset() {
626        return (int) peHeaderOffset + (PEFormat.PE32.equals(getFormat()) ? 120 : 136);
627    }
628
629    /**
630     * Returns the data directory of the specified type.
631     * 
632     * @param type the type of data directory
633     * @return the data directory of the specified type
634     */
635    public DataDirectory getDataDirectory(DataDirectoryType type) {
636        if (type.ordinal() >= getNumberOfRvaAndSizes()) {
637            return null;
638        } else {
639            return new DataDirectory(this, type.ordinal());
640        }
641    }
642
643    /**
644     * Writes the data directory of the specified type. The data is either appended
645     * at the end of the file or written over the previous data of the same type if
646     * there is enough space.
647     * 
648     * @param type the type of the data directory
649     * @param data the content of the data directory
650     * @throws IOException if an I/O error occurs
651     */
652    public synchronized void writeDataDirectory(DataDirectoryType type, byte[] data) throws IOException {
653        DataDirectory directory = getDataDirectory(type);
654        
655        if (!directory.exists()) {
656            // append the data directory at the end of the file
657            long offset = channel.size();
658            
659            channel.position(offset);
660            channel.write(ByteBuffer.wrap(data));
661            
662            // update the entry in the data directory table
663            directory.write(offset, data.length);
664            
665        } else {
666            if (data.length == directory.getSize()) {
667                // same size as before, just overwrite
668                channel.position(directory.getVirtualAddress());
669                channel.write(ByteBuffer.wrap(data));
670
671            } else if (data.length < directory.getSize() && type != DataDirectoryType.CERTIFICATE_TABLE) {
672                // the new data is smaller, erase and rewrite in-place
673                // this doesn't work with the certificate table since it changes the file digest
674                directory.erase();
675                channel.position(directory.getVirtualAddress());
676                channel.write(ByteBuffer.wrap(data));
677                
678                // update the size in the data directory table
679                directory.write(directory.getVirtualAddress(), data.length);
680
681            } else if (directory.isTrailing()) {
682                // the data is at the end of the file, overwrite it
683                channel.position(directory.getVirtualAddress());
684                channel.write(ByteBuffer.wrap(data));
685                channel.truncate(directory.getVirtualAddress() + data.length); // trim the file if the data shrunk
686                
687                // update the size in the data directory table
688                directory.write(directory.getVirtualAddress(), data.length);
689
690            } else {
691                if (type == DataDirectoryType.CERTIFICATE_TABLE) {
692                    throw new IOException("The certificate table isn't at the end of the file and can't be moved without invalidating the signature");
693                }
694                
695                // the new data is larger, erase and relocate it at the end
696                directory.erase();
697                
698                long offset = channel.size();
699                
700                channel.position(offset);
701                channel.write(ByteBuffer.wrap(data));
702                
703                // update the entry in the data directory table
704                directory.write(offset, data.length);
705            }
706        }
707        
708        updateChecksum();
709    }
710
711    @Override
712    public synchronized List<CMSSignedData> getSignatures() {
713        List<CMSSignedData> signatures = new ArrayList<>();
714        
715        for (CertificateTableEntry entry : getCertificateTable()) {
716            try {
717                CMSSignedData signedData = entry.getSignature();
718                signatures.add(signedData);
719                
720                // look for nested signatures
721                SignerInformation signerInformation = signedData.getSignerInfos().getSigners().iterator().next();
722                AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
723                if (unsignedAttributes != null) {
724                    Attribute nestedSignatures = unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID);
725                    if (nestedSignatures != null) {
726                        for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) {
727                            signatures.add(new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(nestedSignature)));
728                        }
729                    }
730                }
731            } catch (UnsupportedOperationException e) {
732                // unsupported type, just skip
733            } catch (Exception e) {
734                e.printStackTrace();
735            }
736        }
737        
738        return signatures;
739    }
740
741    @Override
742    public void setSignature(CMSSignedData signature) throws IOException {
743        // pad the file before adding the certificate table
744        DataDirectory certificateTable = getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
745        if (certificateTable == null || !certificateTable.exists()) {
746            pad(8);
747        }
748
749        CertificateTableEntry entry = new CertificateTableEntry(signature);
750        writeDataDirectory(DataDirectoryType.CERTIFICATE_TABLE, entry.toBytes());
751    }
752
753    private synchronized List<CertificateTableEntry> getCertificateTable() {
754        List<CertificateTableEntry> entries = new ArrayList<>();
755        DataDirectory certificateTable = getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
756        
757        if (certificateTable != null && certificateTable.exists()) {
758            long position = certificateTable.getVirtualAddress();
759            
760            try {
761                entries.add(new CertificateTableEntry(this, position));
762                
763                // todo read the remaining entries (but Authenticode use only one, extra signatures are appended as a SPC_NESTED_SIGNATURE unauthenticated attribute)
764            } catch (Exception e) {
765                e.printStackTrace();
766            }
767        }
768        
769        return entries;
770    }
771
772    public synchronized List<Section> getSections() {
773        List<Section> sections = new ArrayList<>();
774        int sectionTableOffset = getDataDirectoryOffset() + 8 * getNumberOfRvaAndSizes();
775        
776        for (int i = 0; i < getNumberOfSections(); i++) {
777            sections.add(new Section(this, sectionTableOffset + 40 * i));
778        }
779        
780        return sections;
781    }
782
783    /**
784     * Print detailed informations about the PE file.
785     * 
786     * @param out the output stream where the info is printed
787     */
788    public void printInfo(OutputStream out) {
789        printInfo(new PrintWriter(out, true));
790    }
791
792    /**
793     * Print detailed informations about the PE file.
794     * 
795     * @param out the output writer where the info is printed
796     */
797    public void printInfo(PrintWriter out) {
798        if (file != null) {
799            out.println("PE File");
800            out.println("  Name:          " + file.getName());
801            out.println("  Size:          " + file.length());
802            out.println("  Last Modified: " + new Date(file.lastModified()));
803            out.println();
804        }
805        
806        out.println("PE Header");
807        out.println("  Machine:                    " + getMachineType());
808        out.println("  Number of sections:         " + getNumberOfSections());
809        out.println("  Timestamp:                  " + getTimeDateStamp());
810        out.println("  Pointer to symbol table:    0x" + Long.toHexString(getPointerToSymbolTable()));
811        out.println("  Number of symbols:          " + getNumberOfSymbols());
812        out.println("  Size of optional header:    " + getSizeOfOptionalHeader());
813        out.println("  Characteristics:            0x" + Long.toBinaryString(getCharacteristics()));
814        out.println();
815        
816        out.println("Optional Header");
817        PEFormat format = getFormat();
818        out.println("  PE Format:                  0x" + Integer.toHexString(format.value) + " (" + format.label + ")");
819        out.println("  Linker version:             " + getMajorLinkerVersion() + "." + getMinorLinkerVersion());
820        out.println("  Size of code:               " + getSizeOfCode());
821        out.println("  Size of initialized data:   " + getSizeOfInitializedData());
822        out.println("  Size of uninitialized data: " + getSizeOfUninitializedData());
823        out.println("  Address of entry point:     0x" + Long.toHexString(getAddressOfEntryPoint()));
824        out.println("  Base of code:               0x" + Long.toHexString(getBaseOfCode()));
825        if (PEFormat.PE32.equals(getFormat())) {
826            out.println("  Base of data:               0x" + Long.toHexString(getBaseOfData()));
827        }
828        out.println("  Image base:                 0x" + Long.toHexString(getImageBase()));
829        out.println("  Section alignment:          " + getSectionAlignment());
830        out.println("  File alignment:             " + getFileAlignment());
831        out.println("  Operating system version:   " + getMajorOperatingSystemVersion() + "." + getMinorOperatingSystemVersion());
832        out.println("  Image version:              " + getMajorImageVersion() + "." + getMinorImageVersion());
833        out.println("  Subsystem version:          " + getMajorSubsystemVersion() + "." + getMinorSubsystemVersion());
834        out.println("  Size of image:              " + getSizeOfImage());
835        out.println("  Size of headers:            " + getSizeOfHeaders());
836        out.println("  Checksum:                   0x" + Long.toHexString(getCheckSum()));
837        out.println("  Checksum (computed):        0x" + Long.toHexString(computeChecksum()));
838        out.println("  Subsystem:                  " + getSubsystem());
839        out.println("  DLL characteristics:        0x" + Long.toBinaryString(getDllCharacteristics()));
840        out.println("  Size of stack reserve:      " + getSizeOfStackReserve());
841        out.println("  Size of stack commit:       " + getSizeOfStackCommit());
842        out.println("  Size of heap reserve:       " + getSizeOfHeapReserve());
843        out.println("  Size of heap commit:        " + getSizeOfHeapCommit());
844        out.println("  Number of RVA and sizes:    " + getNumberOfRvaAndSizes());
845        out.println();
846        
847        out.println("Data Directory");
848        for (DataDirectoryType type : DataDirectoryType.values()) {
849            DataDirectory entry = getDataDirectory(type);
850            if (entry != null && entry.exists()) {
851                out.printf("  %-30s 0x%08x %8d bytes%n", type, entry.getVirtualAddress(), entry.getSize());
852            }
853        }
854        out.println();
855        
856        out.println("Sections");
857        out.println("      Name     Virtual Size  Virtual Address  Raw Data Size  Raw Data Ptr  Characteristics");
858        List<Section> sections = getSections();
859        for (int i = 0; i < sections.size(); i++) {
860            Section section = sections.get(i);
861            out.printf("  #%d  %-8s     %8d       0x%08x       %8d    0x%08x  %s%n", i + 1, section.getName(), section.getVirtualSize(), section.getVirtualAddress(), section.getSizeOfRawData(), section.getPointerToRawData(), section.getCharacteristics());
862        }
863        out.println();
864        
865        List<CMSSignedData> signatures = getSignatures();
866        if (!signatures.isEmpty()) {
867            out.println("Signatures");
868            for (CMSSignedData signedData : signatures) {
869                SignerInformation signerInformation = signedData.getSignerInfos().getSigners().iterator().next();
870                X509CertificateHolder certificate = (X509CertificateHolder) signedData.getCertificates().getMatches(signerInformation.getSID()).iterator().next();
871                
872                String commonName = certificate.getSubject().getRDNs(X509ObjectIdentifiers.commonName)[0].getFirst().getValue().toString();
873                
874                AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
875                boolean timestamped = unsignedAttributes != null &&
876                           (unsignedAttributes.get(PKCSObjectIdentifiers.pkcs_9_at_counterSignature) != null
877                         || unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_RFC3161_OBJID)  != null);
878                DigestAlgorithm algorithm = DigestAlgorithm.of(signerInformation.getDigestAlgorithmID().getAlgorithm());
879                out.println("  " + commonName + "  " + (algorithm != null ? "[" + algorithm.id + "]  " : "") + (timestamped ? "(timestamped)" : ""));
880            }
881        }
882    }
883
884    /**
885     * Compute the digest of the file. The checksum field, the certificate
886     * directory table entry and the certificate table are excluded from
887     * the digest.
888     * 
889     * @param digest the message digest to update
890     * @return the digest of the file
891     * @throws IOException if an I/O error occurs
892     */
893    @Override
894    public synchronized byte[] computeDigest(MessageDigest digest) throws IOException {
895        long checksumLocation = peHeaderOffset + 88;
896        
897        DataDirectory certificateTable = getDataDirectory(DataDirectoryType.CERTIFICATE_TABLE);
898        
899        // digest from the beginning to the checksum field (excluded)
900        updateDigest(channel, digest, 0, checksumLocation);
901        
902        // skip the checksum field
903        long position = checksumLocation + 4;
904        
905        // digest from the end of the checksum field to the beginning of the certificate table entry
906        int certificateTableOffset = getDataDirectoryOffset() + 8 * DataDirectoryType.CERTIFICATE_TABLE.ordinal();
907        updateDigest(channel, digest, position, certificateTableOffset);
908        
909        // skip the certificate entry
910        position = certificateTableOffset + 8;
911        
912        // todo digest the sections in ascending address order
913        
914        // digest from the end of the certificate table entry to the beginning of the certificate table
915        if (certificateTable != null && certificateTable.exists()) {
916            updateDigest(channel, digest, position, certificateTable.getVirtualAddress());
917            position = certificateTable.getVirtualAddress() + certificateTable.getSize();
918        }
919        
920        // digest from the end of the certificate table to the end of the file
921        updateDigest(channel, digest, position, channel.size());
922        
923        if (certificateTable == null || !certificateTable.exists()) {
924            // if the file has never been signed before, update the digest as if the file was padded on a 8 byte boundary
925            int paddingLength = (int) (8 - channel.size() % 8) % 8;
926            digest.update(new byte[paddingLength]);
927        }
928
929        return digest.digest();
930    }
931
932    /**
933     * Compute the checksum of the file using the specified digest algorithm.
934     * 
935     * @param algorithm the digest algorithm, typically SHA1
936     * @return the checksum of the file
937     * @throws IOException if an I/O error occurs
938     */
939    public byte[] computeDigest(DigestAlgorithm algorithm) throws IOException {
940        return computeDigest(algorithm.getMessageDigest());
941    }
942
943    @Override
944    public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) throws IOException {
945        AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(digestAlgorithm.oid, DERNull.INSTANCE);
946        DigestInfo digestInfo = new DigestInfo(algorithmIdentifier, computeDigest(digestAlgorithm));
947        SpcAttributeTypeAndOptionalValue data = new SpcAttributeTypeAndOptionalValue(AuthenticodeObjectIdentifiers.SPC_PE_IMAGE_DATA_OBJID, new SpcPeImageData());
948
949        return new SpcIndirectDataContent(data, digestInfo);
950    }
951
952    /**
953     * Increase the size of the file up to a size that is a multiple of the specified value.
954     * 
955     * @param multiple the size of the byte alignment
956     * @throws IOException if an I/O error occurs
957     */
958    public synchronized void pad(int multiple) throws IOException {
959        long padding = (multiple - channel.size() % multiple) % multiple;
960        channel.position(channel.size());
961        channel.write(ByteBuffer.allocate((int) padding));
962    }
963}