227 lines
8.2 KiB
Go
227 lines
8.2 KiB
Go
package winpwn
|
|
|
|
import (
|
|
"unsafe"
|
|
)
|
|
|
|
// DllCharacteristics bits (winnt.h IMAGE_DLLCHARACTERISTICS_*).
|
|
const (
|
|
dllCharHighEntropyVA = 0x0020
|
|
dllCharDynamicBase = 0x0040 // ASLR
|
|
dllCharForceIntegrity = 0x0080
|
|
dllCharNXCompat = 0x0100 // DEP
|
|
dllCharNoIsolation = 0x0200
|
|
dllCharNoSEH = 0x0400
|
|
dllCharAppContainer = 0x1000
|
|
dllCharGuardCF = 0x4000 // CFG
|
|
)
|
|
|
|
// Data directory indices (winnt.h IMAGE_DIRECTORY_ENTRY_*).
|
|
const (
|
|
dirEntrySecurity = 4 // Authenticode; VirtualAddress here is a *file offset*, not an RVA.
|
|
dirEntryLoadConfig = 10
|
|
dirEntryComDescriptor = 14 // .NET CLR header
|
|
)
|
|
|
|
// IMAGE_GUARD_CF_INSTRUMENTED, from the GuardFlags field of the Load Config
|
|
// Directory: set when the binary actually has CFG checks emitted, as
|
|
// opposed to just the (necessary but not sufficient) DllCharacteristics bit.
|
|
const imageGuardCFInstrumented = 0x00000100
|
|
|
|
// CheckSecResult mirrors pwntools'/checksec's binary protection summary,
|
|
// adapted to the mitigations that actually exist on PE/Windows.
|
|
type CheckSecResult struct {
|
|
Is64Bit bool
|
|
|
|
ASLR bool // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
|
|
HighEntropyVA bool // IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA (64-bit ASLR range)
|
|
DEP bool // IMAGE_DLLCHARACTERISTICS_NX_COMPAT
|
|
CFG bool // Control Flow Guard
|
|
ForceIntegrity bool
|
|
IsolationAware bool
|
|
AppContainer bool
|
|
DotNET bool // has a CLR/COM descriptor header, i.e. is a managed binary
|
|
|
|
// SEH/SafeSEH only mean anything for 32-bit PE32 images: x64 uses
|
|
// table-based structured exception handling and isn't subject to the
|
|
// classic SEH-chain-overwrite technique at all.
|
|
SEHApplicable bool
|
|
HasSEH bool // false if compiled with the /SAFESEH-equivalent IMAGE_DLLCHARACTERISTICS_NO_SEH
|
|
SafeSEH bool // SEHandlerTable present in Load Config
|
|
|
|
GSHeuristic bool // SecurityCookie present in Load Config (best-effort, see Checksec doc comment)
|
|
|
|
AuthenticodeSigned bool // IMAGE_DIRECTORY_ENTRY_SECURITY present (presence only, not cryptographically verified)
|
|
}
|
|
|
|
// Checksec inspects compile-time/link-time exploit mitigations, the Go
|
|
// analogue of pwntools'/winchecksec's binary checksec report.
|
|
//
|
|
// GSHeuristic is exactly that: a heuristic. Unlike ASLR/DEP/CFG which are
|
|
// global, unambiguous flags, /GS stack-cookie insertion is decided by the
|
|
// compiler per function. The presence of a non-zero SecurityCookie slot in
|
|
// the Load Config Directory only tells you the binary *could* use stack
|
|
// cookies, not that the specific function you're exploiting does — verify
|
|
// against the actual disassembly before relying on it.
|
|
func (p *PEFile) Checksec() (*CheckSecResult, error) {
|
|
h, err := p.header()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r := &CheckSecResult{
|
|
Is64Bit: h.is64,
|
|
ASLR: h.dllCharacteristics&dllCharDynamicBase != 0,
|
|
HighEntropyVA: h.dllCharacteristics&dllCharHighEntropyVA != 0,
|
|
DEP: h.dllCharacteristics&dllCharNXCompat != 0,
|
|
CFG: h.dllCharacteristics&dllCharGuardCF != 0,
|
|
ForceIntegrity: h.dllCharacteristics&dllCharForceIntegrity != 0,
|
|
IsolationAware: h.dllCharacteristics&dllCharNoIsolation == 0,
|
|
AppContainer: h.dllCharacteristics&dllCharAppContainer != 0,
|
|
SEHApplicable: !h.is64,
|
|
HasSEH: h.dllCharacteristics&dllCharNoSEH == 0,
|
|
}
|
|
|
|
r.DotNET = h.dataDirectory[dirEntryComDescriptor].VirtualAddress != 0
|
|
r.AuthenticodeSigned = h.dataDirectory[dirEntrySecurity].VirtualAddress != 0
|
|
|
|
lc, err := p.readLoadConfig(h)
|
|
if err == nil && lc != nil {
|
|
r.SafeSEH = !h.is64 && lc.sehHandlerTable != 0
|
|
r.GSHeuristic = lc.securityCookie != 0
|
|
// Corroborate the DllCharacteristics CFG bit with the GuardFlags
|
|
// instrumentation bit when we have a Load Config to check it against.
|
|
r.CFG = r.CFG && lc.guardFlags&imageGuardCFInstrumented != 0
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// loadConfig64/loadConfig32 mirror winnt.h's IMAGE_LOAD_CONFIG_DIRECTORY64/32:
|
|
// same field order in both, only pointer-sized members change width. Named
|
|
// (not anonymous) so unsafe.Offsetof can validate against each directory's
|
|
// self-reported Size — older toolchains emit a shorter struct with no
|
|
// SafeSEH/Guard CF fields at all, and reading past Size would misattribute
|
|
// zeroed padding as "feature present".
|
|
type loadConfig64 struct {
|
|
Size uint32
|
|
TimeDateStamp uint32
|
|
MajorVersion uint16
|
|
MinorVersion uint16
|
|
GlobalFlagsClear uint32
|
|
GlobalFlagsSet uint32
|
|
CriticalSectionDefaultTimeout uint32
|
|
DeCommitFreeBlockThreshold uint64
|
|
DeCommitTotalFreeThreshold uint64
|
|
LockPrefixTable uint64
|
|
MaximumAllocationSize uint64
|
|
VirtualMemoryThreshold uint64
|
|
ProcessAffinityMask uint64
|
|
ProcessHeapFlags uint32
|
|
CSDVersion uint16
|
|
DependentLoadFlags uint16
|
|
EditList uint64
|
|
SecurityCookie uint64
|
|
SEHandlerTable uint64
|
|
SEHandlerCount uint64
|
|
GuardCFCheckFunctionPointer uint64
|
|
GuardCFDispatchFunctionPointer uint64
|
|
GuardCFFunctionTable uint64
|
|
GuardCFFunctionCount uint64
|
|
GuardFlags uint32
|
|
}
|
|
|
|
type loadConfig32 struct {
|
|
Size uint32
|
|
TimeDateStamp uint32
|
|
MajorVersion uint16
|
|
MinorVersion uint16
|
|
GlobalFlagsClear uint32
|
|
GlobalFlagsSet uint32
|
|
CriticalSectionDefaultTimeout uint32
|
|
DeCommitFreeBlockThreshold uint32
|
|
DeCommitTotalFreeThreshold uint32
|
|
LockPrefixTable uint32
|
|
MaximumAllocationSize uint32
|
|
VirtualMemoryThreshold uint32
|
|
ProcessAffinityMask uint32
|
|
ProcessHeapFlags uint32
|
|
CSDVersion uint16
|
|
DependentLoadFlags uint16
|
|
EditList uint32
|
|
SecurityCookie uint32
|
|
SEHandlerTable uint32
|
|
SEHandlerCount uint32
|
|
GuardCFCheckFunctionPointer uint32
|
|
GuardCFDispatchFunctionPointer uint32
|
|
GuardCFFunctionTable uint32
|
|
GuardCFFunctionCount uint32
|
|
GuardFlags uint32
|
|
}
|
|
|
|
// Field offsets, computed by the compiler instead of hand-counted, used to
|
|
// validate against each Load Config's self-reported Size.
|
|
var (
|
|
offsetOf64SecurityCookie = uint32(unsafe.Offsetof(loadConfig64{}.SecurityCookie))
|
|
offsetOf64SEHandlerCount = uint32(unsafe.Offsetof(loadConfig64{}.SEHandlerCount))
|
|
offsetOf64GuardFlags = uint32(unsafe.Offsetof(loadConfig64{}.GuardFlags))
|
|
|
|
offsetOf32SecurityCookie = uint32(unsafe.Offsetof(loadConfig32{}.SecurityCookie))
|
|
offsetOf32SEHandlerCount = uint32(unsafe.Offsetof(loadConfig32{}.SEHandlerCount))
|
|
offsetOf32GuardFlags = uint32(unsafe.Offsetof(loadConfig32{}.GuardFlags))
|
|
)
|
|
|
|
// loadConfigInfo holds the handful of Load Config Directory fields checksec
|
|
// cares about, already normalized to a common width.
|
|
type loadConfigInfo struct {
|
|
securityCookie uint64
|
|
sehHandlerTable uint64
|
|
guardFlags uint32
|
|
}
|
|
|
|
// readLoadConfig parses the Load Config Directory, respecting its own Size
|
|
// field so we never trust fields beyond what the linker actually emitted.
|
|
func (p *PEFile) readLoadConfig(h peHeader) (*loadConfigInfo, error) {
|
|
dir := h.dataDirectory[dirEntryLoadConfig]
|
|
if dir.VirtualAddress == 0 {
|
|
return nil, nil
|
|
}
|
|
offset := p.RVAToFileOffset(dir.VirtualAddress)
|
|
if offset == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
info := &loadConfigInfo{}
|
|
if h.is64 {
|
|
var lc loadConfig64
|
|
if err := p.readStructAt(offset, &lc); err != nil {
|
|
return nil, err
|
|
}
|
|
if lc.Size > offsetOf64SecurityCookie {
|
|
info.securityCookie = lc.SecurityCookie
|
|
}
|
|
if lc.Size > offsetOf64SEHandlerCount {
|
|
info.sehHandlerTable = lc.SEHandlerTable
|
|
}
|
|
if lc.Size > offsetOf64GuardFlags {
|
|
info.guardFlags = lc.GuardFlags
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
var lc loadConfig32
|
|
if err := p.readStructAt(offset, &lc); err != nil {
|
|
return nil, err
|
|
}
|
|
if lc.Size > offsetOf32SecurityCookie {
|
|
info.securityCookie = uint64(lc.SecurityCookie)
|
|
}
|
|
if lc.Size > offsetOf32SEHandlerCount {
|
|
info.sehHandlerTable = uint64(lc.SEHandlerTable)
|
|
}
|
|
if lc.Size > offsetOf32GuardFlags {
|
|
info.guardFlags = lc.GuardFlags
|
|
}
|
|
return info, nil
|
|
}
|