The blog existentialize did a simple, easy to read writeup.
The basics are:
unsigned char *buffer, *bp;
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
Allocate what the requestor asked for. Without zeroing it out.
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
memcpy(bp, pl, payload);
Copy the sender's payload to send it back to them.
Except... the sender didn't send a payload.
Directly from the blog: "What if the requester didn't actually supply payload bytes, like she said she did? What if pl really is only one byte? Then the read from memcpy is going to read whatever memory was near the SSLv3 record and within the same process."