--- title : 02_Windows_目錄處理與檔案處理 --- # Windows_目錄處理與檔案處理 By Cheng-Yen, Tsai ; BO-JUN Tsai Date : 2021-05-22 ## 壹、前言 基本的目錄處理也就是對該目錄下的文件和子目錄進行操作,包括常用的複製、移動等等,針對這些行為,微軟也提供了蠻多函式可以使用。目錄處理是針對檔案的位置、檔案屬性、檔案的增刪等等進行操作,而檔案處理是針對檔案內部的內容作讀取、寫入等動作,這些對勒索病毒來說是很重要的操作。 --- ## 貳、目錄處理的API ### 01. CopyFile() * 會在同個檔案位置新增一份一模一樣的檔案,連最後修改時間等等都相同。 * 一共有三個參數。第一個是要被複製的檔案名稱,並且要給檔案位,若是不存在的檔案命稱,則產生錯誤**ERROR_FILE_NOT_FOUND**。 * 第二個是新檔案的名稱,同樣要給檔案位置。 * 最後是一個布林值,如果是False,且新檔名存在,則會直接覆蓋過去,但是如果存在的檔案屬性是「唯讀」或是「隱藏」,就會無法覆蓋,產生**ERROR_ACCESS_DENIED**錯誤。如果是True,則檔案不會改變,並產生**ERROR_FILE_EXISTS**錯誤。 * 下面是一個簡單複製檔案的例子: ```clike= #include <windows.h> #include <stdio.h> #include <tchar.h> int main() { if (!CopyFile(_T("C:\\Users\\ASUS\\file.txt"), _T("C:\\Users\\ASUS\\file01.txt"), false)) { _tprintf(_T("CopyFile() error: %d\n"), GetLastError()); return false; } } ``` <div style="text-align: center"> <img src="https://i.imgur.com/KFIt3EE.png"/> </div> * 上表是在說明新舊檔案間的時間關係,在閱讀資料的同時,我們得知這是一個很關鍵的性質,因為許多軟體是用最後修改時間來判斷檔案是否被修改,所以在進行此項操作要留意這個性質。 * 另外與CopyFile()使用方法相近的有DeleteFile()、MoveFile()等等。 ___ ### 02.Delete() * 刪除檔案,從目錄檔案的資訊從表中移除,檔案所佔的磁碟機空間,會被標上「未使用」的記號,而內容還是會留在磁碟機裡原本的位置。 ```cpp= if(!DeleteFile(_T("C:\\TEMP\\file2.txt"))) { _tprintf(_T("CopyFile() Error: %d\n"), GetLastError()); return false; } ``` --- ### 03.MoveFile * 移動檔案,是不會改變磁碟裡的空間,過程:將「檔案G」從「目錄A」移至「目錄B」也就是將原檔案從目錄A移除;但如果檔案A與檔案B在不同的磁碟,就必須要有複製動作,也就是CopyFile()加上DeleteFile()。 ```cpp= if (!MoveFile(_T("C:\\TEMP\\OldName.txt"), _T("C:\\TEMP\\NewName.txt") )) { _tprintf(_T("CopyFile() Error: %d\n"), GetLastError()); return false; } ``` ___ ### 04. GetFileAttributes() * 在CopyFile()函式第三個三數中,已經有關於檔案屬性了,檔案屬性可能造成不同的結果甚至產生錯誤,所以在進行檔案的操作前,要先取得檔案的屬性。 * 以下是檔案屬性表 : <div style="text-align: center"> | 屬性 | 實際數值 | 意義 | | ------------------------ | -------- | ------ | | FILE_ATTRIBUTE_READONLY | 1(0x1) | 唯讀檔案 | | FILE_ATTRIBUTE_HIDDEN | 2(0x2) | 隱藏檔案 | | FILE_ATTRIBUTE_SYSTEM | 4(0x4) | 系統檔案 | | FILE_ATTRIBUTE_ARCHIVE | 32(0x20) | 封存檔案 | | FILE_ATTRIBUTE_NORMAL | 128(0x80) | 普通檔案 | | FILE_ATTRIBUTE_TEMPORARY |256(0x100) | 暫存檔案 | | FILE_ATTRIBUTE_OFFLINE |4096(0x1000) | 離線檔案 | | FILE_ATTRIBUTE_ENCRYPTED |16384(0x4000)| 加密檔案 | </div> ```clike= #include <iostream> #include <windows.h> #include <string> using namespace std; int main() { DWORD T; LPCSTR T1 = "test01.txt"; T = GetFileAttributesA(T1); cout << T; } ``` ___ ### 05. SetFileAttributes() ```clike= #include <iostream> #include <windows.h> #include <string> using namespace std; int main() { LPCSTR T = "test01.txt"; SetFileAttributesA(T, 1); } ``` ## 參、檔案處理 ### 01. CreateFile() * 開啟檔案,系統會在記憶體裡存放結構,結構包含關於檔案的資料,並且回傳一個HANDLE,之後對於這個檔案的操作就透過這個HANDLE進行。 * 在官網上看到CreateFile()的宣告使用七個參數: * **LPCTSTP lpFileName** => 指定檔案名稱,但是不能是目錄。 * **DWORD dwDesiredAccess** => 指定開啟模式,可以是: GENERIC_READ : 可以讀取,不能寫入 GENERIC_WRITE : 可以寫入,不能讀取 GENERIC_READ | GENERIC_WRITE : 寫入並且讀取 0 : 不讀取也不寫入 * **DWORD dwShareMode** => 檔案開啟後,是否允許別的程式對此檔案的動作,可以是: 0 : 完全不允許其他程式開啟此檔案 FILE_SHARE_DELETE : 允許檔案被刪除或更改檔名。 FILE_SHARE_READ : 允許別的程式以GENERIC_READ模式開啟 FILE_SHARE_WRITE : 允許別的程式以GENERIC_WRITE模式開啟 * **LPSECURITY_ATTRIBUTE** => 此參數是關於資訊安全的,可以設為NULL,使用預設值。 * **DWORD CreationDisposition** => 關於檔案存在與不存在的動作,可以是: 1. CREATE_NEW : 若不存在,則建立新檔案,存在則回傳 ERROR_FILE_EXISTS錯誤 2. CREAT_ALWAYS : 若不存在,則建立新檔案,存在且有寫入權限,則清除所有內容,逤存在且沒有寫入權限,則回傳ERROR_ALREADY_EXIST錯誤 3. OPEN_EXISTING : 開啟存在的檔案,若不存在,則回傳ERROR_FILE_NOT_FOUND錯誤 4. OPEN_ALWAYS : 若檔案不存在,則建立檔案,檔案存在且有寫入權限,則開啟檔案且不更動內容,若無權限,則回傳ERROR_ALREADYEXISTS錯誤 5. TRUNCATE_EXISTING : 開啟存在的檔案,且清空內容,若檔案不存在,則回傳ERROR_FILE_NOT_FOUND錯誤 * **DWORD dwFlagsAndAttributes** => 設定檔案屬性,只對產生新檔案有效,至於已存在的檔案,會忽落這個參數,其中參數值就是上方的檔案屬性表 * **HANDLE hTemplateFile** => 此參數目前還沒觀察到特定的效果 * 另外針對回傳值也要檢查,回傳值是一個HANDLE,檔案不一定每次都開啟成功,上述列出許多可能發生的錯誤,但是CreateFile()回傳的是INVALID_HANDLE_VALUE,透過此錯誤訊息,並沒辦法確認實際的出錯點在哪,所以要透過GetLastError()來取得錯誤碼,根據錯誤碼找出真正的出錯原因。 ### 02. ReadFile() * 讀取檔案,除了HANDLE外,還會有存放檔案內容的地方(指標),記憶體可以是一個CHAR陣列,或著是HeapAlloc()配置的記憶體。 * 檔案的參數: * **HANDLE hFile** =>是CreateFile()回傳值,讀取檔案內容必須包含GENERIC_READ模式。 * **LPVOID lpBuffer** =>記憶體地址,可以存放像是整數(小型資料),陣列(大型資料),或著是HeapAlloc()配置的記憶體。 * **DWORD nNumberOFBytesToRead** =>將資料放到lpBuffer的最大數量,例如:一個陣列大小為512bytes,就放512。 * **LPDWORD nNumberOFBytesRead** =>一個指向4位元無號數DWORD的指標,ReadFile()讀取到的數量會放在這裡。如果沒想要知道讀取到多少資料,參數可以放NULL,但無法確認讀取多少數量。 * **LPOVERLAPPED lpOverlapped** =>用於非同步IO,大部分不會用到,放NULL就好。 ### 03. Write() * **HANDLE hFile** =>是CreateFile()回傳值,讀取檔案內容必須包含GENERIC_READ模式。 * **LPCVOID lpBuffer** =>記憶體地址,資料將寫出。 * **DWORD nNumberOFBytesToWrite** =>lpBuffer裡的資料寫出的數量,也可以是一個陣列大小。 * **LPDWORD nNumberOFBytesWritten** =>執行WriteFile()後,會在這裡存放實際寫出數量,和前面的nNumberOFBytesToWrite一樣,不一樣得找出原因。 * **lpOverlapped** =>非同步IO ### 04. GetFileTime() * 每個檔案都有時間,利用命令字元dir可以得到檔案日期,勒索病毒也會用到檔案時間加密檔案與原本檔案時間相同,解密時也會用到檔案時間,寫入解密檔案中。 * **HANDLE hFile** =>檔案傳回的HANDLE。 * **LPFILETIME lpCreationTime** =>檔案產生時間。 * **LPFILETIME lpLastAccessTime** =>檔案存取時間,讀取或寫入後就會改變檔案時間。 * **LPFILETIME lpLastWriteTime** =>檔案修改時間,寫入動作會改變時間。 ### 05. SetFileTime() * 勒索病毒加密前,會讀取出三個檔案時間,這些時間是用SetFileTime()來設定回加密檔案,解密也是透過相同手法,解完密後,內容雖然復原,但檔案時間要利用SetFileTime()來還原時間。 * **HANDLE hFile** =>檔案傳回的HANDLE,必須要有GENERIC_WRITE。 * **const FILETIME \*lpCreationTime** =>檔案產生時間。 * **const FILETIME \*lpLastAccessTime** =>檔案存取時間,讀取或寫入後就會改變檔案時間。 * **const FILETIME \*lpLastWriteTime** =>檔案修改時間,寫入動作會改變時間。 * 讀取、設定檔案時間範例,參數為兩個檔案,範例程式讀取的第一個時間,寫到第二個檔案裡,兩個時間就會變成一樣。 ```cpp=1 #include <Windows.h> #include <tchar.h> BOOL touch(LPCTSTR lpSource, LPCTSTR lpTarget) { HANDLE hFile; FILETIME CreationTime, AccessTime, WriteTime; ////////////////////// // GetFileTime demo // ////////////////////// // Open source file with mode 0 if((hFile=CreateFile( lpSource, 0, FILE_SHARE_READ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ))== INVALID_HANDLE_VALUE) { _tprintf(_T("open file1.txt fails\n")); return false; } //Get file times GetFileTime( hFile, &CreationTime, &AccessTime, &WriteTime); //close CloseHandle(hFile); //////////////// // SetFileTime// //////////////// // Open target file with GENERIC_WRITE mode if (INVALID_HANDLE_VALUE == (hFile=CreateFile( lpTarget, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ))) { _tprintf(_T("open file2.txt fail\n")); return false; } //Set file time SetFileTime( hFile, &CreationTime, &AccessTime, &WriteTime,); //close CloseHandle(hFile); return true; } int main() { touch( _T("C:\\TEMP\\file1.txt"), _T("C:\\TEMP\\file2.txt") ); return 0; } ```