---
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;
}
```