and the debugger outputs the following messages:
HEAP[mem.exe]: Heap block at 002F27D8 modified at 002F2805 past requested size of 25
Unhandled exception at 0x77fa018c in mem.exe: User breakpoint.
That's just a debug output, in release build you won't see any thing, in debug build you can adjust the debug flag to tell the debugger what to check and what to ignore, you can choose to ignore the debug exception, or, you can simply link with the none-debug version of the runtime library. In any case it is not a crash, there is no need to make a big deal out of the debug output you see.

The debugger version puts some 0xFD bytes right after the allocated size, to check if you are attempting to access beyond what you asked for. It may or may not actually do the checking. And the release version of CRT library does not do such checking. In any case, the actual allocate space is a few bytes more than you asked for.

In principle, you should not step beyond what you initially asked for when allocating.

In reality, sometimes stepping just one byte beyond the allocated size will NOT cause crash, since the allocated size is always a multiple of 8 or 16.

In practice, allocating just the right size you want is BAD, whether it is size or size+1. You should NEVER allocate something like 4 bytes or 17 bytes using malloc(). You just throw in a lump sum of 512 bytes or 1024 bytes at least, when you probably needed just a few bytes. And it is prefered you try to get the memory on the stack, instead of on the heap.

malloc() is an expensive operation. I always avoid malloc() (implicit or explicit) where ever possible. And if it is unaviodable, give it a big enough chunk. It's pointless doing malloc(17).