-
-
[学习]根据pid手动遍历进程句柄表思路
-
发表于: 5天前 523
-
大致方向
1.根据传入pid获取EPROCESS对象, GetProcessByPid是我自己实现的, 方式有点拉是硬取, 思路是获取当前进程, 找到活动进程链表, 依次遍历这个链表知道pid匹配则取出对象
1 2 3 4 5 6 7 8 | //根据pid获取eprocessUINT_PTR v_EPROCESS;BOOLEAN isSuccess = GetProcessByPid(pid, &v_EPROCESS);//获取EPROCESS失败则是pid不正确 直接返回if (!isSuccess){ return;} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //通过pid获取EPROCESSBOOLEAN GetProcessByPid(UINT64 pid,UINT_PTR* v_EPROCESS){ //获取当前进程 PEPROCESS CurrentProcess = PsGetCurrentProcess(); //获取ActiveProcessLinks活动进程链表 PLIST_ENTRY Head = (PLIST_ENTRY)((PUCHAR)CurrentProcess + 0x2f0); //临时链表头 PLIST_ENTRY Entry = Head; //do while 循环遍历链表 do { //回到EPROCESS头 PEPROCESS Process = (PEPROCESS)((PUCHAR)Entry - 0x2f0); //获取当前遍历pid HANDLE tempPid = PsGetProcessId(Process); //获取ImageFileName CHAR imageName[16] = { 0 }; GetImageNameByEProcess(Process,&imageName); //RtlCopyMemory(imageName,(PUCHAR)Process + 0x450, 15);// +0x450 ImageFileName偏移 //如果pid相等则返回 if (pid == tempPid) { *v_EPROCESS = Process; PZY_PRINT("通过pid获取EPROCESS成功! pid=%llu imageName=%s", (UINT64)tempPid, imageName); return TRUE; } Entry = Entry->Flink; } while (Entry != Head);//不等于表头则继续循环 return FALSE;} |
2.根据EPROCESS对象获取句柄表_HANDLE_TABLE(我直接使用硬编码偏移了, 没有用结构)

1 2 3 | //获取句柄表HANDLE_TABLE* v_HANDLE_TABLE = (HANDLE_TABLE*)poi(v_EPROCESS + 0x418);//这里+0x418直接取硬编码 比较方便 但是不同系统可能不一致UINT_PTR TableCode = v_HANDLE_TABLE->TableCode; |
3.取出句柄表中的TableCode取最后3字节获取句柄表等级

1 2 3 | //句柄表等级UINT64 level = TableCode & 0x3;UINT64 TableCodeBase = TableCode & ~3; |
4.根据句柄表等级分别遍历句柄表, 句柄表中取出句柄项遍历, 在我的系统上entry为0x10大小, 所以0x1000/0x10=0x100, 我根据windbg取出的大小是0xc, 多了个Spare2保留字段, 但是我查看数据发现实际上只用到0x10大小, 所以还是以数据为准

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //level == 0for (int i = 0;i < 0x100;i++){ }//level == 1for (int i = 0;i < 0x100;i++){ for (int j = 0;j < 0x100;j++) { }}//level == 2for (int i = 0;i < 0x100;i++){ for (int j = 0;j < 0x100;j++) { for (int k = 0;k < 0x100;k++) { } }} |
5.根据句柄项中的InfoTable来判断此句柄项是否有效(这是我测试后选用的判断方法 大家有其他方法也可以自行选择)
1 2 3 4 5 6 7 8 9 | for (int i = 0;i < 0x100;i++){ //如果句柄不为空则继续 if (v_HANDLE_TABLE_ENTRY->InfoTable) { } v_HANDLE_TABLE_ENTRY += 1;} |
6.获取句柄值, 句柄值每次间隔4个单位, 用i*4即可取出, 如果是二级判断则是j * 4 + 0x100 * i, 三级以此类推
1 2 3 4 5 | //句柄值 level == 0UINT64 handleCode = i * 4;//句柄值 level == 1UINT64 handleCode = j * 4 + 0x100 * i;//句柄值 level == 2 这个还没有实现 因为项目没用到3三级表 |
7.根据句柄项中的InfoTable解密得到Object对象, 下面的算法也是直接从系统函数逆出来的, 但是大体都是一样的, 我用的是win10, 不一样的地方可能在于0x0FFFFFFFFFFFFFFF0, win7应该是0x0FF0, 具体参数系统函数, 0x0FFFFFFFFFFFFFFF0这个偏移只有在一级以上才用到, 用来偏移句柄表的, 例如2级句柄表中的二级结构, 我这里的算法是0xe30c72eb2530ffff>>0x10=0xffffe30c72eb2530, 0xffffe30c72eb2530&0x0FFFFFFFFFFFFFFF0=0xffffe30c72eb2530, 0xffffe30c72eb2530+0x30=0xffffe30c72eb2560

1 2 | //解析句柄表的InfoTable得到句柄对象UINT_PTR handleObject = (((INT64)poi(v_HANDLE_TABLE_ENTRY) >> 0x10) & 0x0FFFFFFFFFFFFFFF0) + 0x30; |
8.根据句柄对象判断句柄类型, 我的做法是获取了句柄类型对象后取出Name成员进行判断, 进程就是Process字符

1 2 3 4 5 6 7 8 9 | //获取句柄类型UINT_PTR v_OBJECT_TYPE = MyObGetObjectType(handleObject);//process类型判断字符UNICODE_STRING ProcessType;RtlInitUnicodeString(&ProcessType, L"Process");//得到句柄名称UNICODE_STRING ObjectTypeName = GetObjectTypeNameByObjectType(v_OBJECT_TYPE);//判断句柄类型 是否是Processif (RtlEqualUnicodeString(&ObjectTypeName, &ProcessType, TRUE)) |
9.测试结果: 这里我用的ce来遍历测试, 刚打开ce时只有2个进程句柄分别都是自己cheatengine-x86_64-SSE4-AVX2.exe, 在附加了进程之后会多一个进程句柄, 我选择附加的是vmtoolsd.exe, 当你改变附加进程后遍历依旧是正确的说明遍历算法就是正确的, 以此验证


1 2 3 4 | pzy: [R0][TraverseHandleTable:194] ================ 开始遍历 [cheatengine-x8][3844] 句柄表 ================pzy: [R0][TraverseHandleTable:290] ObjectTypeName: Process PID=3844 Handle=2EC EPROCESS=FFFFE30C72A9B0C0 Attributes=1FFFFF fileName=cheatengine-x86_64-SSE4-AVX2.exe pzy: [R0][TraverseHandleTable:290] ObjectTypeName: Process PID=3844 Handle=36C EPROCESS=FFFFE30C72A9B0C0 Attributes=1FFFFF fileName=cheatengine-x86_64-SSE4-AVX2.exe pzy: [R0][TraverseHandleTable:290] ObjectTypeName: Process PID=5192 Handle=148 EPROCESS=FFFFE30C7260C340 Attributes=1FFFFF fileName=vmtoolsd.exe |
10.之后就是拓展自己的功能了, 总体难度不高 就是前期思路不清楚导致我踩了不少坑, 并且windbg似乎有些bug, 建议多重启虚拟测试机, 我遇到的bug例如: 有时断点停不下来, 值取不到等等, 以为是自己错了其实重启后又正常了