Lenovo Protect Controlled Kernel Heap OOB Write in AddRegistryRepairList ---[ 1. Overview An integer overflow leading to a heap out of bounds write (OOBW) exists in the handling of the "AddRegistryRepairList" command to the Lenovo Protect Driver. Exploitation of this bug allows an attacker to overwrite kernel heap memory within the non-paged nonexecutable pool with attacker-supplied arbitrary data. Carefully crafted input can allow an attacker to achieve arbitrary code execution within the kernel context. The lrtp.sys driver referenced in this bug report is version 5.1.30.8281 with SHA1 hash c9589f6aa0ecac61d0cde9f07ca53e44de843944. PLEASE NOTE: This is an entirely separate bug to that reported in CVE-2024-10253. Moreover, it is possible that the fix for the previously reported bug may have introduced this one. PLEASE NOTE: Variable names and protocol details used in this report will not match those used in the original source for the driver and are only used to convey the usage as observed in the driver. ---[ 2. Lenovo Protect Driver Lenovo Protect is an endpoint protection solution bundled with the Lenovo Secure Browser (available from https://browser.lenovo.com.cn/). Amongst other things, it provides protection against the modification of several key operating system components, including registry keys and files. This is accomplished through the use of a kernel filter driver called lrtp.sys. LRTP is configured with Filter Driver Communication Ports with a custom messaging protocol. ---[ 3. lrtp Custom protocol The protocol used by the driver is relatively straightforward. The first DWORD represents the command ID and the remaining data is specific to the command used. Handlers for each command code are dispatched in the MessageNotifyCallback routine for the communication port "\\LRTPPort" registered using FltCreateCommunicationPort. In the case of "AddRegistryRepairList", the command code is 33 and the data comprises a header representing the total length of the buffer containing the strings, then several string entries. The resulting payload looks like this: |DWORD Command||DWORD Unknown||DWORD ListlenBytes|STRING String[n] Where the structure of a STRING is thus: |DWORD ObjectSize|DWORD Code|DWORD ByteLength|BYTE Buffer[ByteLength] ---[ 3.1 AddRegistryRepairList Handler The Handler for AddRegistryRepairList iterates over the string array provided by the caller, in turn calling a subroutine for each STRING object. The subroutine first checks that the ObjectSize and ByteLength are not zero, then also checks that the first byte of the provided string buffer is not zero. It then allocates a new kernel heap buffer of length ObjectSize + sizeof(LIST_ENTRY). If this allocation is successful, it proceeds to memcpy the STRING object into the new buffer. Once this is done, the routine checks to see whether this string is already present in the repair list, and if not, it adds it to the start of the linked list. ---[ 4. Heap OOB Write An error exists in the calculation of the required length for the new string prior to its storage. An attacker can cause an integer overflow to occur in the NumberOfBytes parameter passed to ExAllocatePoolWithTag. The following is pseudocode for the AddRegistryRepairList function. Note that The value of "NewString" is entirely attacker-controlled in the buffer passed from user mode. 1. ObjectSize = NewString->ObjectSize; 2. if ( NewString->ObjectSize >= 16u ) 3. { 4. _mm_lfence(); 5. listEntry = ExAllocatePoolWithTag( 6. (POOL_TYPE)POOL_NX_ALLOCATION, 7. ObjectSize + 16, 8. 'ssss'); 9. 10. if (stringBuffer) 11. { 12. memmove(&stringBuffer->String, NewString, NewString->ObjectSize); 13. } You will notice that the function first captures the provided object size. It then ensures that the size is greater than 16. If so, it proceeds to allocate the heap memory to store a copy. However, on line [7] of the above code, you will notice that it adds 16u inline. If the provided input size is sufficiently large, this addition calculation will wrap back around to zero or a small number (less than 16) causing a significantly smaller heap allocation to be returned. The function then copies the attacker-provided buffer into the new heap buffer using the larger size, writing off the end of the heap allocation and corrupting subsequent kernel memory. ---[ 5. Signature Verification The driver does validate the identity of the user-mode application by ensuring that it has the string value "MORIYA" and a valid signature. However, this can be bypassed by injecting a DLL into the LISTProt.exe file (requiring high integrity). ---[ 6. Kernel Panic The resulting Windows kernel panic is similar to this (offsets will change). The error code 50 signifies a page fault in non-paged memory, and the 2 signifying a write. *** Fatal System Error: 0x00000050 (0xFFFFCC8574801000,0x0000000000000002,0xFFFFF803166EC593,0x0000000000000002) Driver at fault: *** lrtp.sys - Address FFFFF803166EC593 base at FFFFF803166E0000, DateStamp 67076aa3 . Break instruction exception - code 80000003 (first chance) A fatal system error has occurred. Debugger entered on first try; Bugcheck callbacks have not been invoked. A fatal system error has occurred. # Child-SP RetAddr Call Site 00 ffff8401`759343a8 fffff803`13119022 nt!DbgBreakPointWithStatus 01 ffff8401`759343b0 fffff803`13118606 nt!KiBugCheckDebugBreak+0x12 02 ffff8401`75934410 fffff803`12ffe457 nt!KeBugCheck2+0x946 03 ffff8401`75934b20 fffff803`13045531 nt!KeBugCheckEx+0x107 04 ffff8401`75934b60 fffff803`12e39790 nt!MiSystemFault+0x1ccfd1 05 ffff8401`75934c60 fffff803`1300e46d nt!MmAccessFault+0x400 06 ffff8401`75934e00 fffff803`166ec593 nt!KiPageFault+0x36d 07 ffff8401`75934f98 fffff803`166e7c51 lrtp+0xc593 08 ffff8401`75934fa0 fffff803`166e79a2 lrtp+0x7c51 09 ffff8401`75935010 fffff803`166ebbcb lrtp+0x79a2 0a ffff8401`75935040 fffff803`0ef3bc8b lrtp+0xbbcb 0b ffff8401`75935080 fffff803`0ef727a9 FLTMGR!FltpFilterMessage+0xdb 0c (Inline Function) --------`-------- FLTMGR!FltpMsgDeviceControl+0xfc 0d ffff8401`759350e0 fffff803`0ef34a80 FLTMGR!FltpMsgDispatch+0x179 0e ffff8401`75935150 fffff803`12e4ad55 FLTMGR!FltpDispatch+0xe0 0f ffff8401`759351b0 fffff803`1322d2fc nt!IofCallDriver+0x55 10 ffff8401`759351f0 fffff803`1322cf4a nt!IopSynchronousServiceTail+0x34c 11 ffff8401`75935290 fffff803`1322c226 nt!IopXxxControlFile+0xd0a 12 ffff8401`759353e0 fffff803`13012305 nt!NtDeviceIoControlFile+0x56 The faulting instruction from the memmove routine. lrtp+0xc2a0: fffff804`862ec2a0 f30f7f40f0 movdqu xmmword ptr [rax-10h],xmm0 fffff804`862ec2a5 4803c8 add rcx,rax fffff804`862ec2a8 4d8bc8 mov r9,r8 fffff804`862ec2ab 49c1e905 shr r9,5 fffff804`862ec2af 4981f900200000 cmp r9,2000h fffff804`862ec2b6 0f8776000000 ja lrtp+0xc332 (fffff804`862ec332) fffff804`862ec2bc 4983e01f and r8,1Fh fffff804`862ec2c0 f30f6f440af0 movdqu xmm0,xmmword ptr [rdx+rcx-10h] ---[ 7. Proof of Concept Exploit A rudimentary proof of concept exploit can be found attached to this report. To run the exploit, clone https://github.com/kryc/DllInjector. Replace the DLL code with that in the PoC and compile for x86. Then run the DLLInjector in an Administrator command prompt. .\DllInjector.exe -launch \ "C:\Program Files (x86)\Lenovo\LenovoInternetSoftwareFramework\LISFProt.exe" It may take one or two attempts, but you should see a kernel panic. ---[ 8. Suggested Fix The fix for this issue is relatively simple. Just perform the addition calculation before the call to ExAllocatePoolWithTag and then verify that it is the correct size. ---[ 9. CVE Credit Please provide CVE credit to Gareth Evans (Kryc)