Metafile Image Code Execution

Mark Russinovich's eMail to Microsoft and me
regarding his Windows Metafile Image
Code Execution analysis:

Subject: My findings on the WMF "backdoor"
Date: Mon, 16 Jan 2006 10:45:52 -0600
From: "Mark Russinovich"
To: "Steve Gibson"

Hi Steve,

I've finished by investigation and am reporting my findings.

First, here's very loose pseudocode for PlayMetaFile based on my analysis the binary code (I do not have Windows source code access):

    // Play the records in the specified metafile
    PlayMetaFile( hDC, hMetafile ) {
        // Initialize the metafile playback offset    
        hMetafile->CurrentOffset = 0;
        // Get the next record in the metafile
        while( hMetafile->CurrentRecord = GetEvent( hMetafile ))  {
          // If an abort process is registered, call it and give it
          // a chance to abort the playback
            if( hMetafile->AbortProc ) {
                if( !hMetafile->AbortProc( hDC, 0 )) {
               goto done;
          // Play the next record
          PlayMetaFileRecord( hDC, hMetafile->CurrentRecord );            
            // cleanup
The pseudocode for GetEvent is:
    // Return a pointer to the next record in the metafile; NULL if 
    // there are no more
    GetEvent( hMetafile ) {
      // Increment the offset into the metafile by the size of the last
      // record    
      hMetafile->CurrentOffset += hMetafile->CurrentRecord->Size;
        // If the playback offset is past the end of the file or if it points    
      // at an event ID that's 0 or negative then the playback is done
      // so return NULL
        if( hMetafile->CurrentOffset >= hMetafile->Size ||
            hMetafile->Record[ hMetafile->CurrentOffset]->Event <= 0 ) {
         return NULL;
        // Return the next record
        return hMetafile->Record[ hMetafile->CurrentOffset ];
The code flow explains why you only observe the execution of your abort function when you pass in certain "illegal" values for the escape/abortproc record size: since the record is the last and only one of the metafile, PlayMetaFile won't execute the abort procedure. The values you passed that triggered the execution land GetEvent on non-zero values that GetEvent interprets as valid event IDs, tricking it into thinking that there are more records in the file. The values you pass that don't trigger the abort proc execution land GetEvent on zero values, making it think its at the end of the file.

The fact that PlayMetaFile calls the abort procedure after its set doesn't jibe with the SetAbortProc documentation at:


which states: "It [the abort proc] is called when a print job is to be cancelled during spooling." This implies that the abort proc is called by Windows not notify the application that the job is cancelled and is the interpretation you state at https://www.grc.com/groups/news.feedback:60006

"[the abort proc] is the address of an application-provided "callback" -- a subroutine provided by the application that is expressly designed to asynchronously accept the news and notification of a printing job being aborted for whatever reason."

However, I ran across a statement at the bottom of "Preparing to Print" describes it differently:


"After the application registers the AbortProc abort procedure, GDI calls the function periodically during the printing process to determine whether to cancel the job."

Thus, the abort procedure really works the opposite way, providing the application a way to notify Windows that it wants to cancel printing.

If you take into consideration the fact that a WMF file is a GDI script of arbitrary length (which can also be directed at a printer) with the revised understanding of an abort proc, it makes perfect sense for PlayMetaFile to call the registered abort proc after playing each record to give an application a chance to cancel the playback of the rest of the metafile.

The final question is why PlayMetaFile expects the abort procedure to be in-lined in the metafile. I think that the answer to that can be attributed to the Windows 3.1 memory model, which relied on far procedure calls and data references. Thus, you could easily embed the code into the record and not worry about relative addressing issues as long as you used the far programming model.

The bottom line is that I'm convinced that this behavior, while intentional, is not a backdoor.

Feel free to publish this on your site.

Thanks and let me know if you have any questions.


Note also that later that week Mark expanded upon his initial findings with his own blog posting containing additional information, details, and speculation:


Jump to top of page
Gibson Research Corporation is owned and operated by Steve Gibson.  The contents
of this page are Copyright (c) 2024 Gibson Research Corporation. SpinRite, ShieldsUP,
NanoProbe, and any other indicated trademarks are registered trademarks of Gibson
Research Corporation, Laguna Hills, CA, USA. GRC's web and customer privacy policy.
Jump to top of page

Last Edit: May 04, 2013 at 18:12 (4,001.02 days ago)Viewed 2 times per day