May 13, 2014

Security Implications of IsBad*Ptr Calls in Binaries

IsBad*Ptr [1] functions are to test whether the memory range specified in the argument list is accessible. Despite the fact they have been banned, they are still being referenced in many binaries shipped with popular applications.

In this post I'm describing the inner working of IsBad*Ptr, the steps the attacker may follow to abuse them, and mention few examples of binaries that have a reference to these banned functions.

Inner Working

When IsBad*Ptr is executed it first registers an exception handler. Then, it attempts to access to the memory specified in the argument list.

For example, IsBadReadPtr has the following instruction to read memory. ECX is the memory address specified in the argument list.

mov al,byte ptr [ecx]

If the instruction raises an exception, the execution is transferred to the exception-handler code. And IsBad*Ptr returns TRUE, meaning, it is a "bad" pointer because the data pointed by is inaccessible.

If the instruction executes without an exception being raised IsBad*Ptr returns FALSE.

Steps of Attacking

The attack against IsBad*Ptr looks like this.
  1. The attacker attempts to supply an invalid pointer parameter to IsBad*Ptr that returns TRUE.
  2. The attacker refines step #1 in a way that the supplied invalid pointer becomes valid due to a forced allocation for the location pointed by the invalid pointer. The attacker refines step #1 in a way that the supplied invalid pointer will point to valid memory location allocated by heap spray. And so, IsBad*Ptr returns FALSE leading to enter in an inconsistent state; that may or may not be an exploitable state.
  3. If the attacker can perform step #2 with IsBadWritePtr, when the call returns, it's expected to reach code that writes the location pointed by the pointer -- and that has attacker controlled data. And so, he reaches a presumably exploitable condition.
Referencing IsBad*Ptr can be easily checked during binary analysis and it is worthy to do.

Examples

This code snippet below can be found in msvbvm60.dll in Windows folder.

.text:72A0FEE5         push    38h                     ; ucb
.text:72A0FEE7         push    edi                     ; attacker supplies pointer was invalid before
.text:72A0FEE8         call    ds:IsBadReadPtr         ; and now it's valid because he's filled memory up
.text:72A0FEEE         test    eax, eax
.text:72A0FEF0         jnz     loc_72A0FF80            ; fall through
.text:72A0FEF6         mov     eax, [edi+4]
.text:72A0FEF9         mov     eax, [eax+4]
.text:72A0FEFC         mov     esi, [eax+8]            ; ESI is attacker controlled
.text:72A0FEFF         and     [ebp+arg_0], 0
.text:72A0FF03         mov     ax, [edi+2]
.text:72A0FF07         test    esi, esi
.text:72A0FF09         jz      short loc_72A0FF42      ; fall through
.text:72A0FF0B         movzx   ebx, ax
.text:72A0FF0E         mov     eax, [esi]              ; EAX is attacker controlled
.text:72A0FF10         push    esi
.text:72A0FF11         call    dword ptr [eax+0Ch]     ; EIP is attacker controlled


This one below can be found in dxtrans.dll in Windows folder.

.text:35C6142C         push    4                       ; ucb
.text:35C6142E         push    esi                     ; attacker supplies pointer was invalid before
.text:35C6142F         call    ds:__imp__IsBadWritePtr@8 ; and now it's valid because he's filled memory up
.text:35C61435         test    eax, eax
.text:35C61437         jz      short loc_35C61440      ; jump is taken
.text:35C61439         mov     eax, 80004003h
.text:35C6143E         jmp     short loc_35C6144A
.text:35C61440 loc_35C61440:                           ; CODE XREF: CDXBaseSurface::GetAppData(ulong *)+14
.text:35C61440         mov     eax, [ebp+this]
.text:35C61443         mov     eax, [eax+24h]
.text:35C61446         mov     [esi], eax              ; ESI is attacker controlled

The next code snippet is taken from v2.0.50727\mscorwks.dll in Windows folder. IsBadReadPtr is used to test the pointer that is passed to MultiByteToWideChar.

.text:7A0D17FB         push    eax                     ; ucb
.text:7A0D17FC         push    ebx                     ; attacker supplies pointer was invalid before
.text:7A0D17FD         call    ds:__imp__IsBadReadPtr@8 ; and now it's valid because he's filled memory up
.text:7A0D1803         test    eax, eax
.text:7A0D1805         jz      short loc_7A0D17A7      ; jump is taken
[...]
.text:7A0D17A7         cmp     edi, esi
.text:7A0D17A9         jle     short loc_7A0D17BF
.text:7A0D17AB         push    esi                     ; cchWideChar
.text:7A0D17AC         push    esi                     ; lpWideCharStr
.text:7A0D17AD         push    edi                     ; cbMultiByte
.text:7A0D17AE         push    ebx                     ; lpMultiByteStr - attacker's data
.text:7A0D17AF         push    1                       ; dwFlags
.text:7A0D17B1         push    esi                     ; CodePage
.text:7A0D17B2         call    ?WszMultiByteToWideChar@@YGHIKPBDHPAGH@Z ; WszMultiByteToWideChar(uint,ulong,char const *,int,ushort *,int)

I was collecting files with IsBad*Ptr in them, and have found plenty others including but not exclusively MSCOMCTL.OCX, EXCEL.EXE, Lenovo's, Corel's, Nokia's, AVerMedia's products...

UPDATE 13/May/2014 To add IsBad*Ptr to the program doesn't automatically mean to create bugs. However if IsBad*Ptr is present we have reason to believe that the function is expecting a pointer that might be invalid in certain circumstances. In that case IsBad*Ptr may be used to attack the program. And that's why it's important to conduct the audit according to this.

UPDATE 16/May/2014 The term "valid pointer" had an ambiguous meaning in the part Steps of Attacking. This is now reworded. Reference.

[1] IsBad*Ptr functions are IsBadReadPtr, IsBadCodePtr, IsBadWritePtr, and IsBadStringPtr. All of them are exports of kernel32.dll.
  This blog is written and maintained by Attila Suszter. Read in Feed Reader. Advert is experimentally shown.