Timestamp function with millisecond precision
You state that operating specific functions are out, which I assume means that you don't want to use operating system specific system functions. However in this particular case that is actually hindering you from actually solving your task, as POSIX-based systems and Windows-based systems has two different ways of handling this.

Although the ftime is present in various operating system, it has been marked as obsolete as of POSIX.1-2008:


  4.2BSD, POSIX.1-2001.  POSIX.1-2008 removes the specification of ftime().
  
  This function is obsolete.  Don't use it.  If the time in seconds
  suffices, time(2) can be used; gettimeofday(2) gives microseconds;
  clock_gettime(2) gives nanoseconds but is not as widely available.


Further is the gettimeofday() also obsolete as of POSIX.1-2008, leaving only the clock_gettime() alternative, which is not as widely available. Most notably not present in Windows, as indicated in "Porting clock_gettime to windows".

On Windows there are some other options, but seems that most fall back to using QueryPerformanceCounter &amp; co (referred to as QPCs) as indicated in "Acquiring high-resolution time stamps". Some of my search findings also indicate that the QPCs are not be trusted in SMP (Symmetric MultiProcessing environments).

In other words, if you want to do this correctly for use in various operating systems, you do need to make it operating system dependent. If you choose to carry on with your solution using the obsolete ftime() more could surely be said, but here are some comments on your actual code:


Loose the append variable – You already have room in buf for the result, and know how much you've already used. I.e. replace it with something like either sprintf(&amp;buf[8], ":%03u", start.millitm); or sprintf(buf + 8, ":%03u", start.millitm);1, and you can return without doing the strcat at the end.
Avoid busy loops – Your while loop is called a busy loop as it does nothing excepting counting. It is usually better to trigger a sleep function or similar, allowing the processor to do other work in between. 
Strange reset of countdown – Instead of resetting the countdown in the for loop, it would be clearer code to reset it just before the while loop, or even better (if persisting to use the busy loop) to make it into a locally scoped for loop, i.e for (int countdown = 0; countdown &lt; 1000000; countdown++) ;
Decide on one style of bracing – Decide whether you want to have opening braces on the start of the line, or end of preceding line, and stick to it. Mixing like you've done here make your code somewhat harder to read, and when mixing with empty block (aka the busy loop) with missing braces, it is not good. 

Some guys advocate a different opening brace style for functions versus other blocks, but I would suggest using the same style for everything making it more consistent.


Addendum: C11 and xtime_get or rather timespec_get

Jerry Coffin refers to a xtime_get() with potential nanoseconds precision (albeit still dependent on operating system precision). This is part of the C++ boost library. It was proposed to be included in C11 (see N1548.pdf, 7.25.7, p 383. This was revised in N1570, 7.27.2.7, into a timespec_get() function. N1570 is the latest draft available, before the standard was defined (and made commercially available). From this I quote:


  3 If base is TIME_UTC, the tv_sec member is set to the number of seconds since an implementation defined epoch, truncated to a whole value and the tv_nsec member is set to the integral number of nanoseconds, rounded to the resolution of the system clock.321)
  ...
  321) Although a struct timespec object describes times with nanosecond resolution, the available resolution is system dependent and may even be greater than 1 second.


This does however seem to have made it into C11 (also known as ISO/IEC 9899:2011), according to the table of contents listed here, see section 7.27.2.5. So if your compiler and libraries is compliant to C112, you might use timespec_get() and possibly get a system dependent precision up to nanoseconds (or even greater than 1 second).



1 Thanks to 200_success for pointing out error in code related to buf[8] not being an acceptable target for sprintf()
2 For the gcc family this seems to be gcc ver 4.6 and glibc ver 2.16, or newer