Windows核心编程系列文章仅作为实验报告和Windows编程学习参考,不作为任何技术文章,还望大佬们勿喷。

1. 实验名称

实现windows程序和进程编程

2. 实验环境

  1. VC6.0++
  2. Windows 7

3. 实验目的

  1. 完成一个Windows程序,并掌握进程的概念、进程的状态
  2. 下载process Explorer软件和spy++软件,使用这两款软件工具对内核对象,进程和线程相关信息进行查看。
  3. 要求应用采用进程函数实现一个应用程序
    • 创建进程的方法,结束进程方程;
    • 掌握进程间通信。
    • 内存地址的写入和查询

4. 实验内容、步骤及结果

1. 实验内容

  1. 使用process Explorer软件和Spy++,并学会使用软件,查看内核对象,理解内核对象,进程概念。
  2. 通过创建子进程,打开记事本程序,并调用CreateToolhelp32Snapshot查找测试程序,然后调用ReadProcessMemory查找需要读的值的地址,然后通过writeProcessMemory写入。

2. 实验步骤

  1. 学会使用procexp

【实验一】实现windows程序和进程编程 - 图1

Procexp除了可以查看进程,还可以拿来结束进程,重启进程等

【实验一】实现windows程序和进程编程 - 图2

  1. 学会使用spy++

【实验一】实现windows程序和进程编程 - 图3

可以看到每个进程以及其线程的地址信息

  1. 要求应用采用进程函数实现一个应用程序
  2. (1). 创建进程的方法,结束进程方程;
  1. int main(int argc, char* argv[])
  2. {
  3. TCHAR szFileName[] = TEXT("Notepad");
  4. STARTUPINFO si = { sizeof(si) };
  5. PROCESS_INFORMATION pi;
  6. ::CreateProcess(NULL, szFileName, NULL, NULL, FALSE,CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); // 关闭线程句柄,既然我们不使用它
  7. ::CloseHandle(pi.hThread);
  8. g_hProcess = pi.hProcess;
  9. printf("进程ID:%d,线程ID:%d\n", pi.dwProcessId, pi.dwThreadId);
  10. Sleep(100);
  11. ::CloseHandle(g_hProcess);
  12. return 0;
  13. }

(2). 掌握进程间通信。

  1. HWND nphWnd = ::FindWindow("Notepad", NULL);
  2. if (nphWnd)
  3. {
  4. int iVal;
  5. printf(" Input val = "); // 输入修改的值
  6. scanf("%d", &iVal);
  7. FindFirst(iVal); // 进行第一次查找
  8. ShowList(); // 打印出搜索的结果
  9. while (g_nListCnt > 1)
  10. {
  11. printf(" Input val = ");
  12. scanf("%d", &iVal);
  13. FindNext(iVal); // 进行下次搜索
  14. ShowList(); // 显示搜索结果
  15. }
  16. printf(" New value = "); // 取得新值
  17. scanf("%d", &iVal); // 写入新值
  18. if (WriteMemory(g_arList[0], iVal))
  19. printf(" Write data success \n");
  20. }
  21. else
  22. {
  23. ::MessageBox(NULL, TEXT("please open notepad"), TEXT("error"), MB_OK);
  24. return 0;
  25. }

(3). 内存地址的写入和查询

  1. BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
  2. {
  3. BYTE arBytes[4096]; // 读取1页内存
  4. if (!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL))
  5. return FALSE; // 此页不可读
  6. DWORD* pdw; // 在这1页内存中查找
  7. for (int i = 0; i<(int)4 * 1024 - 3; i++)
  8. {
  9. pdw = (DWORD*)&arBytes[i];
  10. if (pdw[0] == dwValue) // 等于要查找的值?
  11. {
  12. if (g_nListCnt >= 1024)
  13. return FALSE;
  14. // 添加到全局变量中
  15. g_arList[g_nListCnt++] = dwBaseAddr + i;
  16. }
  17. }
  18. return TRUE;
  19. }
  20. BOOL FindFirst(DWORD dwValue)
  21. {
  22. const DWORD dwOneGB = 1024 * 1024 * 1024; // 1GB
  23. const DWORD dwOnePage = 4 * 1024; // 4KB
  24. if (g_hProcess == NULL)
  25. return FALSE;
  26. DWORD dwBase; // 查看操作系统类型,以决定开始地址
  27. OSVERSIONINFO vi = { sizeof(vi) };
  28. ::GetVersionEx(&vi);
  29. if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
  30. dwBase = 4 * 1024 * 1024; // Windows 98系列,4MB
  31. else
  32. dwBase = 640 * 1024; // Windows NT系列,64KB
  33. for (; dwBase < 2 * dwOneGB; dwBase += dwOnePage) // 在开始地址到2GB的地址空间进行查找
  34. {
  35. CompareAPage(dwBase, dwValue); // 比较1页大小的内存
  36. }
  37. return TRUE;
  38. }
  39. BOOL FindNext(DWORD dwValue)
  40. {
  41. int nOrgCnt = g_nListCnt; // 保存m_arList数组中有效地址的个数,初始化新的m_nListCnt值
  42. g_nListCnt = 0; // 在m_arList数组记录的地址处查找
  43. BOOL bRet = FALSE; // 假设失败
  44. DWORD dwReadValue;
  45. for (int i = 0; i<nOrgCnt; i++)
  46. {
  47. if (::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL))
  48. {
  49. if (dwReadValue == dwValue)
  50. {
  51. g_arList[g_nListCnt++] = g_arList[i];
  52. bRet = TRUE;
  53. }
  54. }
  55. }
  56. return bRet;
  57. }
  58. void ShowList() // 打印出搜索到的地址
  59. {
  60. for (int i = 0; i< g_nListCnt; i++)
  61. {
  62. printf("%08lX \n", g_arList[i]);
  63. }
  64. }
  65. BOOL WriteMemory(DWORD dwAddr, DWORD dwValue)
  66. {
  67. return ::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
  68. }

3. 实验结果(实验运行截图)

输入修改的值:

【实验一】实现windows程序和进程编程 - 图4

取得新值并写入新值:

【实验一】实现windows程序和进程编程 - 图5

5. 实验中的问题及心得

  • 一开始使用VS2015去编译老师给的相关代码和书上的代码,发现会爆很多错误,后面发现是因为版本太高,好一些函数在新版本并不能使用。所以最后我还是使用的vc作为编译器。
  • 如果不在查找程序之前让程序稍停一会儿,那么FindWindow()函数会直接执行完毕而导致判断nphWnd时会认为程序并没有打开。所以需要在FindWindow()函数前加上sleep()函数。
  • 对windows的API调用时要注意每个函数的参数的作用,以及参数的类型,如果类型不匹配的话也是不能够成功调用的。

6. 附件

以下是实验代码

链接:https://pan.baidu.com/s/1Ytldu_4DypGdyGEIUCaIHQ
提取码:yaxi