系列前言
这是一个带来 快乐 的专栏,通过分析的 众多 APT 组织的恶意样本,从中 learn 到了许多恶意的编程技巧,当然这些恶意是相对的,主要看人的主观目的,技术本就是中立的。
运用这些技巧来构建一个 整活 的程序,趁 mate 不注意悄悄安装到 ta 的 PC 上,自己则在角落观察 ta 从发现 abnormal,到逐步暴躁的全过程。希望在整活过程中遵循一些底线,不然 friend ship 说翻就翻。
1.Thought
一开始我是 want 做这样的 Eastwest:
- 截图当前桌面。
- 将其设置为桌面背景,并隐藏桌面图标。
这样就会导致,当 mate 点击桌面图标时不会得到任何的相应,当其不知道 整活 存在时会观察其 behavior 将会非常的有意思。想到这里我就开始的动手尝试,通过碰到的问题得到的一下三种解决方案,最终能够使用的就是 桌面背景 和 透明窗口。
- 桌面背景
-
右键菜单隐藏桌面图标,一个最简单的方式就是右键菜单中的 显示桌面图标 选项,将它的 √ 取消就能够达到我们的目的,但是我查阅相关资料之后没有找到与之相关的 API 函数。所以这种方案只能放弃。
-
很遗憾 kill explorer.exe 方案有些过时了,只能在 Win7 之前的 PC 上使用,当 Windows 进入到 Win8 时代之后,对于 explorer.exe 进行了重大的改变,不仅让其有了更为 美し UI,而且其底层实现也发生了重大变化。对于这个 idea 的影响就是:Win7 中如果 kill explorer.exe 不会导致桌面背景的 disappear,而在 Win8/10 则会导致桌面背景变为全黑。这里记录下我当时搜索解决方案的网址:
[1]Standalone desktop wallpaper module to display wallpaper without explorer.exe
[2]Display background image without shell
[3]Windows 10 Black Screen after killing Explorer.exe
[4]How can I set a desktop wallpaper if the explorer.exe shell is intentionally never started
[5]Only wallpaper is shown. Cannot access anything
Search Keyword:display wallpaper without explorer.exe,display background images without shell。


- 挂起 explorer.exe
当 explorer.exe 对于用户的请求没有进行相应时就会产生卡了一样的感觉,这是我在上面的方案不能正常实施之后想到的一个解决办法,但是存在这样的问题 when explorer.exe 未响应时,如果进行操作,系统会自动将 explorer.exe 重启。 - 透明窗口
在 Windows 10 Black Screen after killing Explorer.exe 的回复中给出了另一种可行的思路:
- 创建一个窗口,并将其至于最上层
- 在窗口中加载背景图片
结合这上述思路进行实践,发现了一个重要的特点如果在 WM_PAINT 消息中只调用 BeginPaint 和 EndPaint 能绘制出一幅透明的窗口,如果这个窗口位于顶层则会导致 鼠标无效的假象——图形界面中的任何图标都没有反应。
2.背景桌面(仅win7有效)
完成桌面背景的 Prank 计划需要分为三步:
- 截屏桌面
- 设置为桌面背景
- kill explorer.exe
[1] 截取桌面
- 由于我们希望截取的画面是桌面,所以其中就不应该包含其他的窗口,故在截取前我们需要通过程序发出 Win + D来显示桌面。Windows API 中提供了 SendInput 来发送键盘消息,这里的消息需要有4个内容,包括Win 和 D 的按下和松开。
void DesktopBackGround::SimulateWinD() {
// The INPUT structure is used to store information about a simulated input event
INPUT inputs[4] = {}; // We need 4 inputs: down Win, down D, up D, up Win
// Press the left Windows key
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki.wVk = VK_LWIN; // VK_LWIN is the virtual key code for the left Windows key
inputs[0].ki.wScan = 0;
inputs[0].ki.dwFlags = 0; // 0 for key press
inputs[0].ki.time = 0;
// Press the 'D' key
inputs[1].type = INPUT_KEYBOARD;
inputs[1].ki.wVk = 'D'; // 'D' is the virtual key code for the 'D' key
inputs[1].ki.wScan = 0;
inputs[1].ki.dwFlags = 0; // 0 for key press
inputs[1].ki.time = 0;
// Release the 'D' key
inputs[2] = inputs[1];
inputs[2].ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
// Release the left Windows key
inputs[3] = inputs[0];
inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
// Send the input events to the system
SendInput(4, inputs, sizeof(INPUT));
}
[2] 设置为桌面背景。
- 下面就是截取桌面,并将其存储为一个 .bmp 文件的过程。通过 Windows 提供的 BitBlt 函数来进行相关操作,将文件存入到 strPho 所指向的路径,这里最好是 FullPath,尽管存储时不会有影响,但当使用其作为桌面壁纸的 API 函数需要完整路径。
// Function to save a DIB as a BMP file
bool DesktopBackGround::SaveBitmapToFile(BITMAPINFO* pbi, void* bits, const char* filename) {
HANDLE fh = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (fh == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to create file: " << filename << std::endl;
return FALSE;
}
BITMAPFILEHEADER bfh = { 0 };
bfh.bfType = 0x4D42; // 'BM'
bfh.bfSize = sizeof(BITMAPFILEHEADER) + pbi->bmiHeader.biSize + pbi->bmiHeader.biClrUsed * sizeof(RGBQUAD) + pbi->bmiHeader.biSizeImage;
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + pbi->bmiHeader.biSize + pbi->bmiHeader.biClrUsed * sizeof(RGBQUAD);
DWORD bytesWritten;
WriteFile(fh, &bfh, sizeof(bfh), &bytesWritten, NULL);
WriteFile(fh, &pbi->bmiHeader, sizeof(BITMAPINFOHEADER), &bytesWritten, NULL);
// If there are color table entries, write them
if (pbi->bmiHeader.biClrUsed > 0) {
RGBQUAD* colorTable = reinterpret_cast<RGBQUAD*>((reinterpret_cast<char*>(pbi)) + sizeof(BITMAPINFOHEADER));
WriteFile(fh, colorTable, pbi->bmiHeader.biClrUsed * sizeof(RGBQUAD), &bytesWritten, NULL);
}
// Write the bits
WriteFile(fh, bits, pbi->bmiHeader.biSizeImage, &bytesWritten, NULL);
CloseHandle(fh);
return TRUE;
}
bool DesktopBackGround::SnipDeskTop() {
//SimulateWinD();
//Sleep(100);
HDC hScreen = GetDC(NULL);
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
// Create a device-independent bitmap
HDC hMemDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, width, height);
HGDIOBJ oldObj = SelectObject(hMemDC, hBitmap);
// Copy the screen to the bitmap
BitBlt(hMemDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
// Get the bitmap info
BITMAPINFOHEADER bi = { 0 };
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = -height; // Negative height indicates top-down DIB
bi.biPlanes = 1;
bi.biBitCount = 24; // Assuming 24-bit color
bi.biCompression = BI_RGB;
bi.biSizeImage = 0; // Calculated automatically by Windows
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
BITMAPINFO bmi = { 0 };
bmi.bmiHeader = bi;
// Allocate memory for the bitmap bits
void* bits = new char[width * height * 3];
// Get the bits from the bitmap
GetDIBits(hMemDC, hBitmap, 0, (UINT)height, bits, &bmi, DIB_RGB_COLORS);
// Save the bitmap to a file
SaveBitmapToFile(&bmi, bits, strPho.c_str());
// Clean up
delete[] bits;
SelectObject(hMemDC, oldObj);
DeleteObject(hBitmap);
DeleteDC(hMemDC);
ReleaseDC(NULL, hScreen);
return 1;
}
- 有了 .bmp 文件之后,更改桌面图标就很容易了,直接使用 API 函数 SystemParametersInfoA ,将第 0 个参数 uiAction 设置为 SPI_SETDESKWALLPAPER。
bool DesktopBackGround::ChangeDeskTop() {
if (SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, (void*)strPho.c_str(), SPIF_UPDATEINIFILE | SPIF_SENDCHANGE) == 0) {
THREE_COMBO_PRANK(SystemParametersInfoA);
return 0;
}
return 1;
}
[3] kill explorer.exe
杀死 资源管理器 很简单,只需要使用 CreateProcess 函数来创建一个进程来运行如下命令即可:
taskkill /f /im explorer.exe
3.透明窗口
既然要绘制窗口,则需要遵循 Windows 窗口程序的相应规范,在一个程序中创建窗口的消息处理队列,同时注册一个窗口处理函数来处理不同的消息。
其中最关键的由两部分组成:创建窗口;绘制窗口
- 创建窗口,最关键的是选择 窗口属性,在可选窗口 style 中也就是 CreateWindowExA 函数的第 0 个参数需要选择 WS_EX_TOOLWINDOW 来保证窗口 没有边框;选择 WS_EX_TOPMOST 使得窗口位于最顶层。第 3 个参数窗口 style 中选择 WS_POPUP 和 WS_VISIBLE 使得窗口在被创建后会立马显示。
HWND hwnd = CreateWindowExA(
WS_EX_TOOLWINDOW | WS_EX_TOPMOST, // Optional window styles.
strName.c_str(), // Window class
"Persistent Window", // Window text
WS_POPUP | WS_VISIBLE, // Window
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
- 绘制窗口,这部分的操作很简单,只需要调用 BeginPaint 和 EndPaint 两个函数就能够完成一个透明窗口的绘制。
case WM_PAINT: {
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
}
4.总结
- 背景桌面的方式由于系统的升级已经不具有通用性了,只能在 Win7 之前的版本进行使用,当然效果也挺好的。
- 挂起 explorer.exe 的方式虽然能够产生效果,但是太短暂了,会触发 explorer.exe 未响应,进而导致进程重启。
- 透明窗口,我是更推荐使用这种方式去 整活 的,不仅通用性好,也不会产生任何的破坏。