目录
进程A拿到进程B句柄是否能用
句柄的权限
关于句柄表
跨进程使用句柄-继承
CreateProcess:bInheritHandles
OpenProcess
FindWinodw
GetCurrentProcess
跨进程使用句柄-拷贝
跨进程操作内存
WriteProcessMemory
VirtualProtectEx
ReadProcessMemory
进程A拿到进程B句柄是否能用
创建两个基于对话框的MFC分别为A,B
MFC A
ZeroMemory(&si, sizeof(si)); 是一段用于清空内存的代码,它使用了Windows操作系统提供的ZeroMemory函数。
该函数接受两个参数:第一个参数是指向要清空的内存块的指针,第二个参数是要清空的内存块的大小(以字节为单位)。
在这段代码中,&si 是指向变量 si 的指针,sizeof(si) 则是获取变量 si 所占用的内存块的大小。ZeroMemory函数将会将此内存块中的所有字节都设置为0。
void CADlg::OnBnClickedButton1()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line).
"B.exe", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
AfxMessageBox("CreateProcess failed.");
}
//显示句柄值
SetDlgItemInt(EDT_HANDLE, (UINT)pi.hProcess);
}
MFC B
void CBDlg::OnBnClickedButton1()
{
HANDLE hProc = (HANDLE)GetDlgItemInt(EDT_HANDLE);
::TerminateProcess(hProc, 0);
}
修改输出目录
输出目录如下:
经过实验后:进程B是无法结束进程A的
剖析:一个进程,它所打开的句柄(或者说它所获得的句柄),进程都会把句柄存起来,这样就会形成一个表记录进程拿到哪些句柄;这个表叫做句柄表;
在B进程的句柄表中,并没有自己的进程的句柄,所以就算手动把A进程的句柄给B进程;让B进程结束——B进程表示在自己的句柄表中找不到自己的进程的句柄,所以报错,传入的句柄是无效句柄;
句柄的权限
句柄的权限是指操作系统对于句柄所代表的对象所授予的操作权限。不同类型的对象(如文件、进程、线程等)具有不同的权限集合。以下是一些常见的句柄权限:
- 文件权限:
- FILE_READ_DATA:允许读取文件内容。
- FILE_WRITE_DATA:允许写入文件内容。
- FILE_APPEND_DATA:允许在文件末尾追加数据。
- FILE_EXECUTE:允许执行文件。
- FILE_DELETE:允许删除文件。
- FILE_READ_ATTRIBUTES:允许读取文件属性。
- FILE_WRITE_ATTRIBUTES:允许修改文件属性。
- 进程和线程权限:
- PROCESS_CREATE_PROCESS:允许创建子进程。
- PROCESS_TERMINATE:允许终止进程。
- PROCESS_QUERY_INFORMATION:允许查询进程信息。
- THREAD_CREATE_THREAD:允许创建线程。
- THREAD_TERMINATE:允许终止线程。
- THREAD_QUERY_INFORMATION:允许查询线程信息。
- 窗口权限:
- GWL_STYLE:允许设置窗口样式。
- GWL_EXSTYLE:允许设置窗口扩展样式。
- WM_CLOSE:允许关闭窗口。
- WM_DESTROY:允许销毁窗口。
这些仅是一些常见的句柄权限示例,实际上句柄的权限取决于所代表对象的类型和操作系统的安全策略。在使用句柄进行操作时,需要根据具体的需求和操作对象的类型来确定所需的权限,以确保在合法范围内进行操作。
关于句柄表
Windows操作系统中的句柄表通常被划分为以下三层:
- 用户层:用户层是最高层,包含了应用程序和操作系统之间的交互接口。在用户层,开发人员可以使用操作系统提供的API函数来创建和操作各种内核对象,如文件、进程、线程、窗口、消息等。
- 内核层:内核层是操作系统的核心,包括了内核、设备驱动程序等。在内核层,操作系统可以直接访问硬件资源,提供更加底层的操作接口。
- 硬件层:硬件层包含了操作系统所运行的计算机的物理硬件设备,如CPU、内存、硬盘、网络设备等。
句柄表通常被放置在内核层,用于管理应用程序和内核对象之间的关系。操作系统为每个进程维护一个独立的句柄表,该句柄表包含了该进程所拥有的句柄。在内核层,操作系统使用句柄来标识和访问内核对象。句柄通常被视为指向内核对象的指针,开发人员可以使用操作系统提供的API函数来获取、创建、操作句柄,并使用句柄来操作内核对象。
父进程和子进程:(A创建的进程都是A的子进程)
父进程和子进程从使用的角度讲,没有什么非常特殊操作的关系,是两个单独的进程,各有4g内存,各自有各自的线程,堆和栈,是独立的;使用的过程中,特别:进程句柄的时候用到父子进程,让项目的结构方便一点,
子进程有父进程的进程id,系统管理进程是由结构体管理的,由表管理,在内核里面,子进程的内核存有父进程的id信息;
跨进程使用句柄-继承
继承父进程的条件:
句柄本身可以被继承,CreateProcess的bInHeritHandle为TRUE。
子进程只能继承在自身被创建之前父进程打开的句柄,自身创建后父进程打开的句柄无法继承。
也就是说,CreateProcess创建进程B,进程B不能继承自身的句柄,自身句柄创建完之后才能拿到。解决办法是:
- 获取自己进程的句柄:GetCurrentProcess。返回值为-1,是个伪句柄,该句柄用于操作自身。
- 句柄本身也是带有私有公有属性的,和C++的继承很像,所以句柄都有一个是否可被继承的属性,这个属性由OpenProcess函数决定
CreateProcess:bInheritHandles
新进程是否继承来自父进程的句柄。TRUE则继承。
子进程继承父进程已经打开了的句柄,只能在父子进程之间使用,CreateProcess中的第五个参数可以设置继承关系,TRUE就是可以被子进程继承,FALSE就不会被继承:
CreateProcess:参数三四安全属性指明创建出的子进程的进程句柄和线程句柄能否被继承
安全描述符是一个结构体SECURITY_ATTRIBUTES;定义如下
第一个参数是长度大小,第二个参数一般填NULL,第三个成员决定是否被继承
OpenProcess
OpenProcess函数将打开指定PID的进程,并返回一个与该进程关联的句柄。如果成功,返回的句柄可用于后续的操作,如读取或写入进程的内存、终止进程等。如果操作失败,返回NULL或INVALID_HANDLE_VALUE。
1 // 作用:打开一个存在的进程对象。(获取进程句柄)
2 // 返回值:成功返回进程句柄,失败返回NULL。
3 HANDLE OpenProcess(
4 DWORD dwDesiredAccess, // 权限标志,一般填PROCESS_ALL_ACCESS通杀
5 BOOL bInheritHandle, // OpenProcess打开的句柄能否被子进程继承
6 DWORD dwProcessId // 进程ID
7 );
OpenProcess函数是Windows操作系统提供的函数之一,用于打开一个已存在的进程并返回一个与该进程关联的句柄。它的参数如下:
-
dwDesiredAccess:指定打开进程的访问权限,即访问级别。可以使用以下常量进行设置:
- PROCESS_ALL_ACCESS:具有完全访问权限的句柄,可以执行任意操作。
- PROCESS_CREATE_PROCESS:允许创建进程。
- PROCESS_CREATE_THREAD:允许创建线程。
- PROCESS_DUP_HANDLE:允许复制句柄。
- PROCESS_QUERY_INFORMATION:允许查询进程信息。
- PROCESS_QUERY_LIMITED_INFORMATION:允许有限查询进程信息。
- PROCESS_SET_INFORMATION:允许设置进程信息。
- PROCESS_SET_QUOTA:允许设置进程配额。
- PROCESS_SUSPEND_RESUME:允许挂起或恢复进程。
- PROCESS_TERMINATE:允许终止进程。
- PROCESS_VM_OPERATION:允许对进程进行虚拟内存操作。
- PROCESS_VM_READ:允许读取进程的虚拟内存。
- PROCESS_VM_WRITE:允许写入进程的虚拟内存。
-
bInheritHandle:指定打开的句柄是否可被子进程继承。如果为TRUE,则可被子进程继承;如果为FALSE,则不能被子进程继承。
-
dwProcessId:要打开的进程的标识符(PID)。可以通过其他函数(如EnumProcesses)获取进程的PID,或者使用特定的值来表示特定的进程,如GetCurrentProcessId()表示当前进程的PID。
FindWinodw
FindWindow函数是Windows操作系统提供的函数之一,用于查找具有指定类名和窗口名的顶级窗口。它的参数如下:
-
lpClassName:指定要查找的窗口类名。可以是一个字符串指针,指向类名的字符串,也可以是预定义的常量,如"Button"、"Edit"等。如果想要查找所有窗口,请将该参数设置为NULL。
-
lpWindowName:指定要查找的窗口名。可以是一个字符串指针,指向窗口名的字符串。如果想要查找具有指定类名但没有特定窗口名的窗口,请将该参数设置为NULL。
FindWindow函数将根据提供的类名和窗口名在系统中查找匹配的顶级窗口。如果找到匹配的窗口,将返回该窗口的句柄(HWND)。否则,返回NULL。
// 作用:获取窗口句柄。
// 返回值:成功返回窗口句柄,失败返回NULL。
// 备注:参数二填一即可,另一个写NULL。
HWND FindWindow(
LPCTSTR lpClassName, // 类名
LPCTSTR lpWindowName // 窗口名
);
GetCurrentProcess
// 作用:获取自身进程句柄。
// 返回值:恒为‐1。对任何进程而言,‐1代表自身进程的句柄,‐1是个伪句柄。
HANDLE GetCurrentProcess(void);
测试代码
一个设置句柄权限包括是否可被继承,一个设置可被子进程继承。
//使用继承跨进程使用句柄
void CADlg::OnBnClickedButton1()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE; //允许进程句柄被子进程继承(赋予保护/公有属性)
sa.lpSecurityDescriptor = NULL;
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line).
"B.exe", // Command line.
&sa, // 允许此句柄被继承
NULL, // Thread handle not inheritable.
TRUE, // 允许子进程继承句柄
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
AfxMessageBox("CreateProcess failed.");
}
//显示句柄值
SetDlgItemInt(EDT_HANDLE, (UINT)pi.hProcess);
}
跨进程使用句柄-拷贝
把句柄拷贝到别的进程,有没有父子关系都无所谓,从自己的句柄表里拷贝到对方的句柄表里,注意是拷贝一个句柄而不是一个句柄表,继承才是把句柄表打包一份。说是拷贝实际上对方是重新获取了。
拷贝函数DuplicateHandle:
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle, // 源进程句柄
HANDLE hSourceHandle, // 源句柄
HANDLE hTargetProcessHandle, // 目标进程句柄
LPHANDLE lpTargetHandle, // 目标句柄
DWORD dwDesiredAccess, // 访问权限
BOOL bInheritHandle, // 是否可被继承
DWORD dwOptions // 选项
);
参数说明:
- hSourceProcessHandle:源进程的句柄,即拥有要复制句柄的进程。
- hSourceHandle:要复制的句柄,即源句柄。
- hTargetProcessHandle:目标进程的句柄,即要将复制的句柄关联到的进程。
- lpTargetHandle:指向目标句柄的指针,用于接收复制后的句柄。
当调用DuplicateHandle函数时,有三个参数需要指定具体的取值:
- dwDesiredAccess:表示复制后句柄的访问权限。可以使用以下访问权限常量进行设置,也可以通过逻辑或运算符(|)组合多个权限:
- GENERIC_READ:允许对对象进行读取操作。
- GENERIC_WRITE:允许对对象进行写入操作。
- GENERIC_EXECUTE:允许对对象进行执行操作。
- GENERIC_ALL:允许对对象进行所有操作。
此外,还可以使用特定对象类型的访问权限常量,例如FILE_READ_DATA、FILE_WRITE_DATA等。具体取值取决于复制的句柄所代表的对象类型。
-
bInheritHandle:表示目标句柄是否可被子进程继承。如果值为TRUE,则子进程可以继承目标句柄;如果值为FALSE,则子进程不会继承目标句柄。
-
dwOptions:表示一些额外的选项。可以使用以下常量进行设置:
- 0:没有额外的选项。
- DUPLICATE_SAME_ACCESS:复制后的句柄将具有与源句柄相同的访问权限。
使用DUPLICATE_SAME_ACCESS选项时,dwDesiredAccess参数中的访问权限可以省略,复制后的句柄将具有与源句柄相同的访问权限。
适用场景:跨权限操作一些东西。比如有system权限的进程拿到句柄给管理员权限的进程用。
伪句柄:把进程自身-1的句柄值拷贝给自己,可以获取进程自身真正的句柄值。
此时 -1 对应的就是当前获取的句柄,它操作的是它自己,这就是伪句柄。
void CADlg::OnBnClickedButton1()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line).
"B.exe", // Command line.
NULL, // 不允许此句柄被继承
NULL, // Thread handle not inheritable.
TRUE, // 不允许子进程继承句柄
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
AfxMessageBox("CreateProcess failed.");
}
HANDLE hProcInDst = NULL;
BOOL fSuccess = DuplicateHandle(
GetCurrentProcess(),
pi.hProcess, //被拷贝的句柄
pi.hProcess, //拷贝到B进程
&hProcInDst,
0,
FALSE,
DUPLICATE_SAME_ACCESS
);
if (!fSuccess)
AfxMessageBox("DuplicateHandle failed");
//显示句柄值
SetDlgItemInt(EDT_HANDLE, (UINT)hProcInDst);
}
句柄表里没有自身进程的记录
跨进程操作内存
跨进程读写内存的办法
1. Winhex手动修改进程内存:open Memory - 目标进程 - 修改完保存(写入进程内存)。 | ||
2. API修改:跨进程写内存 - WriteProcessMemory。内存属性不可写会写入失败。 | ||
3. 跨进程读内存 - ReadProcessMemory。 |
内存属性
1. 内存访问属性:R读,W写,X执行,C写时拷贝。修改内存访问属性:VirtualProtectEx。 | ||
2. ProcessHacker看内存属性:exe双击 - Memory - Protection。 | ||
3. WriteProcessMemory不用判断内存属性,每次写之前修改内存属性,写完后还原内存属性。 | ||
4. 写完数据不还原属性:会被检测(向只读内存写入数据,看是否触发异常,触发则表明正常) | ||
PS:内存的基本单位4k【一个分页0x1000】,所以当修改0x1225的内存属性实则修改了0x0 ~ 0x2048的内存属性 |
内存分页
1. 内存分页:大小0x1000(4096),4kb。系统管理内存的基本单位。属性改一字节影响一个分页。 | ||
2. 申请内存时,系统至少分配一个分页,一个字节也分配一个分页。 | ||
3. new和malloc是在系统分配的基础上再次分配,在系统分配的分页中再次分配需要的字节。 | ||
再次以内存属性来看常量区等可读不可写区域 | ||
操作系统喜欢将统一权限属性的放在一起的原因是方便管理,且不浪费内存,也更容易维护。 | ||
WriteProcessMemory
1 // 作用:将数据写入指定进程内存。
2 BOOL WriteProcessMemory(
3 HANDLE hProcess, // 目标进程句柄
4 LPVOID lpBaseAddress, // 需要修改目标进程的地址
5 LPCVOID lpBuffer, // 写入数据缓冲区
6 SIZE_T nSize, // 写入数据缓冲区的大小
7 SIZE_T * lpNumberOfBytesWritten // 传出参数(可选),写入成功的数据大小,不
需要可以填NULL
8 );
VirtualProtectEx
// 作用:修改内存的访问属性。
BOOL VirtualProtectEx(
HANDLE hProcess, // 目标进程句柄
LPVOID lpAddress, // 修改属性的内存地址
SIZE_T dwSize, // 修改属性的内存大小
DWORD flNewProtect, // 修改后的内存访问属性
PDWORD lpflOldProtect // 传出参数,修改前的内存访问属性,填NULL会调用失败
);
参数3:修改属性的内存大小
修改内存属性会影响到这一段地址空间涉及到的所有内存分页。
跨越页面边界的2字节范围会导致两个页面的保护属性都被更改。
参数4:内存属性
PAGE_READONLY 只读
PAGE_READWRITE 可读可写
PAGE_WRITECOPY 写时拷贝
PAGE_EXECUTE 可执行
PAGE_EXECUTE_READ 可执行可读
PAGE_EXECUTE_READWRITE 可执行可读写
PAGE_EXECUTE_WRITECOPY 可执行可写时拷贝
ReadProcessMemory
BOOL ReadProcessMemory(
HANDLE hProcess, // handle to the process
LPCVOID lpBaseAddress, // base of memory area
LPVOID lpBuffer, // data buffer
SIZE_T nSize, // number of bytes to read
SIZE_T * lpNumberOfBytesRead // number of bytes read);
飞机躲子弹-无敌模式
1.编写飞机躲子弹工具
0x00406D6C 4 nplanX, 当前飞机位置
0x00406D70 4 nplanY,
0x00406E10 4 子弹数组首地址
0x00406DA8 4 当前子弹的个数
0x00406D80 4 死亡标志
0x004020F5 1 速度
0x00403616 0xeb __asm jmp 无敌模式
0x74 __asm je 普通模式
0x0040469E 4 初始子弹个数
nBulletX >>= 6;
nBulletX -= 4;
nBulletY >>= 6;
nBulletY -= 4;
+=0xF
子弹:
x坐标 = ary[i * 0xf +0] >>= 6 -= 4
y坐标 = ary[i * 0xf =4] >>= 6 -= 4
代码如下
void CGameAssistDlg::Wudi(BYTE bt)
{
//获取窗口句柄
HWND hWndGame = ::FindWindow(NULL, "摿孭");
if (hWndGame == NULL)
{
AfxMessageBox("获取窗口句柄失败");
return;
}
//获取进程ID
DWORD dwProId = 0;
DWORD dwThreadId = GetWindowThreadProcessId(hWndGame, &dwProId);
if (dwThreadId == 0)
{
AfxMessageBox("获取进程id失败");
return;
}
//获取进程句柄
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProId);
if (hProc == NULL)
{
AfxMessageBox("获取进程句柄失败");
return;
}
//修改内存属性
LPVOID pAddrGod = (LPVOID)0x00403616;
//BYTE bt = 0xeb;
DWORD dwOldProc = 0;
BOOL bRet = VirtualProtectEx(hProc, pAddrGod, sizeof(bt), PAGE_READWRITE, &dwOldProc);
if (!bRet)
{
AfxMessageBox("修改内存属性失败");
return ;
}
//修改内存
bRet = WriteProcessMemory(hProc, pAddrGod, &bt, sizeof(bt), NULL);
if (!bRet)
{
AfxMessageBox("无敌失败");
}
//修改完后还原内存属
VirtualProtectEx(hProc, pAddrGod, sizeof(bt), dwOldProc, &dwOldProc);
//释放进程句柄
CloseHandle(hProc);
}
修改前0x400000是可读的
修改后
还原内存属性