Hello.
In the last entry, we saw how to inject and execute ASM (shellcode) by the use of a new thread using CreateRemoteThread.
The problem is: if a function is self-threaded and cannot be called from a foreign thread, we're f*cked...
To do this injection, we will use the main thread to do our things.
We will open the main thread and redirect the execution flow to our shellcode and then, go back to our previous position.
This can be done using GetThreadContext and SetThreadContext to get and change EIP.
The scheme is almost the same:
* Get process handle using OpenProcess
* Allocate memory for our shellcode using VirtualAllocEx
* Write our shellcode using WriteProcessMemory
* Open the main thread using OpenThread
* Suspend main thread using SuspendThread
* Get EIP using GetThreadContext (and keep it)
* Set EIP to our shellcode's address using SetThreadContext
* Resume main thread using ResumeThread
* Close process and thread handle using CloseHandle
All these imports are from pinvoke.net, so thanks for this useful wiki.
Code:
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
public static extern int ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll")]
public static extern int SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenThread(ThreadAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
public static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT lpContext);
[DllImport("kernel32.dll")]
public static extern bool SetThreadContext(IntPtr hThread, ref CONTEXT lpContext);
Code:
public enum CONTEXT_FLAGS : uint {
CONTEXT_i386 = 0x10000,
CONTEXT_i486 = 0x10000,
CONTEXT_CONTROL = CONTEXT_i386 | 0x01,
CONTEXT_INTEGER = CONTEXT_i386 | 0x02,
CONTEXT_SEGMENTS = CONTEXT_i386 | 0x04,
CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x08,
CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x10,
CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x20,
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS,
CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS
}
[Flags]
public enum ThreadAccessFlags : int {
TERMINATE = 0x0001,
SUSPEND_RESUME = 0x0002,
GET_CONTEXT = 0x0008,
SET_CONTEXT = 0x0010,
SET_INFORMATION = 0x0020,
QUERY_INFORMATION = 0x0040,
SET_THREAD_TOKEN = 0x0080,
IMPERSONATE = 0x0100,
DIRECT_IMPERSONATION = 0x0200
}
Code:
[StructLayout(LayoutKind.Sequential)]
public struct CONTEXT {
public uint ContextFlags;
public uint Dr0;
public uint Dr1;
public uint Dr2;
public uint Dr3;
public uint Dr6;
public uint Dr7;
public FLOATING_SAVE_AREA FloatSave;
public uint SegGs;
public uint SegFs;
public uint SegEs;
public uint SegDs;
public uint Edi;
public uint Esi;
public uint Ebx;
public uint Edx;
public uint Ecx;
public uint Eax;
public uint Ebp;
public uint Eip;
public uint SegCs;
public uint EFlags;
public uint Esp;
public uint SegSs;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
public byte[] ExtendedRegisters;
}
We keep the same shellcode as the previous entry, don't forget to correctly align the stack!
The process opening and shellcode writing is done by the same method, so I will skip this part...
Now, we can begin the interresting part, the threads operations.
We will now get and open the main thread.
The main thread is the first thread of the Threads property of the Process class.
To open the thread, we have to get the thread's ID and then, call OpenThread.
Code:
Process process = /* get your process*/;
uint iThreadId = process.Threads[0].Id;
IntPtr hThread = OpenThread(ThreadAccessFlags.SUSPEND_RESUME | ThreadAccessFlags.SET_CONTEXT | ThreadAccessFlags.GET_CONTEXT, false, iThreadId);
if (hThread == IntPtr.Zero)
throw new ApplicationException("Cannot get thread handle.");
We suspend the process to get no trouble during EIP changing:
Code:
CONTEXT context = new CONTEXT();
if (!GetThreadContext(hThread, ref context))
throw new ApplicationException("Cannot get thread context.");
Now, we've EIP in the CONTEXT structure.
We're a going to modify the shellcode on the fly to get the whole thing working.
If you carefuly understand how we redirect the flow, it should be clear.
We're to save all registers and flags to restore them after the shellcode execution to go back to our previous code (old EIP).
So, we use PUSHAD (push registers on the stack), PUSHFD (push flags on the stack) and then POPFD and POPAD (LIFO order (Last In First Out)).
At the top of the shellcode, we push the old EIP to jump it back using a RET.
I wrote a function who give me the modified shellcode (ugly-fastly-done code):
Code:
private byte[] ModifyShellcode(byte[] bytes, uint eip) {
byte[] bytesEip = BitConverter.GetBytes(eip);
byte[] start = new byte[] {
0x60, // PUSHAD
0x9C // PUSHFD
};
byte[] end = new byte[] {
0x9D, // POPFD
0x61, // POPAD
0xC3 // RET
};
IEnumerable<byte> result = Enumerable.Empty<byte>();
result = result.Concat(new byte[] { 0x68 }); // PUSH
result = result.Concat(bytesEip); // EIP
result = result.Concat(start);
result = result.Concat(bytes);
result = result.Concat(end);
return result.ToArray();
}
Now, we can inject our modified shellcode like the previous entry.
Change EIP is a child game!
Code:
context.Eip = (uint)hAlloc;
You have to set the (new) context of the thread using SetThreadContext.
Code:
if (SetThreadContext(hThread, ref context) == false)
throw new ApplicationException("Cannot set thread context.");
Just resume the process using ResumeThread.
Code:
if (ResumeThread(hThread) == -1)
throw new ApplicationException("Cannot resume thread.");]
And voilà, our shellcode is executed in the main thread of an application.
And to finish, close the thread handle!
Code:
if (!CloseHandle(hThread))
throw new ApplicationException("Cannot close thread handle.");
Go further:
* Check when the shellcode reach the end to clean out the memory.
Thanks:
* Ivanlef0u [FRENCH] (provide me some advice about thread suspending/resuming)
Edit:
* [WoW][C#]RemoteThreadHijack (alternative to BlackMagic's InjectAndExecute) (this is the same method as I did)