本文记录了如何使用Windows提供的ReadProcessMemory和WriteProcessMemory对其他进程的内存空间进行读写操作。
ReadProcessMemory和WriteProcessMemory的函数原型
1 2 3 4 5 6 7
| BOOL ReadProcessMemory( HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead );
|
1 2 3 4 5 6 7
| BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten );
|
Microsoft的文档中有对每个参数的详细解释:
ReadProcessMemory function (memoryapi.h)
WriteProcessMemory function (memoryapi.h)
使用这两个函数,需要传入目标进程的句柄、想要读写内存的基地址、读写内存的大小等信息。在通过CreateProcess创建子进程时,可以通过PROCESS_INFORMATION结构体得到子进程的句柄。因此,先来看读写子进程内存的方法。
读写子进程内存
编写子进程
子进程的功能为打印全局变量i
的值,并使变量i
不断自增。
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> #include <synchapi.h>
int i; int main() { while(1) { printf("%d. ", i++); Sleep(100); } }
|
运行结果为:
1 2
| > child 0. 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. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.
|
编写父进程读取子进程内存
父进程首先要通过CreateProcess创建子进程,然后通过PROCESS_INFORMATION结构体获取子进程的句柄。之后,再将句柄传入ReadProcessMemory,即可读取子进程的内存。
如果要让父进程修改子进程的全局变量i
,则需要知道i
的内存地址,这可以通过反汇编工具(如IDA Pro)获取。我使用的编译器会将全局变量i
的内存地址设置为0x407970,不同编译器编译得到的child中i
的内存地址可能不同。
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 31 32 33 34 35 36 37
| #include <processthreadsapi.h> #include <stdio.h> #include <errhandlingapi.h> #include <synchapi.h> #include <handleapi.h> #include <memoryapi.h>
int main() { STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; si.cb = sizeof(si); BOOL result = CreateProcessA( "child.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); if(!result) { printf("Create Process Failed! Error Code:%d\n", GetLastError()); return -1; } int childi; while(1) { ReadProcessMemory(pi.hProcess, (void *)0x407970, &childi, sizeof(int), NULL); printf("\nparent read child i: %d\n", childi); Sleep(600); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0; }
|
运行parent:
1 2 3 4 5 6 7 8 9 10 11 12 13
| > parent
parent read child i: 0 0. 1. 2. 3. 4. 5. parent read child i: 6 6. 7. 8. 9. 10. 11. parent read child i: 12 12. 13. 14. 15. 16. 17. parent read child i: 18 18. 19. 20. 21. 22. 23. parent read child i: 24 24. 25. 26. 27. 28. 29. parent read child i: 30
|
可以看出parent成功读取了child中变量i
的内容。
添加修改子进程内存的代码
使用WriteProcessMemory可以修改子进程中i
的值。在parent中添加代码:
1 2 3 4 5 6 7 8 9 10
| int childi; while(1) { ReadProcessMemory(pi.hProcess, (void *)0x407970, &childi, sizeof(int), NULL); if(childi > 20) { childi = 0; WriteProcessMemory(pi.hProcess, (void *)0x407970, &childi, sizeof(int), NULL); } Sleep(600); }
|
当读取到的i
大于20时,就将i
清零,并写入子进程的内存当中。运行parent:
1 2 3 4 5
| > parent 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 0. 1. 2. 3. 4. 5. 6.
|
i
大于20后不久就会被清零。之所以没有立即清零是因为parent中有一个Sleep(600);
。
读取其他进程的内存
读写其他进程内存需要首先获取其句柄,这可以通过函数OpenProcess得到。
OpenProcess函数原型
1 2 3 4 5
| HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId );
|
因此,知道了目标进程的进程标识符ProcessId,就可以获得其句柄了。
OpenProcess function (processthreadsapi.h)
编写目标进程代码
目标进程会先调用GetCurrentProcessId获取自身的进程标识符并打印出来,然后就开始不断打印全局变量i
的值并将i
加一。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <stdio.h> #include <synchapi.h> #include <processthreadsapi.h>
int i; int main() { printf("My Process ID: %d\n", GetCurrentProcessId()); while(1) { printf("%d. ", i++); Sleep(100); } }
|
编写修改器代码
修改器会先根据目标进程的ProcessId获取句柄,然后再调用ReadProcessMemory和WriteProcessMemory读写目标进程的全局变量i
,当i
大于20时清零。
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 31 32 33 34 35 36 37 38 39
| #include <stdlib.h> #include <stdio.h> #include <processthreadsapi.h> #include <synchapi.h> #include <memoryapi.h> #include <errhandlingapi.h>
int main(int argc, char **argv) { if(argc < 2) { printf("Usage: modifier [pid]\n"); return -1; } int targetPid = atoi(argv[1]); HANDLE hTarget = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, targetPid); if(NULL == hTarget) { printf("Open Process Failed! Error Code: %d\n", GetLastError()); return -1; } int targetI; BOOL result; while(1) { result = ReadProcessMemory(hTarget, (void *)0x407970, &targetI, sizeof(int), NULL); if(!result) { printf("Read Process Memory Failed! Error Code: %d\n", GetLastError()); return -1; } else if(targetI > 20) { targetI = 0; result = WriteProcessMemory(hTarget, (void *)0x407970, &targetI, sizeof(int), NULL); if(!result) { printf("Write Process Memory Failed! Error Code: %d\n", GetLastError()); return -1; } } Sleep(600); } return 0; }
|
运行结果
首先运行目标程序program,得到其进程标识符为4372,然后再运行修改器程序并传入参数4372,即modifier 4372
,一段时间后终止修改器。最终,program的打印结果如下:
1 2 3 4 5 6 7 8 9 10
| > program My Process ID: 4372 0. 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. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 0. 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. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57.
|
全局变量i
一开始会不断自增,当修改器开始运行后,i
的值便被清零,然后当i
的值超过20后也会被清零。修改器停止运行后,i
的值又会不断自增而不会被清零。