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