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 }