Windows编程&各API收录

作者: const27 分类: All,Windows相关,开发-C 发布时间: 2020-10-24 06:38

说白了,就是以C或c++等语言为载体,调用各种Windows API完成编程
学这个的目的是因为要接触免杀,需要更深层次的了解Windows的一些机制。

HelloWorld

#include<Windows.h>

int WINAPI WinMain(HINSTANCE histance, HINSTANCE hPrevInStance, LPSTR lpCmdLine, INT nShowCmd) {
	MessageBox(NULL, TEXT("HelloWorld"), TEXT("Title"), 0);
	return 0;
}

下面详细分析分析这个HelloWorld

头文件

Windows编程使用windows.h头文件,调用里面的诸多windows API

函数

可以看见Windows编程的入口函数是WinMain。
那么WinMain前的WINAPI是一个宏定义
我们查看它的定义

可以发现WINAPI其实就是__stdcall,__stdcall表示的是一种调用的约定,使编译器以windows兼容的方式来产生机器指令。此外,我们在图中可以看到CALLBACK,APIPRIVATE等具有和WINAPI相同的宏定义,因此,可以使用它们代替WINAPI。

另外,函数的四个参数先不慌提及。

MessageBox函数

用于创建Windows Gui窗口。

其第一个参数用于指定父窗口,没有父窗口直接填NULL即可
第二个参数表示消息框文本内容,实质上是指向字符串的指针
第三个参数与第二个参数同理,只向表示消息框标题的字符串指针
第四个参数表示 窗口的样式和显示图标的类型。

窗口实现

Windows里各个应用其实都是依靠着窗窗口实现的,比如chrome,计算器等等窗口。
窗口不同于刚刚说到的消息框,你可以认为消息框是窗体的一部分。
下面我们来演示一下如何实现一个窗口。
窗口实现有如下流程
设计窗口类、注册窗口类、创建窗口、显示窗口、启动消息循环泵循环获取消息分发到窗体过程函数处理
以下代码在完整代码出现时均可能出现细节上的错误,但思想没毛病

设计窗口

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInStance, LPSTR lpCmdLine, INT nShowCmd) {
	static TCHAR szAPPName[] = TEXT("MyWindow");
	HWND hwnd;  #窗口的句柄
	MSG msg;    #消息(后面深讲
	WNDCLASSEX wndclass; #窗口类名
	wndclass.cbSize = sizeof(wndclass);  #设置整个结构体的字节大小,这里我们对结构体使用sizeof即可得到结构体字节大小
	wndclass.style = CS_HREDRAW | CS_VREDRAW; #设置窗口的各种风格,比如这里设置的就是改变了窗口的高度或宽度,窗口就会刷新重绘
	wndclass.lpfnWndProc = WndProc; #指向窗口过程函数
	wndclass.cbClsExtra = 0; #
	wndclass.cbWndExtra = 0; #这两个值默认为0
	wndclass.hInstance = hinstance; #指向WinMain第一个参数的值
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); #设置光标,这里选用默认光标
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); #设置图标,这里选用默认图标
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);#设置窗口背景色,这里使用白色
	wndclass.lpszMenuName = NULL;  #菜单选项,不加菜单就设为NULL
	wndclass.lpszClassName = szAPPName;  #指向窗口类的名字,即上面的szAPPName
}

通过对WNDCLASSEX的类进行属性修改,即可达到设计窗口的目的。

注册窗口&实现窗口

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInStance, LPSTR lpCmdLine, INT nShowCmd) {
	static TCHAR szAPPName[] = TEXT("MyWindow");
	HWND hwnd;
	MSG msg;
	WNDCLASSEX wndclass;
	wndclass.cbSize = sizeof(wndclass);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hinstance;
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAPPName;
\\调用RegisterClassEx并指向wndclass的地址,使其注册
	if (!RegisterClassEx(&wndclass))
	{
		MessageBox(NULL, TEXT("Only Windows NT!"), szAppName, MB_ICONERROR); #在非WINDOWS NT 系统,注册窗口会返回NULL
		return 0;
	}

}
\\窗口创建:详细定义窗口
	hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
		szAPPName,
		TEXT("窗口名称"),
		WS_OVERLAPPEDWINDOW, #一种窗口风格:重叠窗口
		CW_USEDEFAULT,       #设置为默认位置
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hinstance,
		NULL);

窗口创建时的详细参数内容见下表

窗口显示& 启动消息循环泵循环获取消息分配到窗体过程函数处理

int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInStance, LPSTR lpCmdLine, INT nShowCmd) {
	static TCHAR szAPPName[] = TEXT("MyWindow");
	HWND hwnd;
	MSG msg;
	WNDCLASSEX wndclass;
	wndclass.cbSize = sizeof(wndclass);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hinstance;
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAPPName;

	if (!RegisterClassEx(&wndclass))
	{
		MessageBox(NULL, TEXT("RegisterClassEx failed!"), szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindowEx(
		szAppName,
		TEXT("窗口名称"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hinstance,
		NULL);

	ShowWindow(hwnd, nShowCmd);
	UpdateWindow(hwnd);
\\启动消息循环泵循环获取消息分配到窗体过程函数处理 
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);  #把消息转换为字符型
		DispatchMessage(&msg);  #把消息传给窗口过程处理
	}
	return msg.wParam;
}

提到消息了,就不得不说Windows的消息机制了-这是Windows里非常核心的一个机制.这里简单提提吧。

Windows 消息机制

消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。
http://www.const27.com/2020/10/25/windows%e6%b6%88%e6%81%af%e6%9c%ba%e5%88%b6/

窗口过程

是窗口处理各种消息的部分,一般由窗口类的lpfnWndProc属性指定的函数来实现

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	switch (message)
	{
	case WM_PAINT:   #处理各个消息
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		DrawText(hdc, TEXT("FUCK"), -1, &rect, DT_CENTER);
		EndPaint(hwnd, &ps);
		return 0;

	case WM_LBUTTONUP:
		MessageBox(NULL, TEXT("老子被点了"), TEXT("tick"), 0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);  #其他未被特别指定的消息会被默认处理
}

总览

#include<Windows.h>


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);


int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInStance, LPSTR lpCmdLine, int nShowCmd) {

	static TCHAR szAppName[] = TEXT("窗口类名称");
	HWND         hwnd;
	MSG          msg;
	WNDCLASSEX   wndclass = { 0 };

	//设计窗口类
	wndclass.cbSize = sizeof(WNDCLASSEX);
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hinstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szAppName;


	if (!RegisterClassEx(&wndclass))
	{
		MessageBox(NULL, TEXT("RegisterClassEx failed!"),TEXT("title"), MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
		szAppName,
		TEXT("窗口名称"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hinstance,
		NULL);

	ShowWindow(hwnd, nShowCmd);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, hwnd, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		DrawText(hdc, TEXT("FUCK"), -1, &rect, DT_CENTER);
		EndPaint(hwnd, &ps);
		return 0;

	case WM_LBUTTONUP:
		MessageBox(NULL, TEXT("老子被点了"), TEXT("tick"), 0);
		return 0;
	}

	return DefWindowProc(hwnd, message, wParam, lParam);
}

API:收录一下调过的API

获得文件句柄:GetMoudleHandleA

HMODULE GetModuleHandleA(
  LPCSTR lpModuleName
);

lpModuleName即要获取的文件句柄的文件名,可为exe或dll。
若文件名没有后缀名则默认视为dll文件。
若该项为NULL,则返回当前进程的文件句柄。
存在于Kerner32.dll中
这里介绍一些HMOUDLE是个什么玩意
一般就是一个线性地址,用于记录一个文件句柄的地址。

从dll文件句柄中获得函数:GetProcAddress

FARPROC GetProcAddress(
  HMODULE hModule,
  LPCSTR  lpProcName
);

hmodule 表示要传入的dll文件句柄。这个句柄可以由  LoadLibraryLoadLibraryExLoadPackagedLibrary, or GetModuleHandle 等方法得到。

lpProcName表示要从dll文件中获得的函数没或者变量名。
若函数执行不成功,则返回NULL,否则返回函数或者变量的地址

打开一个已存在的本地进程:OpenProcess

HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);

dwdesiredaccess 指定能获得指定进程哪些权限。可选值如下

PROCESS_ALL_ACCESS  //所有能获得的权限
PROCESS_CREATE_PROCESS  //需要创建一个进程
PROCESS_CREATE_THREAD   //需要创建一个线程
PROCESS_DUP_HANDLE      //重复使用DuplicateHandle句柄
PROCESS_QUERY_INFORMATION   //获得进程信息的权限,如它的退出代码、优先级
PROCESS_QUERY_LIMITED_INFORMATION  /*获得某些信息的权限,如果获得了PROCESS_QUERY_INFORMATION,也拥有PROCESS_QUERY_LIMITED_INFORMATION权限*/
PROCESS_SET_INFORMATION    //设置某些信息的权限,如进程优先级
PROCESS_SET_QUOTA          //设置内存限制的权限,使用SetProcessWorkingSetSize
PROCESS_SUSPEND_RESUME     //暂停或恢复进程的权限
PROCESS_TERMINATE          //终止一个进程的权限,使用TerminateProcess
PROCESS_VM_OPERATION       //操作进程内存空间的权限(可用VirtualProtectEx和WriteProcessMemory) 
PROCESS_VM_READ            //读取进程内存空间的权限,可使用ReadProcessMemory
PROCESS_VM_WRITE           //读取进程内存空间的权限,可使用WriteProcessMemory
SYNCHRONIZE                //等待进程终止

bInheritHandle 若为TRUE,则表示所得到的进程句柄可被进程
dwProcessId 表示要获取的进程的PID

函数成功执行,则返回指定进程的句柄。反之则NULL。

在指定进程的虚拟地址空间中保留,开辟,禁用一段区域 : VirtualAllocEx 

LPVOID VirtualAllocEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);

hProcess 指定的进程的句柄。该句柄需有PROCESS_VM_OPERATION权限(操作进程内存空间的权限)

lpAddress 一个指针,用于选择你想分配的内存的开始地址。如果填NULL,则由dwsize的设置来自动分配。

dwSize 欲分配的内存大小(字节单位)。实际分配的大小是该值与页内存对齐后的结果。如果lpAddress为NULL,则会选中从进程首地址到dwSize的页对齐后的内存区域
如果lpAddress不为NULL,则选定lpaddress到lpaddress+dwsize的按照页对齐后的内存区域。

flAllocationType 内存分配的类型。有很多值,MSDN上有记录。
https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
这个值用来确定一段内存区域用于被置0(commit)还是保留(reserve)还是禁用(reset)

flProtect 内存保护常数

PAGE_NOACCESS
PAGE_GUARD
PAGE_NOCACHE
PAGE_WRITECOMBINE

若函数执行成功,则返回分配的内存地址的基地址。

在指定进程的虚拟地址空间中释放或decommit一段区域 : VirtualFreeEx 

BOOL VirtualFreeEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  dwFreeType
);

hprocess 进程句柄,需有PROCESS_VM_OPERATION权限

lpaddress 一个指向需要被释放或decommit内存区域首地址的指针。若dwfreetype为mem_release(释放),则这里应该填入VirtualAllocEx方法返回的保留的内存区域的基地址。

dwsize 需要被decommit的内存大小。
若dwfreetype为MEM_RELEASE 则此处填0
若为MEM_DECOMMIT,则此处填内存大小。 选定lpaddress到lpaddress+dwsize的按照页对齐后的内存区域。

dwfreetype 需要对内存区域进行的操作。有MEM_DECOMMIT和MEM_RELEASE,更多参数参考MSDNhttps://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfreeex

若函数执行成功则返回一个非零值,不成功则返回0

向指定进程的内存区域写入: WriteProcessMemory 

BOOL WriteProcessMemory(
  HANDLE  hProcess,
  LPVOID  lpBaseAddress,
  LPCVOID lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesWritten
);

hprocess 进程句柄

lpbaseaddress 需要写入的内存区域的起始地址指针

lpbuffer 指向缓冲区的指针,该缓冲区包含要在指定进程的地址空间中写入的数据。

nsize 需要写入的数据大小(字节单位)

lpNumberOfBytesWritten 可选,用来存放”要被写入的数据“的变量

若成功则返回非0值,反之则0

关闭一个句柄:CloseHandle

BOOL CloseHandle(
  HANDLE hObject
);

hobject 要被关闭的句柄

成功则返回非0值,反之则0

创建命名管道句柄:CreateNamedPipeA

HANDLE CreateNamedPipeA(
  LPCSTR                lpName,
  DWORD                 dwOpenMode,
  DWORD                 dwPipeMode,
  DWORD                 nMaxInstances,
  DWORD                 nOutBufferSize,
  DWORD                 nInBufferSize,
  DWORD                 nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

lpName 唯一的管道名。格式是固定的: \\.\pipe\pipename

dwOpenMode 管道的开启方式。有三种:1.数据在服务器和客户机双向流通 PIPE_ACCESS_DUPLEX。2.数据只能从服务器流向客户机 PIPE_ACCESS_OUTBOUND。3.数据只能从客户机流向服务器 PIPE_ACCESS_INBOUND。具体请参考官方文档。以及一些附加参数,详情参考官方文档 https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea

dwPipeMode 管道通信方式。指定字节流或者消息流通信,指定远程链接的访问方式,指定等待方式,详情参考官方文档

nMaxInstancees 管道的最大实例数

nOutBufferSize&nInBufferSize 为输出&输入缓存区保留的内存字节大小

nDefaultTimeOut 设定超时值。置0则为50ms

lpSecurityAttributes 设置安全描述符

若函数执行成功则返回管道服务器句柄。反之则返回INVALID_HANDLE_VALUE

开启一个命名管道等待链接: ConnectNamedPipe 

BOOL ConnectNamedPipe(
  HANDLE       hNamedPipe,
  LPOVERLAPPED lpOverlapped
);

hNamedPipe 指定一个命名管道句柄,该句柄由CreateNamedPipe函数返回

lpOverlapped 一个指向重叠结构的指针,一般为NULL。详情参考官方文档
https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-connectnamedpipe

一般来说,当由客户机连上时返回true,没有客户机链接或链接丢失返回false

模拟一个命名管道客户端:ImpersonateNamedPipeClient

BOOL ImpersonateNamedPipeClient(
  HANDLE hNamedPipe
);

hNamedPipe 即指定的命名管道服务端。

这个函数有个坑点是,服务端才能用这个函数,且必须在服务端读取客户端传来的数据后才能成功,否则就会返回1368错误。

获取当前线程令牌句柄: GetCurrentThreadToken

HANDLE GetCurrentThreadToken();

无参数,直接返回当前线程虚拟句柄

以令牌创建一个进程: CreateProcessWithTokenW

BOOL CreateProcessWithTokenW(
  HANDLE                hToken,
  DWORD                 dwLogonFlags,
  LPCWSTR               lpApplicationName,
  LPWSTR                lpCommandLine,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCWSTR               lpCurrentDirectory,
  LPSTARTUPINFOW        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

有点不懂,链接在这里https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw

hToken 表示用户的令牌 必须有 TOKEN_QUERY, TOKEN_DUPLICATE, and TOKEN_ASSIGN_PRIMARY 权限

dwLogonFlags 登陆选项。LOGON_WITH_PROFILELOGON_NETCREDENTIALS_ONLY

lpApplicationName 要被执行的模块。(比如 C:\\Windows\\system32\\notepad.exe )

lpCommandLine 要被执行的命令。如果为NULL则把 lpApplicationName 当作要执行的命令

dwCreationFlags 控制进程的创建方式。参考MSDN

lpEnvironment 指向新进程环境块的指针。

lpCurrentDirectory 指向进程的“当前目录”路径。若NULL则“当前目录”为调用程序的“当前目录”

lpStartUpInfo 指向  STARTUPINFOSTARTUPINFOEX 结构体

lpProcessInformation 指向  PROCESS_INFORMATION 结果的指针

文件IO

获得一个已存在文件句柄或者新建文件句柄:CreateFile

HANDLE CreateFileW(
  LPCWSTR               lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

lpFileName 文件路径

dwDesiredAccess 对文件的访问控制:读,写。GENERIC_READGENERIC_WRITE, or both (GENERIC_READ | GENERIC_WRITE)

dwShareMode 零表示不共享; FILE_SHARE_READ 和 / 或 FILE_SHARE_WRITE 表示允许对文件进行共享访问

lpSecurityAttributes 安全描述符

dwCreationDisposition 在文件不存在或存在时采取的操作

dwFlagsAndAttributes 设置文件标志位

hTemplateFile 多数情况下为NULL

写文件: ReadFile

BOOL WriteFile(
  HANDLE       hFile,
  LPCVOID      lpBuffer,
  DWORD        nNumberOfBytesToWrite,
  LPDWORD      lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);

hFile 文件句柄

lpBuffer 要写入的数据缓存区

nNumberOfBytesToWrite 要写入的字节数

lpNumberOfBytesWritten 设置一个变量,用来接收写入的内容

lpOverlapped 通常为NULL

读文件: ReadFile 

BOOL ReadFile(
  HANDLE       hFile,
  LPVOID       lpBuffer,
  DWORD        nNumberOfBytesToRead,
  LPDWORD      lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);

和WriteFile差不多,8说了

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

Leave a Reply

Your email address will not be published. Required fields are marked *

标签云