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

65 lines
2.9 KiB
Go

package winpwn
// SprayResult is one labeled sample produced by a single spray attempt --
// ID is caller-defined (a protocol id, an attempt index, whatever the
// target's own response associates with the sample) and Key is whatever
// comparable, measurable value the attempt produced, almost always a
// leaked heap/pointer address.
type SprayResult[K any] struct {
ID int
Key K
}
// SprayAndFind is the one grooming-loop shape behind both
// examples/heap_lfh and examples/heap_segment, pulled out into the library
// after writing near-identical versions of it twice by hand for those two
// tasks -- exactly the kind of repeated pattern worth a real primitive
// instead of a third copy-paste for the next heap task.
//
// It repeats spray up to maxAttempts times. After each new sample, it
// checks that sample against every sample collected so far (seed, plus
// every prior spray result) via match(older, newer); the first pair match
// reports true for is returned immediately, without spending the remaining
// attempts. Two distinct grooming shapes fall out of how seed/match are
// used:
//
// - "Does a spray ever land on this one known target?" (examples/heap_lfh's
// UAF: spray same-size replacements until one reuses the freed victim's
// slot) -- pass seed as the single already-known sample (e.g. the freed
// victim's leaked address) and match as plain equality. Every spray
// attempt is then checked against that one fixed target.
// - "Do any two sprayed samples satisfy a relation to each other?"
// (examples/heap_segment's adjacent-chunk overflow: find two allocations
// exactly sizeof(struct) apart) -- pass seed as nil/empty and match as
// the relation itself (e.g. "exactly N bytes apart"). Every new sample
// is checked against everything sprayed before it.
//
// Returns the matching (older, newer) pair, the attempt count spray reached
// before finding it, and ok=false if maxAttempts was exhausted with no
// match -- the caller decides whether that's worth retrying with a bigger
// spray (both example solve scripts just log.Fatal on it, since their
// USAGE.md walkthroughs already establish what spray size is reliable on a
// given machine/OS build; that reliability number is empirical, not a
// constant this function can know).
func SprayAndFind[K any](seed []SprayResult[K], maxAttempts int, spray func(attempt int) (SprayResult[K], error), match func(older, newer K) bool) (older, newer SprayResult[K], attempts int, ok bool, err error) {
samples := make([]SprayResult[K], len(seed))
copy(samples, seed)
for attempt := 1; attempt <= maxAttempts; attempt++ {
s, serr := spray(attempt)
if serr != nil {
return SprayResult[K]{}, SprayResult[K]{}, attempt, false, serr
}
for _, prev := range samples {
if match(prev.Key, s.Key) {
return prev, s, attempt, true, nil
}
}
samples = append(samples, s)
}
return SprayResult[K]{}, SprayResult[K]{}, maxAttempts, false, nil
}