GOT overwrite: hijacking library calls
With dynamic linking, printf is not called directly: it goes through the PLT, which jumps to the address stored in the GOT. With lazy binding, the GOT is writable.
The attack
If you have an arbitrary write primitive (format string, pointer overflow), overwrite free@got with the address of system. The next free(ptr) runs system(ptr); if ptr holds "/bin/sh", you get a shell.
write(free_got, addr_of_system)
free("/bin/sh") -> system("/bin/sh")Defence
Full RELRO (-Wl,-z,relro,-z,now): resolves all symbols at load and makes the GOT read-only. Combined with ASLR and PIE, it removes this convenient target.