| 1 | + | #include "Tools.h" |
| 2 | + | |
| 3 | + | extern vector<PResultInfo> results; |
| 4 | + | std::unordered_map<std::string, std::wstring> md5Map; |
| 5 | + | std::mutex mtx; |
| 6 | + | |
| 7 | + | std::string calculateMD5(BYTE* buffer, DWORD bytesRead) { |
| 8 | + | std::string md5; |
| 9 | + | |
| 10 | + | // 初始化加密API |
| 11 | + | HCRYPTPROV hProv = 0; |
| 12 | + | if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { |
| 13 | + | std::cerr << "CryptAcquireContext failed\n"; |
| 14 | + | return md5; |
| 15 | + | } |
| 16 | + | |
| 17 | + | HCRYPTHASH hHash = 0; |
| 18 | + | if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { |
| 19 | + | std::cerr << "CryptCreateHash failed\n"; |
| 20 | + | CryptReleaseContext(hProv, 0); |
| 21 | + | return md5; |
| 22 | + | } |
| 23 | + | |
| 24 | + | // 读取文件并更新哈希值 |
| 25 | + | if (!CryptHashData(hHash, buffer, bytesRead, 0)) { |
| 26 | + | std::cerr << "CryptHashData failed\n"; |
| 27 | + | CryptDestroyHash(hHash); |
| 28 | + | CryptReleaseContext(hProv, 0); |
| 29 | + | return md5; |
| 30 | + | } |
| 31 | + | |
| 32 | + | // 获取哈希值 |
| 33 | + | DWORD hashSize = 16; // MD5 哈希值大小为 16 字节 |
| 34 | + | BYTE hashBuffer[16]; |
| 35 | + | if (CryptGetHashParam(hHash, HP_HASHVAL, hashBuffer, &hashSize, 0)) { |
| 36 | + | std::stringstream ss; |
| 37 | + | ss << std::hex << std::setfill('0'); |
| 38 | + | for (int i = 0; i < hashSize; ++i) { |
| 39 | + | ss << std::setw(2) << static_cast<unsigned int>(hashBuffer[i]); |
| 40 | + | } |
| 41 | + | md5 = ss.str(); |
| 42 | + | } |
| 43 | + | else { |
| 44 | + | std::cerr << "CryptGetHashParam failed\n"; |
| 45 | + | } |
| 46 | + | |
| 47 | + | CryptDestroyHash(hHash); |
| 48 | + | CryptReleaseContext(hProv, 0); |
| 49 | + | |
| 50 | + | return md5; |
| 51 | + | } |
| 52 | + | |
| 53 | + | string wstring2string(wstring wstr) |
| 54 | + | { |
| 55 | + | string result; |
| 56 | + | //获取缓冲区大小,并申请空间,缓冲区大小事按字节计算的 |
| 57 | + | int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, 0, NULL, NULL); |
| 58 | + | char* buffer = new char[len + 1]; |
| 59 | + | //宽字节编码转换成多字节编码 |
| 60 | + | WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), buffer, len, NULL, NULL); |
| 61 | + | buffer[len] = '\0'; |
| 62 | + | //删除缓冲区并返回值 |
| 63 | + | result.append(buffer); |
| 64 | + | delete[] buffer; |
| 65 | + | return result; |
| 66 | + | } |
| 67 | + | |
| 68 | + | DWORD rvaToFOA(LPVOID buf, int rva) |
| 69 | + | { |
| 70 | + | PIMAGE_DOS_HEADER pDH = (PIMAGE_DOS_HEADER)buf; |
| 71 | + | IMAGE_SECTION_HEADER* sectionHeader; |
| 72 | + | |
| 73 | + | if (*(PWORD)((size_t)pDH + pDH->e_lfanew + 0x18) == IMAGE_NT_OPTIONAL_HDR32_MAGIC) |
| 74 | + | { |
| 75 | + | PIMAGE_NT_HEADERS32 pNtH32 = PIMAGE_NT_HEADERS32((size_t)pDH + pDH->e_lfanew); |
| 76 | + | |
| 77 | + | sectionHeader = IMAGE_FIRST_SECTION(pNtH32); |
| 78 | + | } |
| 79 | + | else { |
| 80 | + | PIMAGE_NT_HEADERS64 pNtH64 = PIMAGE_NT_HEADERS64((size_t)pDH + pDH->e_lfanew); |
| 81 | + | |
| 82 | + | sectionHeader = IMAGE_FIRST_SECTION(pNtH64); |
| 83 | + | } |
| 84 | + | |
| 85 | + | while (sectionHeader->VirtualAddress != 0) |
| 86 | + | { |
| 87 | + | if (rva >= sectionHeader->VirtualAddress && rva < sectionHeader->VirtualAddress + sectionHeader->Misc.VirtualSize) { |
| 88 | + | return rva - sectionHeader->VirtualAddress + sectionHeader->PointerToRawData; |
| 89 | + | } |
| 90 | + | |
| 91 | + | sectionHeader++; |
| 92 | + | } |
| 93 | + | |
| 94 | + | return 0; |
| 95 | + | } |
| 96 | + | |
| 97 | + | bool containsIgnoreCase(const std::string& str1, const std::string& str2) { |
| 98 | + | std::string str1Lower = str1; |
| 99 | + | std::string str2Lower = str2; |
| 100 | + | |
| 101 | + | // 将两个字符串转换为小写 |
| 102 | + | std::transform(str1Lower.begin(), str1Lower.end(), str1Lower.begin(), ::tolower); |
| 103 | + | std::transform(str2Lower.begin(), str2Lower.end(), str2Lower.begin(), ::tolower); |
| 104 | + | |
| 105 | + | // 在转换后的字符串中查找 |
| 106 | + | return str1Lower.find(str2Lower) != std::string::npos; |
| 107 | + | } |
| 108 | + | |
| 109 | + | BYTE* readRDataSection(BYTE* buffer, PDWORD rdataLength) { |
| 110 | + | PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(buffer); |
| 111 | + | if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { |
| 112 | + | std::cerr << "Invalid DOS header." << std::endl; |
| 113 | + | return 0; |
| 114 | + | } |
| 115 | + | |
| 116 | + | PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<BYTE*>(buffer) + dosHeader->e_lfanew); |
| 117 | + | if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { |
| 118 | + | std::cerr << "Invalid NT headers." << std::endl; |
| 119 | + | return 0; |
| 120 | + | } |
| 121 | + | |
| 122 | + | PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders); |
| 123 | + | for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i) { |
| 124 | + | if (strcmp(".rdata", (char*)sectionHeader[i].Name) == 0) { |
| 125 | + | *rdataLength = sectionHeader[i].SizeOfRawData; |
| 126 | + | return reinterpret_cast<BYTE*>(buffer) + sectionHeader[i].PointerToRawData; |
| 127 | + | } |
| 128 | + | } |
| 129 | + | |
| 130 | + | return 0; |
| 131 | + | } |
| 132 | + | |
| 133 | + | LPSTR ConvertWideToMultiByte(LPCWSTR wideString) { |
| 134 | + | int wideLength = wcslen(wideString); |
| 135 | + | |
| 136 | + | int bufferSize = WideCharToMultiByte(CP_ACP, 0, wideString, wideLength, NULL, 0, NULL, NULL); |
| 137 | + | |
| 138 | + | LPSTR multiByteString = new char[bufferSize + 1]; |
| 139 | + | memset(multiByteString, 0, bufferSize + 1); |
| 140 | + | |
| 141 | + | WideCharToMultiByte(CP_ACP, 0, wideString, wideLength, multiByteString, bufferSize, NULL, NULL); |
| 142 | + | |
| 143 | + | return multiByteString; |
| 144 | + | } |
| 145 | + | |
| 146 | + | std::string GetDirectoryFromPath(const std::string& filePath) { |
| 147 | + | // 将文件路径转换为路径对象 |
| 148 | + | std::filesystem::path pathObj(filePath); |
| 149 | + | |
| 150 | + | // 返回文件所在目录的字符串表示 |
| 151 | + | return pathObj.parent_path().string(); |
| 152 | + | } |
| 153 | + | |
| 154 | + | bool endsWithDLL(const std::string& str) { |
| 155 | + | int strLength = str.length(); |
| 156 | + | for (size_t i = 0; i < strLength; i += 2) { |
| 157 | + | char ch = str[i]; |
| 158 | + | if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == '-')) |
| 159 | + | return false; |
| 160 | + | } |
| 161 | + | |
| 162 | + | return str.size() > 4 && str.compare(str.size() - 4, 4, ".dll") == 0; |
| 163 | + | } |
| 164 | + | |
| 165 | + | bool endsWithDLL(const std::wstring& str) { |
| 166 | + | int strLength = str.length(); |
| 167 | + | for (size_t i = 0; i < strLength; i += 2) { |
| 168 | + | wchar_t ch = str[i]; |
| 169 | + | if (!((ch >= L'a' && ch <= L'z') || (ch >= L'A' && ch <= L'Z') || (ch >= L'0' && ch <= L'9') || ch == L'_' || ch == L'.' || ch == L'-')) |
| 170 | + | return false; |
| 171 | + | } |
| 172 | + | |
| 173 | + | return str.size() > 4 && str.compare(str.size() - 4, 4, L".dll") == 0; |
| 174 | + | } |
| 175 | + | |
| 176 | + | void searchDll(BYTE* buffer, PResultInfo result, LPCWSTR filePath, char* dllsName, string fileDir) { |
| 177 | + | DWORD rdataLength; |
| 178 | + | BYTE* rdata = readRDataSection(buffer, &rdataLength); |
| 179 | + | if (rdata != 0) { |
| 180 | + | LPVOID str = (LPVOID)malloc(255); |
| 181 | + | DWORD begin = 0; |
| 182 | + | int fileDirLength = fileDir.length(); |
| 183 | + | |
| 184 | + | for (size_t i = 0; i < rdataLength; ++i) { |
| 185 | + | char ch = rdata[i]; |
| 186 | + | if (ch == '\0') { |
| 187 | + | if (i - begin > 10 && i - begin < 30) { |
| 188 | + | memcpy(str, rdata + begin, i + 1 - begin); |
| 189 | + | if (endsWithDLL((char*)str)) { |
| 190 | + | char fileFullPath[255] = { 0 }; |
| 191 | + | strcat(fileFullPath, fileDir.c_str()); |
| 192 | + | strcat(fileFullPath, (char*)str); |
| 193 | + | |
| 194 | + | if (filesystem::exists(filesystem::path(fileFullPath)) && containsIgnoreCase(dllsName, (char*)str) == NULL) |
| 195 | + | result->postLoadDlls.push_back(_strdup((char*)str)); |
| 196 | + | } |
| 197 | + | } |
| 198 | + | begin = i + 1; |
| 199 | + | } |
| 200 | + | } |
| 201 | + | |
| 202 | + | begin = 0; |
| 203 | + | for (size_t i = 0; i < rdataLength; i += 2) { |
| 204 | + | wchar_t ch = rdata[i]; |
| 205 | + | |
| 206 | + | if (ch == L'\0') { |
| 207 | + | if (i - begin > 10 && i - begin < 60) { |
| 208 | + | memcpy(str, rdata + begin, i + 2 - begin); |
| 209 | + | if (endsWithDLL((wchar_t*)str)) { |
| 210 | + | char fileFullPath[255] = { 0 }; |
| 211 | + | strcat(fileFullPath, fileDir.c_str()); |
| 212 | + | strcat(fileFullPath, ConvertWideToMultiByte((wchar_t*)str)); |
| 213 | + | |
| 214 | + | if (filesystem::exists(filesystem::path(fileFullPath)) && containsIgnoreCase(dllsName, ConvertWideToMultiByte((wchar_t*)str)) == NULL) |
| 215 | + | result->postLoadDlls.push_back(_strdup((char*)(wstring2string((wchar_t*)str).c_str()))); |
| 216 | + | } |
| 217 | + | } |
| 218 | + | begin = i + 2; |
| 219 | + | } |
| 220 | + | } |
| 221 | + | free(str); |
| 222 | + | } |
| 223 | + | } |
| 224 | + | |
| 225 | + | bool hasWritePermission(const std::string& directoryPath) { |
| 226 | + | std::string tempFilePath = directoryPath + "\\tmp"; |
| 227 | + | { |
| 228 | + | std::lock_guard<std::mutex> lock(mtx); |
| 229 | + | std::ofstream tempFile(tempFilePath); |
| 230 | + | |
| 231 | + | if (!tempFile.is_open()) { |
| 232 | + | return false; // 创建文件失败,目录没有写权限 |
| 233 | + | } |
| 234 | + | tempFile.close(); |
| 235 | + | std::filesystem::remove(tempFilePath); // 创建文件后立即删除 |
| 236 | + | } |
| 237 | + | return true; // 创建文件成功,目录有写权限 |
| 238 | + | } |
| 239 | + | |
| 240 | + | void printImportTableInfo(BYTE* buffer, PResultInfo result, LPCWSTR filePath) |
| 241 | + | { |
| 242 | + | const char* known_dlls[] = {"kernel32", "wow64cpu", "wowarmhw", "xtajit", "advapi32", "clbcatq", "combase", "COMDLG32", "coml2", "difxapi", "gdi32", "gdiplus", "IMAGEHLP", "IMM32", "MSCTF", "MSVCRT", "NORMALIZ", "NSI", "ole32", "OLEAUT32", "PSAPI", "rpcrt4", "sechost", "Setupapi", "SHCORE", "SHELL32", "SHLWAPI", "user32", "WLDAP32", "wow64cpu", "wow64", "wow64base", "wow64con", "wow64win", "WS2_32", "xtajit64"}; |
| 243 | + | string fileDir = GetDirectoryFromPath(ConvertWideToMultiByte(filePath)) + "\\"; |
| 244 | + | |
| 245 | + | if (hasWritePermission(fileDir)) |
| 246 | + | result->isWrite = true; |
| 247 | + | else |
| 248 | + | result->isWrite = false; |
| 249 | + | |
| 250 | + | PIMAGE_DOS_HEADER pDH = (PIMAGE_DOS_HEADER)buffer; |
| 251 | + | IMAGE_DATA_DIRECTORY directory; |
| 252 | + | DWORD THUNK_DATA_SIZE; |
| 253 | + | |
| 254 | + | if (*(PWORD)((size_t)pDH + pDH->e_lfanew + 0x18) == IMAGE_NT_OPTIONAL_HDR32_MAGIC) |
| 255 | + | { |
| 256 | + | PIMAGE_NT_HEADERS32 pNtH32 = PIMAGE_NT_HEADERS32((size_t)pDH + pDH->e_lfanew); |
| 257 | + | PIMAGE_OPTIONAL_HEADER32 pOH32 = &pNtH32->OptionalHeader; |
| 258 | + | |
| 259 | + | directory = pOH32->DataDirectory[1]; |
| 260 | + | THUNK_DATA_SIZE = 4; |
| 261 | + | result->bit = 32; |
| 262 | + | } |
| 263 | + | else { |
| 264 | + | PIMAGE_NT_HEADERS64 pNtH64 = PIMAGE_NT_HEADERS64((size_t)pDH + pDH->e_lfanew); |
| 265 | + | PIMAGE_OPTIONAL_HEADER64 pOH64 = &pNtH64->OptionalHeader; |
| 266 | + | |
| 267 | + | directory = pOH64->DataDirectory[1]; |
| 268 | + | THUNK_DATA_SIZE = 8; |
| 269 | + | result->bit = 64; |
| 270 | + | } |
| 271 | + | |
| 272 | + | PIMAGE_IMPORT_DESCRIPTOR ImportTable = PIMAGE_IMPORT_DESCRIPTOR(rvaToFOA(buffer, directory.VirtualAddress) + buffer); |
| 273 | + | //获取导入dll名称 |
| 274 | + | char* dllsName = (char*)malloc(0x2000); |
| 275 | + | memset(dllsName, 0, 0x2000); |
| 276 | + | while (ImportTable->Name) |
| 277 | + | { |
| 278 | + | char* pName = (char*)(rvaToFOA(buffer, ImportTable->Name) + buffer); |
| 279 | + | strcat(dllsName, pName); |
| 280 | + | ImportTable++; |
| 281 | + | } |
| 282 | + | |
| 283 | + | ImportTable = PIMAGE_IMPORT_DESCRIPTOR(rvaToFOA(buffer, directory.VirtualAddress) + buffer); |
| 284 | + | while (ImportTable->Name) |
| 285 | + | { |
| 286 | + | char* pName = (char*)(rvaToFOA(buffer, ImportTable->Name) + buffer); |
| 287 | + | DWORD nameSize = sizeof(known_dlls) / 4; |
| 288 | + | bool flag = true; |
| 289 | + | |
| 290 | + | for (int i = 0; i < nameSize; i++) |
| 291 | + | { |
| 292 | + | if (containsIgnoreCase(pName, known_dlls[i]) != NULL) |
| 293 | + | { |
| 294 | + | flag = false; |
| 295 | + | break; |
| 296 | + | } |
| 297 | + | } |
| 298 | + | |
| 299 | + | PIMAGE_THUNK_DATA INT = PIMAGE_THUNK_DATA(rvaToFOA(buffer, ImportTable->OriginalFirstThunk) + buffer); |
| 300 | + | PIMAGE_THUNK_DATA IAT = PIMAGE_THUNK_DATA(rvaToFOA(buffer, ImportTable->FirstThunk) + buffer); |
| 301 | + | PIMAGE_IMPORT_BY_NAME temp = { 0 }; |
| 302 | + | int count = 0; |
| 303 | + | while (INT->u1.AddressOfData)//当遍历到的是最后一个是时候是会为0,所以随便遍历一个就好 |
| 304 | + | { |
| 305 | + | if (!(INT->u1.Ordinal & 0x80000000)) |
| 306 | + | { |
| 307 | + | temp = (PIMAGE_IMPORT_BY_NAME)(rvaToFOA(buffer, INT->u1.AddressOfData) + buffer); |
| 308 | + | if (containsIgnoreCase(temp->Name, "loadlibrary") != NULL) |
| 309 | + | { |
| 310 | + | searchDll(buffer, result, filePath, dllsName, fileDir); |
| 311 | + | break; |
| 312 | + | } |
| 313 | + | } |
| 314 | + | INT = PIMAGE_THUNK_DATA((PBYTE)INT + THUNK_DATA_SIZE);//INT在INT数组中下移 |
| 315 | + | count++; |
| 316 | + | } |
| 317 | + | |
| 318 | + | char fileFullPath[255] = { 0 }; |
| 319 | + | strcat(fileFullPath, fileDir.c_str()); |
| 320 | + | strcat(fileFullPath, pName); |
| 321 | + | |
| 322 | + | if (filesystem::exists(filesystem::path(fileFullPath))) |
| 323 | + | flag = true; |
| 324 | + | |
| 325 | + | if (flag) |
| 326 | + | result->preLoadDlls.push_back(_strdup(pName)); |
| 327 | + | |
| 328 | + | ImportTable++; |
| 329 | + | } |
| 330 | + | |
| 331 | + | free(dllsName); |
| 332 | + | } |
| 333 | + | |
| 334 | + | BOOL VerifyFileSignature(LPCWSTR filePath) { |
| 335 | + | DWORD dwEncoding, dwContentType, dwFormatType; |
| 336 | + | HCERTSTORE hStore = NULL; |
| 337 | + | HCRYPTMSG hMsg = NULL; |
| 338 | + | BOOL bResult = FALSE; |
| 339 | + | |
| 340 | + | // Open the file and get the file handle |
| 341 | + | HANDLE hFile = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); |
| 342 | + | if (hFile == INVALID_HANDLE_VALUE) { |
| 343 | + | return FALSE; |
| 344 | + | } |
| 345 | + | |
| 346 | + | // Get the size of the file |
| 347 | + | DWORD dwFileSize = GetFileSize(hFile, NULL); |
| 348 | + | |
| 349 | + | // Read the file into a buffer |
| 350 | + | BYTE* pbFile = (BYTE*)malloc(dwFileSize); |
| 351 | + | DWORD dwBytesRead; |
| 352 | + | if (!ReadFile(hFile, pbFile, dwFileSize, &dwBytesRead, NULL)) { |
| 353 | + | CloseHandle(hFile); |
| 354 | + | free(pbFile); |
| 355 | + | return FALSE; |
| 356 | + | } |
| 357 | + | |
| 358 | + | // Verify the signature |
| 359 | + | bResult = CryptQueryObject(CERT_QUERY_OBJECT_FILE, filePath, CERT_QUERY_CONTENT_FLAG_ALL, |
| 360 | + | CERT_QUERY_FORMAT_FLAG_ALL, 0, &dwEncoding, &dwContentType, |
| 361 | + | &dwFormatType, &hStore, &hMsg, NULL); |
| 362 | + | if (!bResult) { |
| 363 | + | CloseHandle(hFile); |
| 364 | + | free(pbFile); |
| 365 | + | return FALSE; |
| 366 | + | } |
| 367 | + | |
| 368 | + | PIMAGE_DOS_HEADER pDH = (PIMAGE_DOS_HEADER)pbFile; |
| 369 | + | IMAGE_DATA_DIRECTORY directory; |
| 370 | + | |
| 371 | + | if (*(PWORD)((size_t)pDH + pDH->e_lfanew + 0x18) == IMAGE_NT_OPTIONAL_HDR32_MAGIC) |
| 372 | + | { |
| 373 | + | PIMAGE_NT_HEADERS32 pNtH32 = PIMAGE_NT_HEADERS32((size_t)pDH + pDH->e_lfanew); |
| 374 | + | PIMAGE_OPTIONAL_HEADER32 pOH32 = &pNtH32->OptionalHeader; |
| 375 | + | |
| 376 | + | directory = pOH32->DataDirectory[1]; |
| 377 | + | } |
| 378 | + | else { |
| 379 | + | PIMAGE_NT_HEADERS64 pNtH64 = PIMAGE_NT_HEADERS64((size_t)pDH + pDH->e_lfanew); |
| 380 | + | PIMAGE_OPTIONAL_HEADER64 pOH64 = &pNtH64->OptionalHeader; |
| 381 | + | |
| 382 | + | directory = pOH64->DataDirectory[1]; |
| 383 | + | } |
| 384 | + | //没有导入表的程序 |
| 385 | + | if (directory.VirtualAddress == 0) { |
| 386 | + | CloseHandle(hFile); |
| 387 | + | free(pbFile); |
| 388 | + | return FALSE; |
| 389 | + | } |
| 390 | + | |
| 391 | + | /*string md5 = calculateMD5(pbFile, dwFileSize); |
| 392 | + | { |
| 393 | + | std::lock_guard<std::mutex> lock(mtx); |
| 394 | + | if (md5Map.find(md5) != md5Map.end()) |
| 395 | + | return FALSE; |
| 396 | + | |
| 397 | + | md5Map[md5] = filePath; |
| 398 | + | }*/ |
| 399 | + | |
| 400 | + | ResultInfo* result = new ResultInfo; |
| 401 | + | result->filePath = wstring2string(filePath); |
| 402 | + | |
| 403 | + | printImportTableInfo(pbFile, result, filePath); |
| 404 | + | |
| 405 | + | if (result->preLoadDlls.size() > 0 || result->postLoadDlls.size() > 0) { |
| 406 | + | { |
| 407 | + | std::lock_guard<std::mutex> lock(mtx); |
| 408 | + | results.push_back(result); |
| 409 | + | } |
| 410 | + | } |
| 411 | + | |
| 412 | + | // Clean up resources |
| 413 | + | if (hMsg != NULL) |
| 414 | + | CryptMsgClose(hMsg); |
| 415 | + | if (hStore != NULL) |
| 416 | + | CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG); |
| 417 | + | CloseHandle(hFile); |
| 418 | + | free(pbFile); |
| 419 | + | |
| 420 | + | return TRUE; |
| 421 | + | } |