Talos Vulnerability Report

TALOS-2015-0024

Total Commander FileInfo Plugin Multiple Denial of Service Vulnerabilities

Jul 16, 2015

Description

Multiple exploitable denial of service vulnerabilities exist in the FileInfo Plugin for Total Commander. An attacker who can control the contents of a COFF Archive Library (.lib) file can can cause an out of bounds read by specifying overly large values for the 'Size' field of the Archive Member Header or the "Number Of Symbols" field in the 1st Linker Member. An attacker who can control the contents of a Linear Executable file can cause an out of bounds read by specifying overly large values for the "Resource Table Count" field of the LE Header or the "Object" field at offset 0x8 from a "Resource Table Entry". An attacker who successfully exploits this vulnerability can cause the Total Commander application to unexpectedly terminate.

Tested Versions

FileInfo 2.21
FileInfo 2.22

Product URLs

http://www.totalcmd.net/plugring/fileinfo.html

Details

The Microsoft documentation defines the COFF Archive File Structure as shown below:

        
Signature :”!<arch>\n”
    Header 1st Linker Member
    Header 2nd Linker Member
    Header Longnames Member
    Header Contents of OBJ File 1
    (COFF format)
    Header Contents of OBJ File 2
    (COFF format)
    Header Contents of OBJ File N
    (COFF format)
            
        

Each Archive Member is preceeded by a Header structure which contains a 'Size' field as shown below:

        
Offset Size Field        Description
    48   10  Size        ASCII decimalrepresentation of the total sizeof the
                          archivemember,not including the size of the header.
        
      

The 1st Linker Member contains a structure which includes a 'Number of Symbols' field as shown below:

        
Offset Size Field        Description
    0    4   Number of Symbols  Unsigned long containing the number of symbols indexed.
        
      

The first vulnerability is due to lack of validation on the 'Size' field from the Member Header. The below pseudocode is decompiled from the function that reads the Archive Member Headers, we have included annotations inline:

         
int __cdecl sub_10016D53(int value, char *fileContent)
    {
        char *currentLocation; // esi@3
        int v3; // eax@4
        int v4; // eax@6
        int v5; // eax@8
        int v6; // eax@10
        int v7; // eax@10
        int v8; // eax@11
        int v10; // [sp+0h] [bp-2Ch]@1
        int v11; // [sp+4h] [bp-28h]@10
        char v12; // [sp+8h] [bp-24h]@8
        char v13; // [sp+Ch] [bp-20h]@6
        int v14; // [sp+10h] [bp-1Ch]@4
        int v15; // [sp+14h] [bp-18h]@7
        int v16; // [sp+18h] [bp-14h]@5
        char v17; // [sp+1Ch] [bp-10h]@1
        int v18; // [sp+28h] [bp-4h]@1

        v10 = 0;
        ((void (__thiscall *)(char *, _DWORD))mfc90_316)(&v17, *(_DWORD *)&v17);
        v18 = 0;
        if ( strncmp(fileContent, "!<arch>\n", 8u) )
        {
         mfc90_310(value, "Not a valid .LIB file - signature not found\n");
        }
        else
        {
         currentLocation = fileContent + 8;
         if ( fileContent != (char *)-8 )
         {
           
      

This is where we begin to read the series of Archive Members which are described by their headers:

        
do
    {
         v3 = readArchiveHeader(&v14, currentLocation, currentLocation - fileContent);
         LOBYTE(v18) = 1;
         mfc90_941(&v17, v3);
         LOBYTE(v18) = 0;
         mfc90_601(&v14);
         mfc90_945(&v17, "\n");
         if ( strncmp(currentLocation, "/        ", 0x10u) )
         {
          if ( strncmp(currentLocation, "//       ", 0x10u) )
          {
                 -- snip --
          }
          else
          {
                 -- snip --
          }
         }
         else
         {
          if ( v16 )
          {
                 -- snip --
          }
          else
          {
           v4 = sub_10016BE0((int)&v13, currentLocation + 60);
           LOBYTE(v18) = 2;
           mfc90_941(&v17, v4);
           LOBYTE(v18) = 0;
           mfc90_601(&v13);
           mfc90_945(&v17, "\n");
           v16 = 1;
          }
         }
         
      

The next line reads the Size field offset (48) from the current Archive Header pointer which is held in the 'currentLocation' variable and then adds the Size value to the current Archive Header pointer:

        
currentLocation += (atoi(currentLocation + 48) + 61) & 0xFFFFFFFE;
     }
        
      

Finally, the attacker controlled pointer is passed to the strncmp function, resulting in an invalid memory access:

        
while ( !strncmp(currentLocation + 58, "`\n", 2u) && currentLocation );
      }
      mfc90_300(value, &v17);
     }
     mfc90_601(&v17);
     return value;
    }
        
     

The second vulnerability is due to lack of validation on the 'Number of Symbols' field from the 1st Linker Member. The below pseudocode is decompiled from the function that reads the 1st Linker Member, we have included annotations inline:

        
int __cdecl sub_10016BE0(int a1, char *pLinkerMember)
    {
     int numberOfSymbols; // edi@1
     const CHAR *v3; // esi@1
     int v4; // eax@3
     int v5; // edi@3
     LPSTR v6; // eax@5
     char flagMoreSymbols; // zf@8
     int counter; // [sp+4h] [bp-1Ch]@2
     int v10; // [sp+8h] [bp-18h]@1
     int v11; // [sp+8h] [bp-18h]@4
     int v12; // [sp+Ch] [bp-14h]@1
     const CHAR *v13; // [sp+10h] [bp-10h]@1
     int v14; // [sp+1Ch] [bp-4h]@1
     CHAR UnDecoratedName; // [sp+20h] [bp+0h]@1
     unsigned int v16; // [sp+420h] [bp+400h]@1

     v16 = (unsigned int)&UnDecoratedName ^ __security_cookie;
     mfc90_316(a1);
     mfc90_316(&v13);
     v13 = *(const CHAR **)pLinkerMember;
     v14 = 1;
         
       

The next two lines read the numberOfSymbols value from the file and set a pointer based upon that value:

        
numberOfSymbols = readBigEndinanDWORD(v13);
     v3 = &pLinkerMember[4 * numberOfSymbols + 4];
     mfc90_945(a1, "FIRST LINKER MEMBER\n");
     v13 = (const CHAR *)numberOfSymbols;
     mfc90_2539(&v13, "\tSymbols\t: %08X\n\n");
     mfc90_941(v12, &v13);
     mfc90_945(v12, "\tMbrOffs \tName\n\t-------------------- \t--------------------\n");
     if ( numberOfSymbols )
     {
      counter = numberOfSymbols;
      do
      {
       v13 = *(const CHAR **)v10;
       v4 = readBigEndinanDWORD(v13);
       v5 = v4;
       if ( dword_100310A8 )
       {
             
      

Finally, the attacker controlled value is passed to the strncmp function, resulting in an invalid memory access:

        
if ( strncmp("__imp_", v3, 6u) ) <- read out of bound
              {
               v6 = sub_10016F56(v3, &UnDecoratedName, 0x400u);
               mfc90_2539(&v13, "\t%08X \t%s\r\n", v5, v6);
              }
              else
              {
               mfc90_820(&v13, &Default);
              }
             }
             else
             {
              v13 = v3;
              mfc90_2539(&v13, "\t%08X \t%s\r\n", v4, v3);
             }
             mfc90_941(v12, &v13);
             v10 = v11 + 4;
             flagMoreSymbols = counter-- == 1;
             v3 += strlen(v3) + 1;
            }
            while ( !flagMoreSymbols );
           }
           mfc90_601(&v13);
           return v12;
          }
          
      

Linear Executable File Format: http://faydoc.tripod.com/formats/exe-LE.htm

The important structures for these vulnerabilities are:

        
 ---------------------------
 |   MS-DOS header   |
 ---------------------------
 |    LE header    |
 ---------------------------
 |      ...      |
 ---------------------------
 | Windows resource data |
 ---------------------------
 |      ...      |
 ---------------------------
        
      

The below pseudocode is decompiled from the function that reads the Resource Table Entry, we have included annotations inline:

        
 00000001 bool __thiscall sub_1000542B(void *this, struct_somestruct *somestruct)
 00000002 {
 00000003  void *v2; // edi@1
 00000004  LE_header *le_header; // eax@1
 00000005  size_t winResSize; // edx@1
 00000006  bool result; // eax@2
 00000007  WinResource *winResource; // esi@3
 00000008  void *winResourceBuffer; // eax@7
 00000009  size_t v8; // ST08_4@7
 00000010  LPVOID lpBuffer; // [sp+4h] [bp-8h]@7
 00000011  size_t puLen; // [sp+8h] [bp-4h]@1
 00000012
 00000013  v2 = this;
 00000014  sub_10004903();
 00000015  le_header = (LE_header *)&somestruct->fileContent[somestruct->LE_offset];
 00000016  winResSize = le_header->winResSizeData;
 00000017  puLen = winResSize;
 00000018  *((_DWORD *)v2 + 18) = winResSize;
 00000019  if ( winResSize )
 00000020  {
 00000021   winResource = (WinResource *)&somestruct->fileContent[le_header->winResOffset];
 00000022   if ( winResource->byte3 > 0 )
 00000023   {
 00000024    strlen(&winResource->byte3);
 00000025    winResSize = puLen;
 00000026    ++winResource;
 00000027   }
 00000028   if ( winResource->dword8 <= winResSize ) <-- check to bypass
 00000029   {
 00000030    winResSize = winResource->dword8;
 00000031    puLen = winResource->dword8;
 00000032   }
 00000033   winResourceBuffer = (void *)mfc90_265(winResSize);
 00000034   v8 = puLen;
 00000035   *((_DWORD *)v2 + 3) = winResourceBuffer;
 00000036   memcpy(winResourceBuffer, &winResource->charC, v8); <--- read access violation
 00000037   if ( VerQueryValueA(*((const LPVOID *)v2 + 3), "\\", &lpBuffer, &puLen) )
 00000038    memcpy((char *)v2 + 20, lpBuffer, puLen);
 00000039   result = sub_10004FD5(v2) != 0;
 00000040  }
 00000041  else
 00000042  {
 00000043   result = 0;
 00000044  }
 00000045  return result;
 00000046 }
        
      

The vulnerability is due to lack of validation whether the size of the resource data is greater than entire file size before passing it as the size parameter for a memcpy operation.

Value of this fieLd is defined in LE Header at specified location:

        
+0xBC size of win-resource data             DWORD
        
      

Setting this value greater than file size causes a read access violation at line:

        
00000036   memcpy(winResourceBuffer, &winResource->charC, v8);
        
      

WinDbg output when read access violation occurs:

        
(d1c.82c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=10020ac0 ecx=03b37ffd edx=00000000 esi=1004fffc edi=1ed30010
eip=7855b05b esp=000a5f34 ebp=000a5f3c iopl=0     nv dn ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000       efl=00010612
MSVCR90!memcpy+0x23b:
7855b05b f3a5      rep movs dword ptr es:[edi],dword ptr [esi]
0:000> kb

ChildEBP RetAddr Args to Child
000a5f3c 10005496 10050020 0137000c 44332211 MSVCR90!memcpy+0x23b
WARNING: Stack unwind information not available. Following frames may be wrong.
000a5f64 10005644 000a5f80 4bd180e7 000a6020 fileinfo+0x5496
000a5fdc 100074f9 0123b250 4bd1bf97 00000000 fileinfo+0x5644
000a60ac 7e42f40b 0095be08 009574c0 00000000 fileinfo!ListLoad+0x17b1
000a6190 00000000 00000000 00000000 00000000 user32!SendMessageA+0x7f
           
        

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos

*Note - This report replaces TALOS-2015-24 and TALOS-2015-25.