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 }