woonadz :)

WIN32 API 프로그래밍 - 폴더 내 모든 파일 출력하기(FindFirstFileA, FindNextFileA) 본문

IT/malware dev

WIN32 API 프로그래밍 - 폴더 내 모든 파일 출력하기(FindFirstFileA, FindNextFileA)

C_scorch 2023. 10. 26. 01:07
반응형

FindFirstFileA 함수

HANDLE FindFirstFileA(
  [in]  LPCSTR             lpFileName,
  [out] LPWIN32_FIND_DATAA lpFindFileData
);

[in] lpFileName

디렉터리 또는 경로 및 파일 이름입니다. 파일 이름에는 와일드카드 문자(예: 별표(*) 또는 물음표(?)가 포함될 수 있습니다. 이 매개 변수는 NULL, 잘못된 문자열(예: 빈 문자열 또는 종료 null 문자가 없는 문자열) 또는 후행 백슬래시(\)로 끝나서는 안 됩니다.

문자열이 와일드카드, 마침표(.) 또는 디렉터리 이름으로 끝나는 경우 사용자는 경로의 루트 및 모든 하위 디렉터리에 대한 액세스 권한이 있어야 합니다.

⇒ 파일 목록을 검사하고 싶은 폴더 경로를 지정해줍니다.

 

[out] lpFindFileData

찾은 파일 또는 디렉터리에 대한 정보를 수신하는 WIN32_FIND_DATA 구조체에 대한 포인터입니다.

⇒ 파일 정보를 저장할 수 있도록 지정해줍니다.

 

반환 값

함수가 성공하면 반환 값은 FindNextFile 또는 FindClose에 대한 후속 호출에 사용되는 검색 핸들이며 lpFindFileData 매개 변수에는 발견된 첫 번째 파일 또는 디렉터리에 대한 정보가 포함됩니다.

함수가 lpFileName 매개 변수의 검색 문자열에서 파일을 찾지 못하거나 실패하면 반환 값이 INVALID_HANDLE_VALUElpFindFileData 의 내용이 확정되지 않습니다. 확장 오류 정보를 가져오려면 GetLastError 함수를 호출합니다.

일치하는 파일을 찾을 수 없어 함수가 실패하면 GetLastError 함수는 ERROR_FILE_NOT_FOUND 반환합니다.

⇒ HANDLE 값을 반환값으로 받기 때문에 HANDLE 구조체로 변수를 선언합니다.

#include <stdio.h>
#include <Windows.h>
#include <fileapi.h>

int main(void) {
	LPCSTR folder_path = "C:\\\\Users\\\\Owner\\\\Documents\\\\test\\\\*"; //파일 리스트를 찾을 폴더명
	WIN32_FIND_DATAA file_list;
	HANDLE result_handle;

	result_handle = FindFirstFileA(folder_path,&file_list);
	if (result_handle != INVALID_HANDLE_VALUE) { //FineFirstFileA 함수가 성공했을 때
		printf("%s", file_list.cFileName); //첫번째 파일명을 출력
	}
	else { //FineFirstFileA 함수가 실패했을 때
		printf("첫번째 파일 찾기에 실패하였습니다.");
	}

	return 0;
}

LPWIN32_FIND_DATAA 는 WIN32_FIND_DATAA 구조체의 포인터입니다.

저는 구조체 형식이 더 익숙해서 WIN32_FIND_DATAA 로 변수 선언을 해주었습니다.

 

WIN32_FINE_DATAA 의 멤버들을 아래에서 확인할 수 있습니다. 위 코드에서는 FileName만 필요하기 때문에 그 값만 가져왔습니다.

typedef struct _WIN32_FIND_DATAA {
  DWORD    dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD    nFileSizeHigh;
  DWORD    nFileSizeLow;
  DWORD    dwReserved0;
  DWORD    dwReserved1;
  CHAR     cFileName[MAX_PATH];
  CHAR     cAlternateFileName[14];
  DWORD    dwFileType; // Obsolete. Do not use.
  DWORD    dwCreatorType; // Obsolete. Do not use
  WORD     wFinderFlags; // Obsolete. Do not use
} WIN32_FIND_DATAA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA;

"C:\\Users\\Owner\\Documents\\test\\*" 위 사진은 탐색하려던 폴더에 있는 파일들입니다. 하지만 실제 출력값은 “.” 만 확인할 수 있었습니다. 그 이유는 FindFirstFileA 함수는 “.”과 “..”의 경로까지 포함하여 출력을 하기 때문에 가장 첫번째 파일로 “.”이 출력된 것 입니다.

다음 코드에서는 “.”과 “..” 경로를 예외처리하고 FindNextFileA 함수를 이용하여 폴더 내에 있는 모든 폴더와 파일들을 출력해보겠습니다.

FindNextFileA 함수

BOOL FindNextFileA(
  [in]  HANDLE             hFindFile,
  [out] LPWIN32_FIND_DATAA lpFindFileData
);

[in] hFindFile

FindFirstFile 또는 FindFirstFileEx 함수에 대한 이전 호출에서 반환된 검색 핸들입니다.

⇒ 저는 FindFirstFile을 이용하여 첫번째 파일의 정보를 불러왔습니다.

 

[out] lpFindFileData

찾은 파일 또는 하위 디렉터리에 대한 정보를 수신하는 WIN32_FIND_DATA 구조체에 대한 포인터입니다.

⇒ FindFirstFile의 output과 같은 형식입니다.

 

반환값

함수가 성공하면 반환 값이 0이 아니고 lpFindFileData 매개 변수에 찾은 다음 파일 또는 디렉터리에 대한 정보가 포함됩니다.

함수가 실패하면 반환 값은 0이고 lpFindFileData 의 내용은 확정되지 않습니다. 확장 오류 정보를 가져오려면 GetLastError 함수를 호출합니다.

일치하는 파일을 더 이상 찾을 수 없으므로 함수가 실패하면 GetLastError 함수는 ERROR_NO_MORE_FILES 반환합니다.

⇒ 반환값이 0인지 여부에 따라 함수의 성공 여부가 달라집니다.

#include <stdio.h>
#include <Windows.h>
#include <fileapi.h>
#include <string.h>

int main(void) {
	LPCSTR folder_path = "C:\\\\Users\\\\Owner\\\\Documents\\\\test\\\\*"; //파일 리스트를 찾을 폴더명
	WIN32_FIND_DATAA file_list;
	HANDLE result_handle;

	result_handle = FindFirstFileA(folder_path,&file_list);
	while (1){
		if (result_handle != INVALID_HANDLE_VALUE) { //FindFirstFileA 함수가 성공했을 때
			if (strcmp(file_list.cFileName, ".") != 0 && strcmp(file_list.cFileName, "..") != 0) { //"." 경로와 ".." 경로 예외처리
				printf("%s\\n", file_list.cFileName);
			}
			if (!FindNextFileA(result_handle, &file_list)) { //FindNextFileA 함수를 실행시키고 실패했을 때(모든 파일 목록을 찾았을 때)
				printf("모든 파일 목록을 찾았습니다.");
				break;
			}
		}
		else { //FindFirstFileA 함수가 실패했을 때
			printf("첫번째 파일 찾기에 실패하였습니다.");
			break;
		}
	}

	return 0;
}

 

 

다음 코드에서는 폴더를 출력하지 않고 폴더 안에 있는 파일들까지 출력하는 코드를 만들어보겠습니다.

#include <stdio.h>
#include <Windows.h>
#include <fileapi.h>
#include <string.h>

#define MAX_PATH 1000

void all_file_list(char* path);

int main(void) {
	char *root_path = "C:\\\\Users\\\\Owner\\\\Documents\\\\test";
	all_file_list(root_path);
	printf("모든 파일 목록을 찾았습니다.");

	return 0;
}

void all_file_list(char* path) {
	char folder_path[MAX_PATH];
	WIN32_FIND_DATAA file_list;
	HANDLE result_handle;

	strcpy_s(folder_path, sizeof(folder_path), path);
	strcat_s(folder_path, sizeof(folder_path), "\\\\*");
	result_handle = FindFirstFileA(folder_path, &file_list);

	while (1) {
		if (result_handle != INVALID_HANDLE_VALUE) {
			if (strcmp(file_list.cFileName, ".") != 0 && strcmp(file_list.cFileName, "..") != 0) {
				if (file_list.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) { //만약 폴더라면
					char tmp_path[MAX_PATH];
					WIN32_FIND_DATAA tmp_file_list;
					strcpy_s(tmp_path, sizeof(tmp_path), path);
					strcat_s(tmp_path, sizeof(tmp_path), "\\\\");
					strcat_s(tmp_path, sizeof(tmp_path), file_list.cFileName);
					all_file_list(tmp_path); //재귀함수를 통해 자식 폴더의 파일들까지 모두 출력
				}
				else {
					printf("%s\\n", file_list.cFileName);
				}
			}
			if (!FindNextFileA(result_handle, &file_list)) {
				break;
			}
		}
		else {
			printf("첫번째 파일 찾기에 실패하였습니다.");
			break;
		}
	}

}

간단하게 재귀함수를 이용하여 루트 폴더 내에 있는 모든 파일들을 출력하는 코드를 만들 수 있었습니다.

랜섬웨어는 이런식으로 파일 탐색을 하여 파일을 하나 하나 암호화하는 동작을 수행할 것 입니다. 다만, 걱정되는 부분이 재귀함수를 이용하여 시간 복잡도가 높아졌습니다. 만약 악성코드 개발자라면 랜섬웨어가 빠른 시간 내에 파일을 암호화하는 것을 중요하게 생각할 것 같습니다. 그래서 다른 탐색 알고리즘을 사용하여 시간 복잡도를 줄여보는 것도 좋을 것 같습니다.

 

반응형