78 lines
2.2 KiB
Go
78 lines
2.2 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"debug/pe"
|
|
"encoding/binary"
|
|
"os"
|
|
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
)
|
|
|
|
const (
|
|
peSecurityDirectoryIndex = 4
|
|
nsisFirstHeaderSize = 28
|
|
nsisSignature = 0xDEADBEEF
|
|
nsisIdentity = "NullsoftInst"
|
|
nsisMaximumAlignmentTail = 8
|
|
)
|
|
|
|
func validateNSISExecutable(path string) error {
|
|
executable, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer executable.Close()
|
|
information, err := executable.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
peFile, err := pe.NewFile(executable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer peFile.Close()
|
|
var overlayOffset uint64
|
|
for _, section := range peFile.Sections {
|
|
sectionEnd := uint64(section.Offset) + uint64(section.Size)
|
|
if sectionEnd > overlayOffset {
|
|
overlayOffset = sectionEnd
|
|
}
|
|
}
|
|
contentEnd := uint64(information.Size())
|
|
var securityOffset uint64
|
|
switch optionalHeader := peFile.OptionalHeader.(type) {
|
|
case *pe.OptionalHeader32:
|
|
securityOffset = uint64(optionalHeader.DataDirectory[peSecurityDirectoryIndex].VirtualAddress)
|
|
case *pe.OptionalHeader64:
|
|
securityOffset = uint64(optionalHeader.DataDirectory[peSecurityDirectoryIndex].VirtualAddress)
|
|
}
|
|
if securityOffset != 0 {
|
|
if securityOffset > contentEnd {
|
|
return E.New("invalid Authenticode directory offset")
|
|
}
|
|
contentEnd = securityOffset
|
|
}
|
|
if overlayOffset+nsisFirstHeaderSize > contentEnd {
|
|
return E.New("missing NSIS first header")
|
|
}
|
|
firstHeader := make([]byte, nsisFirstHeaderSize)
|
|
_, err = executable.ReadAt(firstHeader, int64(overlayOffset))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if binary.LittleEndian.Uint32(firstHeader[4:8]) != nsisSignature || string(firstHeader[8:20]) != nsisIdentity {
|
|
return E.New("invalid NSIS first header signature")
|
|
}
|
|
headerSize := uint64(binary.LittleEndian.Uint32(firstHeader[20:24]))
|
|
archiveSize := uint64(binary.LittleEndian.Uint32(firstHeader[24:28]))
|
|
if headerSize < nsisFirstHeaderSize || archiveSize < headerSize || overlayOffset+archiveSize > contentEnd {
|
|
return E.New("invalid NSIS archive size")
|
|
}
|
|
if contentEnd-(overlayOffset+archiveSize) > nsisMaximumAlignmentTail {
|
|
return E.New("unexpected data after NSIS archive")
|
|
}
|
|
return nil
|
|
}
|