Files
go_pwner/checksec.go
T
2026-07-18 21:37:15 +03:00

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
}