65 lines
2.9 KiB
Go
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
|
|
}
|