神刀安全网

C/C++ pointers: pointers abuse in Windows kernel

2-Jun-2016: C/C++ pointers: pointers abuse in Windows kernel

(For those who have a hard time in understanding C/C++ pointers).

(First parthere, second ishere).

The resource section of PE executable file in Windows OS is a section containing pictures, icons, strings, etc. Early Windows versions allowed to address resources only by IDs, but then Microsoft added a way to address them using strings.

So then it would be possible to pass ID or string to FindResource() function. Which is defined like this:

 HRSRC WINAPI FindResource(   _In_opt_ HMODULE hModule,   _In_     LPCTSTR lpName,   _In_     LPCTSTR lpType ); 

lpName and lpType has char* or wchar* types, and when someone still wants to pass ID, he/she should use MAKEINTRESOURCE macro, like this:

 result = FindResource(..., MAKEINTRESOURCE(1234), ...); 

It’s interesting fact that MAKEINTRESOURCE is merely casting integer to pointer. In MSVC 2013, in the file Microsoft SDKs/Windows/v7.1A/Include/Ks.h we can find this:

 ...  #if (!defined( MAKEINTRESOURCE ))  #define MAKEINTRESOURCE( res ) ((ULONG_PTR) (USHORT) res) #endif  ... 

Sounds insane. Let’s peek into ancient leaked Windows NT4 source code. In private/windows/base/client/module.c we can find FindResource() source code:

 HRSRC FindResourceA(     HMODULE hModule,     LPCSTR lpName,     LPCSTR lpType     )  ...  {     NTSTATUS Status;     ULONG IdPath[ 3 ];     PVOID p;      IdPath[ 0 ] = 0;     IdPath[ 1 ] = 0;     try {         if ((IdPath[ 0 ] = BaseDllMapResourceIdA( lpType )) == -1) {             Status = STATUS_INVALID_PARAMETER;             }         else         if ((IdPath[ 1 ] = BaseDllMapResourceIdA( lpName )) == -1) {             Status = STATUS_INVALID_PARAMETER; ... 

Let’s proceed to BaseDllMapResourceIdA in the same source file:

 ULONG BaseDllMapResourceIdA(     LPCSTR lpId     ) {     NTSTATUS Status;     ULONG Id;     UNICODE_STRING UnicodeString;     ANSI_STRING AnsiString;     PWSTR s;      try {         if ((ULONG)lpId & LDR_RESOURCE_ID_NAME_MASK) {             if (*lpId == '#') {                 Status = RtlCharToInteger( lpId+1, 10, &Id );                 if (!NT_SUCCESS( Status ) || Id & LDR_RESOURCE_ID_NAME_MASK) {                     if (NT_SUCCESS( Status )) {                         Status = STATUS_INVALID_PARAMETER;                         }                     BaseSetLastNTError( Status );                     Id = (ULONG)-1;                     }                 }             else {                 RtlInitAnsiString( &AnsiString, lpId );                 Status = RtlAnsiStringToUnicodeString( &UnicodeString,                                                        &AnsiString,                                                        TRUE                                                      );                 if (!NT_SUCCESS( Status )){                     BaseSetLastNTError( Status );                     Id = (ULONG)-1;                     }                 else {                     s = UnicodeString.Buffer;                     while (*s != UNICODE_NULL) {                         *s = RtlUpcaseUnicodeChar( *s );                         s++;                         }                      Id = (ULONG)UnicodeString.Buffer;                     }                 }             }         else {             Id = (ULONG)lpId;             }         }     except (EXCEPTION_EXECUTE_HANDLER) {         BaseSetLastNTError( GetExceptionCode() );         Id =  (ULONG)-1;         }     return Id; } 

lpId is ANDed with LDR_RESOURCE_ID_NAME_MASK . Which we can find in public/sdk/inc/ntldr.h :

 ...  #define LDR_RESOURCE_ID_NAME_MASK 0xFFFF0000  ... 

So lpId is ANDed with 0xFFFF0000 and if some bits beyond lowest 16 bits are still present, first half of function is executed ( lpId is treated as a string). Otherwise – second half ( lpId is treated as 16-bit value).

Still, this code can be found in Windows 7 kernel32.dll file:

 ....  .text:0000000078D24510 ; __int64 __fastcall BaseDllMapResourceIdA(PCSZ SourceString) .text:0000000078D24510 BaseDllMapResourceIdA proc near         ; CODE XREF: FindResourceExA+34 .text:0000000078D24510                                         ; FindResourceExA+4B .text:0000000078D24510 .text:0000000078D24510 var_38          = qword ptr -38h .text:0000000078D24510 var_30          = qword ptr -30h .text:0000000078D24510 var_28          = _UNICODE_STRING ptr -28h .text:0000000078D24510 DestinationString= _STRING ptr -18h .text:0000000078D24510 arg_8           = dword ptr  10h .text:0000000078D24510 .text:0000000078D24510 ; FUNCTION CHUNK AT .text:0000000078D42FB4 SIZE 000000D5 BYTES .text:0000000078D24510 .text:0000000078D24510                 push    rbx .text:0000000078D24512                 sub     rsp, 50h .text:0000000078D24516                 cmp     rcx, 10000h .text:0000000078D2451D                 jnb     loc_78D42FB4 .text:0000000078D24523                 mov     [rsp+58h+var_38], rcx .text:0000000078D24528                 jmp     short $+2 .text:0000000078D2452A ; --------------------------------------------------------------------------- .text:0000000078D2452A .text:0000000078D2452A loc_78D2452A:                           ; CODE XREF: BaseDllMapResourceIdA+18 .text:0000000078D2452A                                         ; BaseDllMapResourceIdA+1EAD0 .text:0000000078D2452A                 jmp     short $+2 .text:0000000078D2452C ; --------------------------------------------------------------------------- .text:0000000078D2452C .text:0000000078D2452C loc_78D2452C:                           ; CODE XREF: BaseDllMapResourceIdA:loc_78D2452A .text:0000000078D2452C                                         ; BaseDllMapResourceIdA+1EB74 .text:0000000078D2452C                 mov     rax, rcx .text:0000000078D2452F                 add     rsp, 50h .text:0000000078D24533                 pop     rbx .text:0000000078D24534                 retn .text:0000000078D24534 ; --------------------------------------------------------------------------- .text:0000000078D24535                 align 20h .text:0000000078D24535 BaseDllMapResourceIdA endp  ....  .text:0000000078D42FB4 loc_78D42FB4:                           ; CODE XREF: BaseDllMapResourceIdA+D .text:0000000078D42FB4                 cmp     byte ptr [rcx], '#' .text:0000000078D42FB7                 jnz     short loc_78D43005 .text:0000000078D42FB9                 inc     rcx .text:0000000078D42FBC                 lea     r8, [rsp+58h+arg_8] .text:0000000078D42FC1                 mov     edx, 0Ah .text:0000000078D42FC6                 call    cs:__imp_RtlCharToInteger .text:0000000078D42FCC                 mov     ecx, [rsp+58h+arg_8] .text:0000000078D42FD0                 mov     [rsp+58h+var_38], rcx .text:0000000078D42FD5                 test    eax, eax .text:0000000078D42FD7                 js      short loc_78D42FE6 .text:0000000078D42FD9                 test    rcx, 0FFFFFFFFFFFF0000h .text:0000000078D42FE0                 jz      loc_78D2452A  ....  

If value in input pointer is greater than 0x10000, jump to string processing is occurred. Otherwise, input value of lpId is returned as is. 0xFFFF0000 mask is not used here any more, because this is 64-bit code after all, but still, 0xFFFFFFFFFFFF0000 could work here.

Attentive reader may ask, what if address of input string is lower than 0x10000? This code relied on the fact that there are no pointers below 0x10000 in Windows code, well, at least in Win32 realm.

Raymond Chen writes about this:

How does MAKE­INT­RESOURCE work? It just stashes the integer in the bottom 16 bits of a pointer, leaving the upper bits zero. This relies on the convention that the first 64KB of address space is never mapped to valid memory, a convention that is enforced starting in Windows 7.

In short words, this is dirty hack and probably you should use it only if you have to. Probably, FindResource() function in past had SHORT types for its arguments, and then Microsoft has added a way to pass strings there, but older code should also work? I’m not sure, but that’s possible.

Now here is my short distilled example:

 #include <stdio.h> #include <stdint.h>  void f(char* a) {  if (((uint64_t)a)>0x10000)   printf ("Pointer to string has been passed: %s/n", a);  else   printf ("16-bit value has been passed: %d/n", (uint64_t)a); };  int main() {  f("Hello!"); // pass string  f((char*)1234); // pass 16-bit value }; 

It works!

This open sourced site and this page in particular ishosted on GitHub. Patches, suggestions and comments are welcome.

Interested in articles like this? Subscribe to myblog and/or twitter: @yurichev and/or facebook .

→ [list of blog posts]

The page last updated on 02-June-2016

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » C/C++ pointers: pointers abuse in Windows kernel

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址