Con un overflow dello stack - se continui a straripare - tu trabocchi prima dei vars dei locals, poi registri salvati, poi restituisci l'indirizzo, poi gli argomenti della funzione, poi piomba più in basso nello stack, forse gestori di eccezioni, ecc.
Di solito come attaccante si usa effettivamente l'indirizzo di ritorno sovrascritto per saltare da qualche parte interessante.
Con un overflow dell'heap si trabocca ... qualunque cosa si trova oltre il tuo pezzo di memoria. Nelle implementazioni di heap vecchie o errate che potrebbero essere metadati dell'heap che possono darti, ad es. il potere di scrivere cose in memoria (che potresti usare per sovrascrivere un puntatore a funzione). Nelle altre implementazioni dell'heap dovrai progettare un modello di allocazioni e deallocazioni per ottenere l'heap in uno stato in cui vi è un pezzo allocato interessante sul lato destro del tuo pezzo di memoria. Forse un vtable o qualche altro puntatore di funzione.
Gli overflow dell'heap sono altamente specifici per l'implementazione e l'applicazione dell'heap. Su Linux ci sono le tecniche "house of ..." perché glibc malloc è uno scherzo (mi spiace!), Su Windows l'heap è diventato molto ben protetto e ben randomizzato e bisogna sperare che le applicazioni portino le proprie implementazioni di heap insicure a ottieni l'1% di prestazioni in più. Lo fanno spesso.