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