首页
论坛
课程
招聘
[原创]用户层基础且有用的三种技术
2021-11-14 21:33 12461

[原创]用户层基础且有用的三种技术

2021-11-14 21:33
12461

一.远程CMD

在Windows系统中有不少API可以用来执行CMD命令。比如:CreateProcess,WinExec,system等等。但是,这些API都没办法获取执行的结果。为了获取这些结果,就需要使用到CreatePipe函数来创建管道实现进程间的通信。

该函数的定义如下:

BOOL WINAPI CreatePipe(
  __out     PHANDLE hReadPipe,
  __out     PHANDLE hWritePipe,
  __in_opt  LPSECURITY_ATTRIBUTES lpPipeAttributes,
  __in      DWORD nSize);
参数含义
hReadFile用来接收一个可读管道数据的文件句柄
hWritePipe用来接收一个可写管道数据的文件句柄
lpPipeAttributes传入一个SECURITY_ATTRIBUTES结构的指针,该结构决定函数返回的句柄是否可由子进程继承。如果传入NULL,则返回的句柄不可继承
nSize指向管道的缓冲区大小。但只是一个理想的大小,系统会根据传入的值分配适当的值,如果传入的值是0,则系统将使用一个默认的大小

通过这个函数就可以创建一对读写管道,通过这对读写管道就可以实现进程间的通信。其中,hReadPipe代表读管道,进程可以通过这个管道来读取内容,而hWritePipe则是写管道,进程执行完CMD命令的结果是通过写入这个管道来通信的。

由于要创建子进程来执行CMD命令,然后要子进程将执行的结果写入写管道hWritePipe中,所以子进程需要拥有hWritePipe管道,这就需要创建这套读写管道的时候需要让它们的属性为可继承的。这样,创建的子进程才会拥有这对读写管道,才可以实现与父进程的通信。而要让这套读写管道可继承,就需要设置第三个参数,也就是设置lpPipeAttributes,该结构体定义如下

typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
成员含义
nLength结构体SECURITY_ATTRIBUTES结构体大小
lpSecurityDescriptor安全描述对象,置为NULL就好
bInheritHandle为TRUE,创建的读写管道可继承,否则不可继承

由此可知在创建读写管道之前,需要将lpPipeAttributes的bInheritHandle设为TRUE,以让读写管道可继承。

接下来就需要通过CreateProcess来创建子进程执行CMD命令,而在调用函数前,也需要通过设置lpStartupInfo来让子进程执行结果输出到写管道hWritePipe中。另外要注意,为了让可继承的读写管道句柄顺利继承到子进程中,使用CreateProcess函数创建子进程的时候要将第五个参数也就是bInheritHandles设为TRUE。

完成的代码如下:

#include <cstdio>
#include <windows.h>

#define CMD "ping www.baidu.com"	// 要执行的CMD命令

VOID ShowError(PCHAR msg);

int main()
{
	HANDLE hReadPipe = NULL, hWritePipe = NULL;
	STARTUPINFO si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	SECURITY_ATTRIBUTES secAttr = { 0 };
	CHAR szRes[1024] = { 0 };

	secAttr.bInheritHandle = TRUE;	// 生成的管道可继承
	secAttr.nLength = sizeof(secAttr);
	secAttr.lpSecurityDescriptor = NULL;
	if (!CreatePipe(&hReadPipe, &hWritePipe, &secAttr, 0))
	{
		ShowError("CreatePipe");
		goto exit;
	}

	si.cb = sizeof(si);
	si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	si.wShowWindow = SW_HIDE;
	// 将子进程的错误输出与正确输出设置为写管道
	si.hStdError = hWritePipe;
	si.hStdOutput = hWritePipe;

	if (!CreateProcess(NULL,
					   CMD,
		               NULL,
		               NULL,
		               TRUE,	// 让读写管道句柄继承到子进程中
					   0,
					   NULL,
					   NULL,
					   &si,
					   &pi))
	{
		ShowError("CreateProcess");
		goto exit;
	}

	// 等待子进程执行完毕
	WaitForSingleObject(pi.hProcess, INFINITE);

	// 从读管道中得到执行结果
	RtlZeroMemory(szRes, 1024);
	ReadFile(hReadPipe, szRes, 1024, NULL, NULL);

	printf("执行%s的结果:%s\n", CMD, szRes);
exit:
	system("pause");

	return 0;
}

VOID ShowError(PCHAR msg)
{
	printf("%s Error %d\n", msg, GetLastError());
}

最终可以看到CMD执行的结果被成功输出:

二.自删除技术

1.通过MoveFileEx来自删除

该函数的主要作用是对文件进行移动,定义如下

BOOL WINAPI MoveFileEx(
  __in      LPCTSTR lpExistingFileName,
  __in_opt  LPCTSTR lpNewFileName,
  __in      DWORD dwFlags);
参数含义
lpExitstingFileName指向一个存在的文件或文件夹的字符串指针
lpNewFileName指向一个不存在的文件或文件夹的字符串指针
dwFlags移动标记

当参数三,也就是dwFlags设置为MOVEFILE_DELAY_UNTIL_REBOOT且第二个参数设为NULL的时候,系统就会在重启的时候把第一个参数指定的文件删除掉,这样就可以实现重启自删除,完整代码如下:

#include <cstdio>
#include <windows.h>

#define FILE_NAME "test.exe"	// 要删除的文件

VOID ShowError(PCHAR msg);

int main()
{
	if (!MoveFileEx(FILE_NAME, NULL, MOVEFILE_DELAY_UNTIL_REBOOT))
	{
		ShowError("MoveFileEx");
	}
exit:
	system("pause");

	return 0;
}

VOID ShowError(PCHAR msg)
{
	printf("%s Error %d\n", msg, GetLastError());
}

成功运行程序以后,重启电脑就会发现文件被删掉了,但要注意需要用管理员权限来运行程序才可以。

2.通过批处理来自删除

还可以通过创建".cmd"批处理文件来实现自删除。批处理文件可以执行DOS命令,其中的del表示的就是删除命令。所以可以通过创建并执行一个批处理文件来把要删除的文件以及批处理文件删除以后就可以实现自删除,完整代码如下:

#include <cstdio>
#include <windows.h>

#define CMD_FILE_NAME "delSelf.cmd"	// 批处理文件名

VOID ShowError(PCHAR msg);

int main()
{
	DWORD dwNum = 0;
	CHAR szBat[MAX_PATH] = { 0 };	// 要执行的批处理的命令
	CHAR szFileName[MAX_PATH] = { 0 };	// 要删除的文件名
	HANDLE hFile = NULL;

	hFile = CreateFile(CMD_FILE_NAME,
		           GENERIC_WRITE,
		           0,
			   NULL,
		           CREATE_ALWAYS,
		           FILE_ATTRIBUTE_NORMAL,
			   NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		ShowError("CreateFile");
		goto exit;
	}

	GetModuleFileName(NULL, szFileName, MAX_PATH);	// 获得当前程序的文件名

	// 要执行的DOS命令
	strcpy(szBat, "del ");
	strcat(szBat, szFileName);
	strcat(szBat, "\r\n");
	strcat(szBat, "del ");
	strcat(szBat, CMD_FILE_NAME);

	if (!WriteFile(hFile, szBat, strlen(szBat) + 1, &dwNum, NULL))
	{
		ShowError("WriteFile");
		goto exit;
	}

	CloseHandle(hFile);
	// 执行批处理文件
	WinExec(CMD_FILE_NAME, SW_HIDE);
exit:
	if (hFile) CloseHandle(hFile);

	return 0;
}

VOID ShowError(PCHAR msg)
{
	printf("%s Error %d\n", msg, GetLastError());
}

只要把程序运行起来,就会发现文件被删除掉了。

三.文件监控技术

要在用户层进行文件监控,可以通过Hook相关API来实现,也可通过ReadDirectoryChangesW函数来实现。该函数可以实现对目录以及目录文件的实时监控,可以有效地发现文件被改动地情况,函数定义如下:

BOOL WINAPI ReadDirectoryChangesW(
  __in         HANDLE hDirectory,
  __out        LPVOID lpBuffer,
  __in         DWORD nBufferLength,
  __in         BOOL bWatchSubtree,
  __in         DWORD dwNotifyFilter,
  __out_opt    LPDWORD lpBytesReturned,
  __inout_opt  LPOVERLAPPED lpOverlapped,
  __in_opt     LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
参数含义
hDirectory指定要监控地目录的句柄。该句柄必须具有FILE_LIST_DIRECTORY访问权限
lpBuffer指向要读取DWORD对齐结果的格式化缓冲区的指针。该缓冲区的结构由FILE_NOTIFY_INFORMATION结构定义
nBufferLengthlpBuffer参数指向的缓冲区的大小
bWatchSubtree如果此参数为TRUE,则该函数将监视以指定目录为根的目录树。如果此参数为FALSE,则该功能仅监视由hDirectory参数指定的目录
dwNotifyFilter
指定要监控的文件操作
lpBytesReturned对于同步调用,此参数接收传输到lpBuffer参数中的字节数。对于异步调用,此参数未定义。必须使用异步通知技术来检索传输的字节数
lpOverlapped指向OVERLAPPED结构的指针,提供在异步操作期间要使用的数据。否则,此值为NULL。
lpCompletionRoutine指向完成例程的指针,当操作已经完成或取消并且调用线程处于可警告的等待状态时才会调用它

其中dwNotifyFilter用来指定要监控的文件操作,可以是以下的一个或多个:

含义

FILE_NOTIFY_CHANGE_FILE_NAME
0x00000001

监控目录或子树中的任何文件名,更改会导致更改通知等待操作的返回。更改包括重命名,创建或删除文件

FILE_NOTIFY_CHANGE_DIR_NAME
0x00000002

监视目录或子树中的任何目录名更改都会导致更改通知等待操作返回。更改包括创建或删除目录

FILE_NOTIFY_CHANGE_ATTRIBUTES
0x00000004

监视目录或子树中的任何属性更改都会导致更改通知等待操作返回

FILE_NOTIFY_CHANGE_SIZE
0x00000008

监视的目录或子树中的任何文件大小更改都会导致更改通知等待操作返回。只有当文件写入磁盘时,操作系统才会检测到文件大小的变化。对于使用广泛缓存的操作系统,只有在充分刷新缓存时才会进行检测

FILE_NOTIFY_CHANGE_LAST_WRITE
0x00000010

对监视目录或子树中文件的上次写入时间的任何更改都会导致更改通知等待操作返回。仅当文件写入磁盘时,操作系统才会检测到上次写入时间的更改。对于使用广泛缓存的操作系统,只有在充分刷新缓存时才会进行检测

FILE_NOTIFY_CHANGE_LAST_ACCESS
0x00000020

对监视目录或子树中文件的上次访问时间的任何更改都会导致更改通知等待操作返回

FILE_NOTIFY_CHANGE_CREATION
0x00000040

对监视目录或子树中的文件创建时间的任何更改都会导致更改通知等待操作返回

FILE_NOTIFY_CHANGE_SECURITY
0x00000100

监视的目录或子树中的任何安全描述符更改都会导致更改通知等待操作返回

通过该值的指定,函数将不同的数据返回到lpBuffer中,该指针将会指向FILE_NOTIFY_INFORMATION结构体,该结构体的定义如下

typedef struct _FILE_NOTIFY_INFORMATION {
    DWORD NextEntryOffset;
    DWORD Action;
    DWORD FileNameLength;
    WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;

其中的Action表示监控到对目录的操作,FileNameLength和FileName则用来保存被操作的文件名或目录的路径。

而Action的取值可以为以下这些:

#define FILE_ACTION_ADDED                   0x00000001   
#define FILE_ACTION_REMOVED                 0x00000002   
#define FILE_ACTION_MODIFIED                0x00000003   
#define FILE_ACTION_RENAMED_OLD_NAME        0x00000004   
#define FILE_ACTION_RENAMED_NEW_NAME        0x00000005

根据这些宏定义名字就不难知道它们的含义,这样就获得对文件的操作类型。

由于监控开启以后会无限循环,为了避免卡死需要开起一个新线程来执行监控代码。

完成的文件监控代码如下:

#include <cstdio>
#include <windows.h>

#define DIRECTORY_NAME "C:\\Users\\1900\\Desktop\\test\\"	// 要监控的目录完整路径

VOID ShowError(PCHAR msg);
DWORD WINAPI ThreadProc(LPVOID lpParameter);

int main()
{
	HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

	if (!hThread)
	{
		ShowError("CreateThread");
		goto exit;
	} 

	printf("%s监控开启\n", DIRECTORY_NAME);
	CloseHandle(hThread);
exit:
	system("pause");

	return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	HANDLE hDirectory = NULL;
	DWORD dwRet = 0;
	PFILE_NOTIFY_INFORMATION pBuffer = NULL;	// 指向保存查询结果的字符串
	DWORD dwBufferSize = 0x400;
	BOOL bRet = FALSE;

	hDirectory = CreateFile(DIRECTORY_NAME,
							FILE_LIST_DIRECTORY,
							FILE_SHARE_READ | FILE_SHARE_WRITE,
							NULL,
							OPEN_EXISTING,
							FILE_FLAG_BACKUP_SEMANTICS,
							NULL);
	if (hDirectory == INVALID_HANDLE_VALUE)
	{
		ShowError("CreateFile");
		goto exit;
	}

	pBuffer = (PFILE_NOTIFY_INFORMATION)malloc(dwBufferSize);
	if (!pBuffer)
	{
		ShowError("malloc");
		goto exit;
	}

	do {
		RtlZeroMemory(pBuffer, dwBufferSize);

		bRet = ReadDirectoryChangesW(hDirectory,
									 pBuffer,
									 dwBufferSize,
									 TRUE,
									 FILE_NOTIFY_CHANGE_FILE_NAME,	// 对文件的创建修改删除进行监控
									 &dwRet,
									 NULL,
									 NULL);
		if (!bRet)
		{
			ShowError("ReadDirectoryChangesW");
			break;
		}

		switch (pBuffer->Action)
		{
			case FILE_ACTION_ADDED:
			{
				printf("增加了文件:%ws\n", pBuffer->FileName);
				break;
			}
			case FILE_ACTION_REMOVED:
			{
				printf("删除了文件:%ws\n", pBuffer->FileName);
			}
		}
	} while (bRet);
exit:
	return 0;
}

VOID ShowError(PCHAR msg)
{
	printf("%s Error %d\n", msg, GetLastError());
}

可以看到,对test文件夹的操作会被监控打印出来




【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 2021-11-30 09:56 被1900编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回