學習《Windows程序設計》記錄
概念貼士:
1. 每個進程都有賦予它自己的私有地址空間。當進程內的線程運行時,該線程僅僅能夠訪問屬於它的進程的內存,而屬於其他進程的內存被屏蔽了起來,不能被該線程訪問。
PS:進程A在其地址空間的0x12345678地址處能夠有一個數據結構,而進程B能夠在其地址空間的0x12345678處存儲一個完全不同的數據。彼此不能訪問。
2. 在大多數系統中,Windows將地址空間的一半(4GB的前一半,0x00000000-0x7FFFFFFF)留給進程作為私有存儲,自己使用另一半(4GB的後一半,0x80000000-0xFFFFFFFFF)來存儲操作系統內部使用的數據。
3. 各進程的地址空間被分成了用戶空間和系統空間兩部分。用戶空間部分是進程私有地址空間,一個進程不能一任何方式讀、寫其他進程此部分空間中的數據。系統空間部分放置操作系統的代碼,包括內核代碼、設備驅動代碼、設備I/O緩沖區等。系統空間部分在所有的進程中是共享的。在部分系統中,這些數據結構是被保護的。試圖訪問時,會遇到訪問異常。
4. 處理器定義了多個特權級別。如80386處理器共定義了4種(0-3)特權級別,或者稱為環。其中0級是最高級(特權級),3級是最低級(用戶級)。
5. 為了阻止應用應用程序訪問或者修改關鍵的系統數據(即2GB系統空間內的數據),Windows使用了兩種訪問模式:內核模式和用戶模式,它們分別使用了處理器中的0和3這兩個特權級別。用戶程序的代碼在用戶模式下運行,系統程序(如系統服務程序和硬件驅動)的代碼在內核模式下運行。
6. 內核對象是系統提供的用戶模式下代碼與內核模式下代碼進行交互的基本接口。使用內核交互對象是應用程序和系統內核進行交互的重要方式之一。
7. 引入內核對象後,系統可以較為方便的完成以下4個任務:
1)為系統資源提供可識別的名字;
2)在進程之間共享資源和數據;
3)保護資源不會被未經許可的代碼訪問;
4)跟蹤對象的引用情況,這使得系統知道什麼時候一個對象不再被使用了,以便釋放它占用的空間。
8. 內核對象的數據結構僅能夠從內核模式訪問,所以直接在內存中定位這些數據結構對應用程序來說是不可能的。應用程序必須使用API函數來訪問內核對象。
9. 調用函數創建內核對象時,函數會返回標識此內核對象的句柄。句柄是進程相關的,僅對創建該內核對象的進程有效。當然,多個進程共享一個內核對象也是可能的,調用DuplicateHandle函數復制一個進程句柄傳給其他進程即可。
10. 內核對象中最簡單最常用的屬性---使用計數。使用計數屬性指明進程對特定內核對象的應用次數,當系統發現引用次數為0時,它會自動關閉資源。(創建時初始化為1,每次打開這個內核對象時,計數加1,關閉則減1。當減到0時,說明進程對這個內核對象的所有引用都已經關閉了,系統應該釋放此內核對象資源了。)
11. 進程(Process)是一個正在運行的程序,它擁有自己的虛擬地址空間,擁有自己的代碼、數據和其他系統資源,如進程創建的文件、管道、同步對象等。一個進程也包含一個或者多個運行在此進程內的線程(Thread)。
12. 程序與進程在表面很相似。但是,程序是一連串靜態的指令,而進程是一個容器,它包含了一系列運行在這個程序實例上下問中的線程使用的資源。
13. 線程是進程內執行代碼的獨立實體。操作系統創建了進程後,會創建一個線程執行進程中的代碼。通常稱這個線程為主線程。主線程在運行過程中可能會創建其他線程(稱為輔助線程)。
14. Win進程兩個組成部分:
1)進程內核對象。操作系統使用此內核對象來管理該進程。這個內核對象也是操作系統存放進程統計信息的地方。
2)私有的虛擬地址空間。此地址空間包含了所有可執行的或者是DLL模塊的代碼和數據,它也是程序動態申請內存的地方,比如說線程堆棧和進程堆。
15. 應用程序必須有一個入口函數,它在程序開始運行的時候被調用。如果創建的是控制台應用程序,此入口函數就是main。
PS:int main(int argc,char *argv[]);
16. 事實上,操作系統並不是真的調用main函數,而是去調用C/C++運行期啟動函數,此函數會初始化C/C++運行期庫。因此,在程序中可以調用malloc和free之類的函數。
17. 在控制台程序中,C/C++運行期啟動函數會調用程序入口函數main。如果沒有,將會返回“unresolved external symbol”錯誤。
18. Win32程序的啟動過程就是進程的創建過程,操作系統通過調用CreateProcess函數(代碼解釋中將會有對該函數的具體解釋)來創建新的進程的。當一個線程調用CreateProcess函數的時候,系統會創建一個進程內核對象,其使用計數被初始化為1.此進程內核對象不是進程本身,僅僅是一個系統用來管理這個進程的小的數據結構。系統然後會為新的進程創建一個虛擬地址空間,加載應用程序運行時所需要的代碼和數據。系統接著會為新進程創建一個主線程,這個主線程通過執行C/C++運行期啟動代碼開始運行,C/C++運行期啟動代碼又會調用main函數。如果系統能夠成功創建新的進程和進程的主線程,CreateProcess函數會返回TRUE,否則會返回FALSE。
19. 一般將創建進程稱為父進程,被創建的進程稱為子進程。系統在創建新的進程時會為新進程指定一個STARTUPINFO類型的變量,這個結構包含了父進程傳遞給子進程的一些顯示信息。(對圖形界面應用程序來說,這些信息會影響新的進程中主線程的主窗口的顯示等。)
代碼解釋:
1.CreateProcess(create process)
PS:一個完整的創建進程的程序,打開了Windows自帶的命令行程序cmd.exe。(不過我一般是Win+R,直接cmd進入。)
1 #include "stdafx.h" 2 #include <windows.h> 3 #include <stdio.h> 4 5 int main(int argc, char* argv[]) 6 { 7 char szCommandLine[] = "cmd"; 8 STARTUPINFO si = { sizeof(si) }; 9 PROCESS_INFORMATION pi; 10 11 si.dwFlags = STARTF_USESHOWWINDOW; // 指定wShowWindow成員有效 12 si.wShowWindow = TRUE; // 此成員設為TRUE的話則顯示新建進程的主窗口, 13 // 為FALSE的話則不顯示 14 BOOL bRet = ::CreateProcess ( 15 NULL, // 不在此指定可執行文件的文件名 16 szCommandLine, // 命令行參數 17 NULL, // 默認進程安全性 18 NULL, // 默認線程安全性 19 FALSE, // 指定當前進程內的句柄不可以被子進程繼承 20 CREATE_NEW_CONSOLE, // 為新進程創建一個新的控制台窗口 21 NULL, // 使用本進程的環境變量 22 NULL, // 使用本進程的驅動器和目錄 23 &si, 24 &pi); 25 26 if(bRet) 27 { 28 // 既然我們不使用兩個句柄,最好是立刻將它們關閉 29 ::CloseHandle (pi.hThread); 30 ::CloseHandle (pi.hProcess); 31 32 printf(" 新進程的進程ID號:%d \n", pi.dwProcessId); 33 printf(" 新進程的主線程ID號:%d \n", pi.dwThreadId); 34 } 35 return 0; 36 }
2.ProcessList(process list)
PS:使用ToolHelp函數中的CreateToolhelp32Snapshop函數給當前系統內執行的進程拍快照(Snapshot),獲取進程列表。然後利用Process32First函數和Process32Next函數遍歷快照中的記錄的列表。
1 #include "stdafx.h" 2 #include <windows.h> 3 #include <tlhelp32.h> // 聲明快照函數的頭文件 4 5 int main(int argc, char* argv[]) 6 { 7 PROCESSENTRY32 pe32; 8 // 在使用這個結構之前,先設置它的大小 9 pe32.dwSize = sizeof(pe32); 10 11 // 給系統內的所有進程拍一個快照 12 HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 13 if(hProcessSnap == INVALID_HANDLE_VALUE) 14 { 15 printf(" CreateToolhelp32Snapshot調用失敗! \n"); 16 return -1; 17 } 18 19 // 遍歷進程快照,輪流顯示每個進程的信息 20 BOOL bMore = ::Process32First(hProcessSnap, &pe32); 21 while(bMore) 22 { 23 printf(" 進程名稱:%s \n", pe32.szExeFile); 24 printf(" 進程ID號:%u \n\n", pe32.th32ProcessID); 25 26 bMore = ::Process32Next(hProcessSnap, &pe32); 27 } 28 29 // 不要忘記清除掉snapshot對象 30 ::CloseHandle(hProcessSnap); 31 return 0; 32 }
3.TerminateProcess(terminate process)
PS:該程序通過輸入進程獨有的ID號,來結束該進程,兵返回操作結果。(你們可以從任務管理器找一個進程ID試試,建議拿QQ什麼的試。千萬別找Windows什麼的)
1 #include "stdafx.h" 2 #include <windows.h> 3 4 BOOL TerminateProcessFromId(DWORD dwId) 5 { 6 BOOL bRet = FALSE; 7 // 打開目標進程,取得進程句柄 8 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId); 9 if(hProcess != NULL) 10 { 11 // 終止進程 12 bRet = ::TerminateProcess(hProcess, 0); 13 } 14 CloseHandle(hProcess); 15 return bRet; 16 } 17 18 int main(int argc, char* argv[]) 19 { 20 DWORD dwId; 21 printf(" 請輸入您要終止的進程的ID號: \n"); 22 scanf("%u", &dwId); 23 if(TerminateProcessFromId(dwId)) 24 { 25 printf(" 終止進程成功! \n"); 26 } 27 else 28 { 29 printf(" 終止進程失敗! \n"); 30 } 31 32 return 0; 33 }
4.Testor
PS:這個是為了這章最後一個程序--內存修改器服務的一個測試程序。後面會通過內存修改器修改g_nNum和i的值。
1 #include "stdafx.h" 2 #include <stdio.h> 3 // 全局變量測試 4 int g_nNum; 5 int main(int argc, char* argv[]) 6 { 7 int i = 198; // 局部變量測試 8 g_nNum = 1003; 9 10 while(1) 11 { 12 // 輸出個變量的值和地址 13 printf(" i = %d, addr = %08lX; g_nNum = %d, addr = %08lX \n", 14 ++i, &i, --g_nNum, &g_nNum); 15 getchar(); 16 } 17 18 return 0; 19 }
5.MemRepair(memory repair)
PS:這章最後一個程序--內存修改器。具體說明程序備注有寫。(大家可以試試,游戲掛了不負責。另外,在第七章後將會有一個GUI版本的。)
1 #include "stdafx.h" 2 #include "windows.h" 3 #include "stdio.h" 4 #include <iostream.h> 5 6 7 BOOL FindFirst(DWORD dwValue); // 在目標進程空間進行第一次查找 8 BOOL FindNext(DWORD dwValue); // 在目標進程地址空間進行第2、3、4……次查找 9 10 DWORD g_arList[1024]; // 地址列表 11 int g_nListCnt; // 有效地址的個數 12 HANDLE g_hProcess; // 目標進程句柄 13 14 15 ////////////////////// 16 17 BOOL WriteMemory(DWORD dwAddr, DWORD dwValue); 18 void ShowList(); 19 20 21 int main(int argc, char* argv[]) 22 { 23 // 啟動02testor進程 24 char szFileName[] = "..\\02testor\\debug\\02testor.exe"; 25 STARTUPINFO si = { sizeof(si) }; 26 PROCESS_INFORMATION pi; 27 ::CreateProcess(NULL, szFileName, NULL, NULL, FALSE, 28 CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); 29 // 關閉線程句柄,既然我們不使用它 30 ::CloseHandle(pi.hThread); 31 g_hProcess = pi.hProcess; 32 33 // 輸入要修改的值 34 int iVal; 35 printf(" Input val = "); 36 scanf("%d", &iVal); 37 38 // 進行第一次查找 39 FindFirst(iVal); 40 41 // 打印出搜索的結果 42 ShowList(); 43 44 45 while(g_nListCnt > 1) 46 { 47 printf(" Input val = "); 48 scanf("%d", &iVal); 49 50 // 進行下次搜索 51 FindNext(iVal); 52 53 // 顯示搜索結果 54 ShowList(); 55 } 56 57 58 // 取得新值 59 printf(" New value = "); 60 scanf("%d", &iVal); 61 62 // 寫入新值 63 if(WriteMemory(g_arList[0], iVal)) 64 printf(" Write data success \n"); 65 66 67 ::CloseHandle(g_hProcess); 68 return 0; 69 } 70 71 BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue) 72 { 73 // 讀取1頁內存 74 BYTE arBytes[4096]; 75 if(!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL)) 76 return FALSE; // 此頁不可讀 77 78 // 在這1頁內存中查找 79 DWORD* pdw; 80 for(int i=0; i<(int)4*1024-3; i++) 81 { 82 pdw = (DWORD*)&arBytes[i]; 83 if(pdw[0] == dwValue) // 等於要查找的值? 84 { 85 if(g_nListCnt >= 1024) 86 return FALSE; 87 // 添加到全局變量中 88 g_arList[g_nListCnt++] = dwBaseAddr + i; 89 } 90 } 91 92 return TRUE; 93 } 94 95 BOOL FindFirst(DWORD dwValue) 96 { 97 const DWORD dwOneGB = 1024*1024*1024; // 1GB 98 const DWORD dwOnePage = 4*1024; // 4KB 99 100 if(g_hProcess == NULL) 101 return FALSE; 102 103 // 查看操作系統類型,以決定開始地址 104 DWORD dwBase; 105 OSVERSIONINFO vi = { sizeof(vi) }; 106 ::GetVersionEx(&vi); 107 if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) 108 dwBase = 4*1024*1024; // Windows 98系列,4MB 109 else 110 dwBase = 640*1024; // Windows NT系列,64KB 111 112 // 在開始地址到2GB的地址空間進行查找 113 for(; dwBase < 2*dwOneGB; dwBase += dwOnePage) 114 { 115 // 比較1頁大小的內存 116 CompareAPage(dwBase, dwValue); 117 } 118 119 return TRUE; 120 } 121 122 BOOL FindNext(DWORD dwValue) 123 { 124 // 保存m_arList數組中有效地址的個數,初始化新的m_nListCnt值 125 int nOrgCnt = g_nListCnt; 126 g_nListCnt = 0; 127 128 // 在m_arList數組記錄的地址處查找 129 BOOL bRet = FALSE; // 假設失敗 130 DWORD dwReadValue; 131 for(int i=0; i<nOrgCnt; i++) 132 { 133 if(::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL)) 134 { 135 if(dwReadValue == dwValue) 136 { 137 g_arList[g_nListCnt++] = g_arList[i]; 138 bRet = TRUE; 139 } 140 } 141 } 142 143 return bRet; 144 } 145 146 // 打印出搜索到的地址 147 void ShowList() 148 { 149 for(int i=0; i< g_nListCnt; i++) 150 { 151 printf("%08lX \n", g_arList[i]); 152 } 153 } 154 155 BOOL WriteMemory(DWORD dwAddr, DWORD dwValue) 156 { 157 return ::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL); 158 }
總結:重要的概念、知識點、代碼講解都被標識出來了。通過這章可以對Win32程序運行等原理有著清晰的了解。真正地懂得了程序運行的完整過程、了解內存的實際情況。改變了以前對Win32程序認識的不清晰。