diff --git a/README.md b/README.md index d700d1b..021714c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # EmoCheck +[![GitHub release](https://img.shields.io/github/release/jpcertcc/emocheck.svg)](https://github.com/jpcertcc/emocheck/releases) +[![Github All Releases](https://img.shields.io/github/downloads/jpcertcc/emocheck/total.svg)](http://www.somsubhra.com/github-release-stats/?username=jpcertcc&repository=emocheck) + Emotet detection tool for Windows OS. ## How to use @@ -10,48 +13,104 @@ Emotet detection tool for Windows OS. ## Download -Please download from [Releases](https://github.com/JPCERTCC/EmoCheck/releases) page. +Please download from the [Releases](https://github.com/JPCERTCC/EmoCheck/releases) page. + +Latest hash: > emocheck_x86.exe -> MD5 : 9508DACDF443B422D159160E02043045 -> SHA256: 3F9BEFD9287923A844FA7F38DEADFE2238380398E1F4F4C902A18CD4CCF1BFA0 +> MD5 : 89863A79D531E2730D450F2D1C99EB6C +> SHA256: 5A459538DE0A5B1C270C0617191A71D23EA6C705650761EF9B7095A736AF7301 > emocheck_x64.exe -> MD5 : 9E1B8BE8402A51B8FEE0B590B4965060 -> SHA256: C8CC438BF271DFAA110C58C748C54175823269DA7202EA19FC75EEEA359FAEB5 +> MD5 : 94005A6447CA810619FF24D67EF67A93 +> SHA256: 65838C35D03FE36E9DBA1408E2278F8BC282B1319FEFAABEE4491B45E1254163 + +## Command options + +(since v0.0.2) + +- Specify output directory for the report (default: current directory) + - `/output [your output directory]` or `-output [your output directory]` +- No console output + - `/quiet` or `-quiet` +- Export the report in JSON style + - `/json` or `-json` +- Debug mode (no report) + - `/debug` or `-debug` +- Show help + - `/help` or `-help` ## How EmoCheck detects Emotet -Emotet generates their process name from a specific word dictionary and C drive serial. +(v0.0.1) +Emotet generates their process name from a specific word dictionary and C drive serial number. EmoCheck scans the running process on the host, and find Emotet process from their process name. +(added in v0.0.2) +Emotet keeps their encoded process name in a specific registry key. +EmoCheck looks up and decode the registry value, and find it from the process list. + ## Sample Report +Text stlye: + ```txt -[Emocheck v0.0.1] -Scan time: 2020-02-03 13:06:20 +[Emocheck v0.0.2] +Scan time: 2020-02-10 13:06:20 ____________________________________________________ -[Result] +[Result] Detected Emotet process. -[Emotet Process] - Process Name : khmerbid.exe - Process ID : 10508 - Image Path : C:\Users\[username]\AppData\Local\khmerbid.exe +[Emotet Process] + Process Name : mstask.exe + Process ID : 716 + Image Path : C:\Users\[username]\AppData\Local\mstask.exe ____________________________________________________ Please remove or isolate the suspicious execution file. ``` +JSON style (added in v0.0.2): + +```json +{ + "scan_time":"2020-02-10 13:06:20", + "hostname":"[your hostname]", + "emocheck_version":"0.0.2", + "is_infected":"yes", + "emotet_processes":[ + { + "process_name":"mstask.exe", + "process_id":"716", + "image_path":"C:\\Users\\[username]\\AppData\\Local\\mstask.exe" + } + ] +} +``` + The report will be exported to the following path. -- [path of emocheck.exe]\yyyymmddhhmmss_emocheck.txt +(v0.0.1) +`[current directory]\yyyymmddhhmmss_emocheck.txt` + +(since v0.0.2) +`[output path]\[computer name]_yyyymmddhhmmss_emocheck.txt` +`[output path]\[computer name]_yyyymmddhhmmss_emocheck.json` ## Screenshot +(v0.0.1)
+## Releases + +- (Feb. 3, 2020) v0.0.1 + - Initial release +- (Feb. 10, 2020) v0.0.2 + - update detecting method + - add options + ## Notes ### Tested environments diff --git a/README_jp.md b/README_jp.md index dee87d2..f685f8e 100644 --- a/README_jp.md +++ b/README_jp.md @@ -1,62 +1,115 @@ # EmoCheck -Windows OS 向け Emotet 検知ツール +[![GitHub release](https://img.shields.io/github/release/jpcertcc/emocheck.svg)](https://github.com/jpcertcc/emocheck/releases) +[![Github All Releases](https://img.shields.io/github/downloads/jpcertcc/emocheck/total.svg)](http://www.somsubhra.com/github-release-stats/?username=jpcertcc&repository=emocheck) + +Windows OS 用 Emotet 検知ツール ## 使用方法 1. Releases からツールをダウンロード 2. 感染が疑われるホストでツールを実行 -3. 出力されるレポートテキストを確認 +3. 出力されるレポートを確認 ## ダウンロード 以下のページからダウンロードできます。 - [Release](https://github.com/JPCERTCC/EmoCheck/releases) + [Releases](https://github.com/JPCERTCC/EmoCheck/releases) なお、ファイルのハッシュ値は以下の通りです。 > emocheck_x86.exe -> MD5 : 9508DACDF443B422D159160E02043045 -> SHA256: 3F9BEFD9287923A844FA7F38DEADFE2238380398E1F4F4C902A18CD4CCF1BFA0 +> MD5 : 89863A79D531E2730D450F2D1C99EB6C +> SHA256: 5A459538DE0A5B1C270C0617191A71D23EA6C705650761EF9B7095A736AF7301 > emocheck_x64.exe -> MD5 : 9E1B8BE8402A51B8FEE0B590B4965060 -> SHA256: C8CC438BF271DFAA110C58C748C54175823269DA7202EA19FC75EEEA359FAEB5 +> MD5 : 94005A6447CA810619FF24D67EF67A93 +> SHA256: 65838C35D03FE36E9DBA1408E2278F8BC282B1319FEFAABEE4491B45E1254163 + +## コマンドオプション + +(v0.0.2 追加) + +- レポート出力先ディレクトリ指定 (デフォルト: カレントディレクトリ) + - `/output [出力先ディレクトリ]` または `-output [出力先ディレクトリ]` +- コマンドライン出力抑止 + - `/quiet` または `-quiet` +- JSON形式でのレポート出力 + - `/json` または `-json` +- 詳細表示(レポート出力なし) + - `/debug` または `-debug` +- ヘルプ表示 + - `/help` または `-help` ## Emotetの検知方法 -EmoCheck ではホストのプロセス名から Emotet のプロセスを検知します。 +EmoCheck はホスト上のプロセス一覧から Emotet のプロセスを検知します。 ## レポート例 -Emotetが検知された場合、以下のようなレポートが作成されます。 +Emotetが検知された場合、以下のようなレポートが作成されます。 + +テキスト形式: ```txt -[Emocheck v0.0.1] -プログラム実行時刻: 2020-02-03 13:45:51 +[Emocheck v0.0.2] +プログラム実行時刻: 2020-02-10 10:45:51 ____________________________________________________ [結果] Emotetを検知しました [詳細] - プロセス名 : khmerbid.exe - プロセスID : 6132 - イメージパス : C:\Users\[username]\AppData\Local\khmerbid.exe + プロセス名 : mstask.exe + プロセスID : 716 + イメージパス : C:\Users\[ユーザー名]\AppData\Local\mstask.exe ____________________________________________________ -イメージパスの実行ファイルを隔離/削除してください。 +不審なイメージパスの実行ファイルを隔離/削除してください。 +``` + +JSON形式 (v0.0.2 追加): + +```json +{ + "scan_time":"2020-02-10 10:45:51", + "hostname":"[ホスト名]", + "emocheck_version":"0.0.2", + "is_infected":"yes", + "emotet_processes":[ + { + "process_name":"mstask.exe", + "process_id":"716", + "image_path":"C:\\Users\\[ユーザー名]\\AppData\\Local\\mstask.exe" + } + ] +} ``` レポートは以下のパスに生成されます。 -- [emocheck.exe を実行したフォルダー]\yyyymmddhhmmss_emocheck.txt +(v0.0.1) +`[カレントディレクトリ]\yyyymmddhhmmss_emocheck.txt` + +(v0.0.2 以降) +`[指定ディレクトリ]\[ホスト名]_yyyymmddhhmmss_emocheck.txt` +`[指定ディレクトリ]\[ホスト名]_yyyymmddhhmmss_emocheck.json` + +Emotetが検知された場合、以下のようなレポートが作成されます。 ## スクリーンショット +(v0.0.1)
+## 更新履歴 + +- (2020/02/03) v0.0.1 +- (2020/02/10) v0.0.2 + - 検知手法の追加 + - コマンドオプションの追加 + ## その他 ### 動作確認環境 diff --git a/emocheck/emocheck.cpp b/emocheck/emocheck.cpp index bda32da..05c0971 100644 --- a/emocheck/emocheck.cpp +++ b/emocheck/emocheck.cpp @@ -1,4 +1,4 @@ -/** +/* * LICENSE * Please reffer to the LICENSE.txt in the https://github.com/JPCERTCC/EmoCheck/ */ @@ -6,7 +6,9 @@ #include "emocheck.h" // standard modules +#include #include +#include #include // windows basic modules @@ -18,6 +20,8 @@ namespace emocheck { +bool is_debug = false; + std::string WideCharToString(wchar_t *wide_char) { int buf_size = WideCharToMultiByte(CP_OEMCP, 0, wide_char, -1, (char *)NULL, 0, NULL, NULL); char *char_ptr = new char[buf_size]; @@ -26,7 +30,122 @@ std::string WideCharToString(wchar_t *wide_char) { std::string result(char_ptr, char_ptr + buf_size - 1); delete[] char_ptr; - return (result); + return result; +} + +std::vector IntToBytes(unsigned int integer) { + std::vector bytes_vector(4); + for (int i = 0; i < 4; i++) + bytes_vector[i] = (integer >> (i * 8)); + return bytes_vector; +} + +std::string QueryRegistry(unsigned int serial, HKEY root, std::wstring key_path) { + DWORD buf_len; + LSTATUS status; + HKEY hKey; + std::wstringstream wstring_serial; + std::string filename = ""; + + // convert integer to wstring stream + wstring_serial << std::hex << serial; + + status = RegOpenKeyEx(root, key_path.c_str(), 0, KEY_READ, &hKey); + if (status) { + // std::cerr << "[*] Emotet registry key not found. status: " << status << std::endl; + return filename; + } + + // debug message + if (is_debug) { + if (root == HKEY_CURRENT_USER) { + std::wcout << L"[DEBUG] Open registry: HKCU\\" << key_path << std::endl; + } else if (root == HKEY_LOCAL_MACHINE) { + std::wcout << L"[DEBUG] Open registry: HKLM\\" << key_path << std::endl; + } else { + std::wcout << L"[DEBUG] Open registry: " << std::hex << root << L" path: " << key_path << std::endl; + } + } + + status = RegGetValue(hKey, nullptr, wstring_serial.str().c_str(), RRF_RT_REG_BINARY, nullptr, nullptr, &buf_len); + if (status) { + // std::cerr << "[*] Emotet registry key not found. status: " << status << std::endl; + return filename; + } + + // debug message + if (is_debug) { + std::wcout << L"[DEBUG] Found suspicous subkey: " << wstring_serial.str() << std::endl; + std::cout << "[DEBUG] Data length: " << buf_len << std::endl; + } + + // buffur to save binary_data in registry value. + unsigned char *buffer = new unsigned char[buf_len]; + + RegGetValue(hKey, nullptr, wstring_serial.str().c_str(), RRF_RT_REG_BINARY, nullptr, buffer, &buf_len); + + // XOR registry value with drive serial num; + unsigned char *decoded_chars = new unsigned char[buf_len]; + std::vector xor_key = IntToBytes(serial); + + // debug message + if (is_debug) { + std::cout << "[DEBUG] Binary data:\n[DEBUG] "; + for (unsigned int i = 0; i < buf_len; i++) { + printf("0x%02x ", buffer[i]); + } + std::cout << std::endl; + } + + for (unsigned int i = 0; i < buf_len; i++) { + decoded_chars[i] = (xor_key[i % 4] ^ buffer[i]); + } + + // debug message + if (is_debug) { + std::cout << "[DEBUG] Decoded data:\n[DEBUG] "; + for (unsigned int i = 0; i < buf_len; i++) { + printf("0x%02x ", decoded_chars[i]); + } + std::cout << std::endl; + } + + for (unsigned int i = 0; i < buf_len; i++) { + if (0x20 < int(decoded_chars[i]) && int(decoded_chars[i]) < 0x7e) { + filename += decoded_chars[i]; + } + } + // debug message + if (is_debug) { + std::cout << "[DEBUG] Decoded string: " << filename << std::endl; + } + return filename; +} + +std::vector GetEmotetFileNameFromRegistry(int serial) { + std::vector filenames; + std::string filename; + std::wstring reg_key_path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"; + std::wstring reg_key_path_admin = L"Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Explorer"; + + // if emotet runs with user auth.(x64,x32) + filename = QueryRegistry(serial, HKEY_CURRENT_USER, reg_key_path); + if (filename.length() > 0) { + filenames.push_back(filename); + } + + // if emotet runs with admin auth. (x32) + filename = QueryRegistry(serial, HKEY_LOCAL_MACHINE, reg_key_path); + if (filename.length() > 0) { + filenames.push_back(filename); + } + + // if emotet runs with admin auth. (x64) + filename = QueryRegistry(serial, HKEY_LOCAL_MACHINE, reg_key_path_admin); + if (filename.length() > 0) { + filenames.push_back(filename); + } + return filenames; } std::string ConvertDivecePath(wchar_t *imagepath) { @@ -55,25 +174,37 @@ std::string GetImageFileName(DWORD pid) { wchar_t imagepath[MAX_PATH + 1]; hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); if (hProcess == NULL) { - std::clog << "[!!] Error: Fail to get handle from pid " << pid << std::endl; - std::exit(1); + std::clog << "[!!] Error: Failed to get handle from pid " << pid << std::endl; + return std::string(""); } if (GetProcessImageFileName(hProcess, imagepath, sizeof(imagepath) / sizeof(*imagepath)) == 0) { - std::clog << "[!!] Error: Fail to get image file name from pid " << pid << std::endl; - std::exit(1); + std::clog << "[!!] Error: Failed to get image file name from pid " << pid << std::endl; + return std::string(""); } else { return ConvertDivecePath(imagepath); } } -int GetVolumeSerialNumber() { +unsigned int GetVolumeSerialNumber() { + wchar_t windows_directory_path[MAX_PATH + 1] = {0}; wchar_t volumename[MAX_PATH + 1] = {0}; wchar_t filesystemname[MAX_PATH + 1] = {0}; + std::wstring drive_letter; DWORD serialnumber = 0; DWORD max_componentlen = 0; DWORD filesystem_flags = 0; + + // get drive letter from system drive + if (GetWindowsDirectory(windows_directory_path, MAX_PATH)) { + for (int i = 0; i < 3; i++) { + drive_letter += windows_directory_path[i]; + } + } else { + drive_letter = std::wstring(L"C:\\"); + } + if (GetVolumeInformation( - L"C:\\", + drive_letter.c_str(), volumename, sizeof(volumename), &serialnumber, @@ -81,7 +212,6 @@ int GetVolumeSerialNumber() { &filesystem_flags, filesystemname, sizeof(filesystemname)) == TRUE) { - // std::cout << "[debug] Serial Number :" << std::hex << serialnumber << std::endl; } else { std::clog << "[!] GetVolumeInformation() failed, error " << GetLastError() << std::endl; std::exit(1); @@ -122,6 +252,8 @@ std::string GenerateEmotetProcessName() { std::string keywords; std::string keyword; + seed = GetVolumeSerialNumber(); + keywords = "duck,mfidl,targets,ptr,khmer,purge,metrics,acc,inet,msra,symbol,driver," "sidebar,restore,msg,volume,cards,shext,query,roam,etw,mexico,basic,url," @@ -132,7 +264,6 @@ std::string GenerateEmotetProcessName() { keylen = int(keywords.length()); // first round - seed = GetVolumeSerialNumber(); q = seed / keylen; mod = seed % keylen; keyword += GetWord(keywords, mod, keylen); @@ -142,11 +273,10 @@ std::string GenerateEmotetProcessName() { mod = seed % keylen; keyword += GetWord(keywords, mod, keylen); - // std::cout << "[debug] Emotet process name on this host is " << keyword.c_str() << ".exe" << std::endl; return keyword; } -std::vector ScanEmotetProcess(std::string keyword) { +std::vector ScanEmotetProcess(std::vector keywords) { PROCESSENTRY32 pe = {sizeof(PROCESSENTRY32)}; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); BOOL result; @@ -155,24 +285,52 @@ std::vector ScanEmotetProcess(std::string keyword) { for (result = Process32First(snapshot, &pe); result == TRUE; result = Process32Next(snapshot, &pe)) { std::string process_name = WideCharToString(pe.szExeFile); - if (process_name.find(keyword) != std::string::npos) { - EmotetProcess emotet_process; - emotet_process.pid = pe.th32ProcessID; - emotet_process.process_name = process_name; - emotet_process.image_path = GetImageFileName(emotet_process.pid); - emotet_processes.push_back(emotet_process); + for (unsigned int i = 0; i < keywords.size(); i++) { + if (process_name == keywords[i] + std::string(".exe")) { + EmotetProcess emotet_process; + emotet_process.pid = int(pe.th32ProcessID); + emotet_process.process_name = process_name; + emotet_process.image_path = GetImageFileName(emotet_process.pid); + emotet_processes.push_back(emotet_process); + } } } return emotet_processes; } -std::vector ScanEmotet() { +std::vector ScanEmotet(bool debug) { + std::vector filenames; std::string emotet_process_name; + std::vector emotet_process_names; std::vector emotet_processes; + is_debug = debug; + + // old emotet (- 2020/02/05) emotet_process_name = GenerateEmotetProcessName(); - emotet_processes = ScanEmotetProcess(emotet_process_name); + emotet_process_names.push_back(emotet_process_name); + + // new emotet (2020/02/06 -) + filenames = GetEmotetFileNameFromRegistry(GetVolumeSerialNumber()); + for (unsigned int i = 0; i < filenames.size(); i++) { + if (filenames[i].length() != 0) { + emotet_process_names.push_back(filenames[i]); + } + } + // unique emotet processes names + std::sort(emotet_process_names.begin(), emotet_process_names.end()); + emotet_process_names.erase(std::unique(emotet_process_names.begin(), emotet_process_names.end()), emotet_process_names.end()); + + // debug message + if (is_debug) { + std::cout << "[DEBUG] Search following name(s) from running processes: "; + for (unsigned int i = 0; i < emotet_process_names.size(); i++) { + std::cout << emotet_process_names[i] << " "; + } + std::cout << std::endl; + } + emotet_processes = ScanEmotetProcess(emotet_process_names); return emotet_processes; } diff --git a/emocheck/emocheck.h b/emocheck/emocheck.h index 731c299..acd0859 100644 --- a/emocheck/emocheck.h +++ b/emocheck/emocheck.h @@ -1,4 +1,4 @@ -/** +/* * LICENSE * Please reffer to the LICENSE.txt in the https://github.com/JPCERTCC/EmoCheck/ */ @@ -11,8 +11,8 @@ namespace emocheck { -static char EMOCHECK_VERSION[] = "0.0.1"; -static char EMOCHECK_RELEASE_DATE[] = "2020/02/03"; +static char EMOCHECK_VERSION[] = "0.0.2"; +static char EMOCHECK_RELEASE_DATE[] = "2020/02/10"; static char EMOCHECK_URL[] = "https://github.com/JPCERTCC/EmoCheck"; static char LINE_DELIMITER[] = "____________________________________________________\n"; @@ -25,7 +25,8 @@ struct EmotetProcess { std::string image_path; }; -std::vector ScanEmotet(); +std::string WideCharToString(wchar_t *wide_char); +std::vector ScanEmotet(bool); } //namespace emocheck diff --git a/emocheck/emocheck.vcxproj b/emocheck/emocheck.vcxproj index a55ced2..fa1b4be 100644 --- a/emocheck/emocheck.vcxproj +++ b/emocheck/emocheck.vcxproj @@ -72,9 +72,11 @@ true + false true + false false @@ -82,36 +84,79 @@ false + false - Use - Level3 + NotUsing + Level4 Disabled true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - pch.h + + + MultiThreaded + + + stdcpp17 + /utf-8 %(AdditionalOptions) Console - true + false + + + + + false + + + + + + false + + + + - Use - Level3 + NotUsing + Level4 Disabled true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - pch.h + + + MultiThreaded + stdcpp17 + + + /utf-8 %(AdditionalOptions) Console - true + false + + + + + false + + + + + + false + + + + @@ -123,34 +168,72 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - pch.h + + + MultiThreaded + + + stdcpp17 + /utf-8 %(AdditionalOptions) Console true true false - Default + + + + + false + + + + + + false + + + + - Use - Level3 + NotUsing + Level4 MaxSpeed true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - pch.h + + + MultiThreaded + + + stdcpp17 + /utf-8 %(AdditionalOptions) Console true true - true + false + + + + + false + + + + + + + diff --git a/emocheck/main.cpp b/emocheck/main.cpp index 0451a00..20f74f2 100644 --- a/emocheck/main.cpp +++ b/emocheck/main.cpp @@ -1,4 +1,4 @@ -/** +/* * LICENSE * Please reffer to the LICENSE.txt in the https://github.com/JPCERTCC/EmoCheck/ */ @@ -7,19 +7,38 @@ #include "emocheck.h" // standard modules +#include #include -#include #include #include // windows basic module #include -namespace emocheck -{ +// defines +#define PARAM_SWITCH1 '/' +#define PARAM_SWITCH2 '-' +#define PARAM_QUIET "quiet" +#define PARAM_DEBUG "debug" +#define PARAM_OUTPUT "output" +#define PARAM_HELP "help" +#define PARAM_JSON "json" -void PrintBanner() -{ +namespace emocheck { + +bool is_param(const char *str) { + if (!str) return false; + + const size_t len = strlen(str); + if (len < 2) return false; + + if (str[0] == PARAM_SWITCH1 || str[0] == PARAM_SWITCH2) { + return true; + } + return false; +} + +void PrintBanner() { char banner[] = " ______ _____ _ _ \n" "| ____| / ____| | | | \n" @@ -40,53 +59,59 @@ void PrintBanner() // unsigned short int usrDefLangId = GetUserDefaultLangID(); } -void PrintReport(std::vector emotet_processes) -{ - if (GetUserDefaultLangID() == LANG_ID_JP) - { +void PrintHelp() { + if (GetUserDefaultLangID() == LANG_ID_JP) { + // Japanese help + std::cout << "[オプション説明]\n" + << "コマンドラインの出力抑止:\n\t /quiet または -quiet\n" + << "JSON形式でのレポート出力:\n\t /json または -json\n" + << "レポート出力先ディレクトリ指定 (デフォルト カレントディレクトリ):\n\t /output [出力先ディレクトリ] または -output [出力先ディレクトリ]\n" + << "詳細表示:\n\t/debug または -debug" << std::endl; + } else { + // English Help + std::cout << "[Options]\n" + << "Suppress command line output:\n\t/quiet or -quiet\n" + << "Export report in JSON sytle:\n\t/json or -json\n" + << "Set output directory (default: current directory ):\n\t/output [output directory] or -output [output directory]\n" + << "Debug mode:\n\t/debug or -debug" << std::endl; + } +} + +void PrintReport(std::vector emotet_processes) { + if (GetUserDefaultLangID() == LANG_ID_JP) { // Japanese Report - if (emotet_processes.size() > 0) - { + if (emotet_processes.size() > 0) { std::cout.imbue(std::locale("")); - for (int i = 0; i < emotet_processes.size(); ++i) - { + for (unsigned int i = 0; i < emotet_processes.size(); ++i) { std::cout << "[!!] Emotet 検知" << "\n" - << " プロセス名 : " << emotet_processes[i].process_name.c_str() << "\n" + << " プロセス名 : " << emotet_processes[i].process_name << "\n" << " プロセスID : " << emotet_processes[i].pid << "\n" - << " イメージパス : " << emotet_processes[i].image_path.c_str() << std::endl; + << " イメージパス : " << emotet_processes[i].image_path << std::endl; } std::cout << LINE_DELIMITER << std::endl; std::cout << "Emotetのプロセスが見つかりました。\n" - << "イメージパスの実行ファイルを隔離/削除してください。\n" + << "不審なイメージパスの実行ファイルを隔離/削除してください。\n" << std::endl; - } - else - { + } else { std::cout << "Emotetは検知されませんでした。\n" << std::endl; } - } - else - { + } else { // English Report - if (emotet_processes.size() > 0) - { - for (int i = 0; i < emotet_processes.size(); ++i) - { + if (emotet_processes.size() > 0) { + for (unsigned int i = 0; i < emotet_processes.size(); ++i) { std::cout << "[!!] Detected" << "\n" - << " Process Name: " << emotet_processes[i].process_name.c_str() << "\n" + << " Process Name: " << emotet_processes[i].process_name << "\n" << " PID : " << emotet_processes[i].pid << "\n" - << " Image Path : " << emotet_processes[i].image_path.c_str() << std::endl; + << " Image Path : " << emotet_processes[i].image_path << std::endl; } std::cout << LINE_DELIMITER << std::endl; - std::cout << "Emotet had be detected.\n" + std::cout << "Emotet had been detected.\n" << "Please remove or isolate the suspicious execution file.\n" << std::endl; - } - else - { + } else { std::cout << "No detection.\n" << std::endl; } @@ -94,100 +119,249 @@ void PrintReport(std::vector emotet_processes) return; } -void WriteReport(std::vector emotet_processes) -{ - char filename[28]; +void WriteReport(std::vector emotet_processes, bool is_quiet, std::string output_path) { + std::string filename; + char time_file[16]; char time_iso8601[20]; + wchar_t computer_name[256] = {'\0'}; + unsigned long dword_size = sizeof(computer_name) / sizeof(computer_name[0]); + std::string hostname; + + if (GetComputerName(computer_name, &dword_size)) { + hostname = emocheck::WideCharToString(computer_name); + } else { + hostname = std::string(""); + } time_t t = time(nullptr); struct tm local_time; localtime_s(&local_time, &t); std::strftime(time_iso8601, 20, "%Y-%m-%d %H:%M:%S", &local_time); - std::strftime(filename, 28, "%Y%m%d%H%M%S_emocheck.txt", &local_time); + std::strftime(time_file, 16, "%Y%m%d%H%M%S", &local_time); - std::ofstream outputfile(filename); + filename += output_path; + filename += "\\"; + filename += std::string(hostname); + filename += std::string("_"); + filename += std::string(time_file); + filename += std::string("_emocheck.txt"); - if (GetUserDefaultLangID() == LANG_ID_JP) - { + std::ofstream outputfile(filename.c_str()); + + if (GetUserDefaultLangID() == LANG_ID_JP) { // Japanese Report - outputfile << "[Emocheck v" << EMOCHECK_VERSION << "]" << std::endl; + outputfile << "[EmoCheck v" << EMOCHECK_VERSION << "]" << std::endl; outputfile << "プログラム実行時刻: " << time_iso8601 << std::endl; outputfile << LINE_DELIMITER << std::endl; - if (emotet_processes.size() > 0) - { + if (emotet_processes.size() > 0) { outputfile << "[結果]\n" << "Emotetを検知しました。\n" << std::endl; - for (int i = 0; i < emotet_processes.size(); ++i) - { + for (unsigned int i = 0; i < emotet_processes.size(); ++i) { outputfile << "[詳細]\n" - << " プロセス名 : " << emotet_processes[i].process_name.c_str() << "\n" + << " プロセス名 : " << emotet_processes[i].process_name << "\n" << " プロセスID : " << emotet_processes[i].pid << "\n" - << " イメージパス : " << emotet_processes[i].image_path.c_str() << std::endl; + << " イメージパス : " << emotet_processes[i].image_path << std::endl; } outputfile << LINE_DELIMITER << std::endl; outputfile << "イメージパスの実行ファイルを隔離/削除してください。" << std::endl; - } - else - { + } else { outputfile << "[結果]\n" << "検知しませんでした。" << std::endl; } outputfile.close(); - std::cout.imbue(std::locale("")); - std::cout << "以下のファイルに結果を出力しました。" << std::endl; - std::cout << "\n\t" << filename << "\n" - << std::endl; - std::cout << "ツールのご利用ありがとうございました。\n" - << std::endl; - } - else - { + if (!is_quiet) { + std::cout.imbue(std::locale("")); + std::cout << "以下のファイルに結果を出力しました。" << std::endl; + std::cout << "\n\t" << filename << "\n" + << std::endl; + std::cout << "ツールのご利用ありがとうございました。\n" + << std::endl; + } + } else { // English Report - outputfile << "[Emocheck v" << EMOCHECK_VERSION << "]" << std::endl; + outputfile << "[EmoCheck v" << EMOCHECK_VERSION << "]" << std::endl; outputfile << "Scan time: " << time_iso8601 << std::endl; outputfile << LINE_DELIMITER << std::endl; - if (emotet_processes.size() > 0) - { + if (emotet_processes.size() > 0) { outputfile << "[Result] \nDetected Emotet process.\n" << std::endl; - for (int i = 0; i < emotet_processes.size(); ++i) - { + for (unsigned int i = 0; i < emotet_processes.size(); ++i) { outputfile << "[Emotet Process] \n" - << " Process Name : " << emotet_processes[i].process_name.c_str() << "\n" + << " Process Name : " << emotet_processes[i].process_name << "\n" << " Process ID : " << emotet_processes[i].pid << "\n" - << " Image Path : " << emotet_processes[i].image_path.c_str() << std::endl; + << " Image Path : " << emotet_processes[i].image_path << std::endl; } outputfile << LINE_DELIMITER << std::endl; outputfile << "Please remove or isolate the suspicious execution file." << std::endl; - } - else - { + } else { outputfile << "[Result] \nNo detection." << std::endl; } outputfile.close(); - std::cout << "Report has exported to following file." << std::endl; - std::cout << "\n\t" << filename << "\n" - << std::endl; - std::cout << "Thank you for using our tool.\n" - << std::endl; + if (!is_quiet) { + std::cout << "Report has exported to following file." << std::endl; + std::cout << "\n\t" << filename << "\n" + << std::endl; + std::cout << "Thank you for using our tool.\n" + << std::endl; + } } return; } -} // namespace emocheck +std::string EscapeBackSlash(std::string s) { + std::string target = "\\"; + std::string replacement = "\\\\"; + if (!target.empty()) { + std::string::size_type pos = 0; + while ((pos = s.find(target, pos)) != std::string::npos) { + s.replace(pos, target.length(), replacement); + pos += replacement.length(); + } + } + return s; +} + +void JsonReport(std::vector emotet_processes, bool is_quiet, std::string output_path) { + std::string filename; + char time_file[16]; + char time_iso8601[20]; + wchar_t computer_name[256] = {'\0'}; + unsigned long dword_size = sizeof(computer_name) / sizeof(computer_name[0]); + std::string hostname; + + if (GetComputerName(computer_name, &dword_size)) { + hostname = emocheck::WideCharToString(computer_name); + } else { + hostname = std::string(""); + } -int main() -{ + time_t t = time(nullptr); + struct tm local_time; + + localtime_s(&local_time, &t); + std::strftime(time_iso8601, 20, "%Y-%m-%d %H:%M:%S", &local_time); + std::strftime(time_file, 16, "%Y%m%d%H%M%S", &local_time); + + filename += output_path; + filename += "\\"; + filename += std::string(hostname); + filename += std::string("_"); + filename += std::string(time_file); + filename += std::string("_emocheck.json"); + + std::ofstream outputfile(filename.c_str()); + + outputfile << "{\n \"scan_time\":\"" << time_iso8601 << "\",\n" + << " \"hostname\":\"" << hostname << "\",\n" + << " \"emocheck_version\":\"" << EMOCHECK_VERSION << "\"," << std::endl; + if (emotet_processes.size() > 0) { + outputfile << " \"is_infected\":\"yes\",\n \"emotet_processes\":[" << std::endl; + for (unsigned int i = 0; i < emotet_processes.size(); ++i) { + outputfile << " {\n" + << " \"process_name\":\"" << emotet_processes[i].process_name << "\",\n" + << " \"process_id\":\"" << emotet_processes[i].pid << "\",\n" + << " \"image_path\":\"" << EscapeBackSlash(emotet_processes[i].image_path) << "\"" << std::endl; + if (i == emotet_processes.size() - 1) { + outputfile << " }" << std::endl; + } else { + outputfile << " }," << std::endl; + } + } + outputfile << " ]\n}" << std::endl; + } else { + outputfile << " \"is_infected\":\"no\"\n}" << std::endl; + } + outputfile.close(); + if (!is_quiet) { + if (GetUserDefaultLangID() == LANG_ID_JP) { + std::cout.imbue(std::locale("")); + std::cout << "以下のファイルに結果を出力しました。" << std::endl; + std::cout << "\n\t" << filename << "\n" + << std::endl; + std::cout << "ツールのご利用ありがとうございました。\n" + << std::endl; + } else { + std::cout << "Report has exported to following file." << std::endl; + std::cout << "\n\t" << filename << "\n" + << std::endl; + std::cout << "Thank you for using our tool.\n" + << std::endl; + } + } + return; +} + +} // namespace emocheck + +int main(int argc, char *argv[]) { std::vector scan_result; + bool is_debug = false; + bool is_quiet = false; + bool is_json = false; + std::string output_path = "."; - emocheck::PrintBanner(); - scan_result = emocheck::ScanEmotet(); - emocheck::PrintReport(scan_result); - emocheck::WriteReport(scan_result); + if (argc < 2) { + emocheck::PrintBanner(); + scan_result = emocheck::ScanEmotet(is_debug); + emocheck::PrintReport(scan_result); + emocheck::WriteReport(scan_result, is_quiet, output_path); + system("pause"); + return 0; + } - system("pause"); + // Parse parameters + for (int i = 1; i < argc; i++) { + if (emocheck::is_param(argv[i])) { + const char *param = &argv[i][1]; + if (!strcmp(param, PARAM_QUIET)) { + is_quiet = true; + } else if (!strcmp(param, PARAM_DEBUG)) { + is_debug = true; + } else if (!strcmp(param, PARAM_JSON)) { + is_json = true; + } else if (!strcmp(param, PARAM_OUTPUT)) { + const char *next_param = &argv[i + 1][0]; + if (std::filesystem::exists(std::string(next_param))) { + output_path = std::string(next_param); + i++; + } else { + std::cout << "Invalid output path: " << next_param << std::endl; + std::cout << "Report will be generated on current directory," << std::endl; + i++; + } + } else if (!strcmp(param, PARAM_HELP)) { + emocheck::PrintBanner(); + emocheck::PrintHelp(); + return 0; + } else { + std::cout << "Invalid parameter: " << param << std::endl; + return 0; + } + } else { + const char *param = &argv[i][0]; + std::cout << "Invalid parameter: " << param << std::endl; + return 0; + } + } + if (!is_quiet) { + emocheck::PrintBanner(); + } + scan_result = emocheck::ScanEmotet(is_debug); + if (!is_quiet) { + emocheck::PrintReport(scan_result); + } + if (!is_debug) { + if (is_json) { + emocheck::JsonReport(scan_result, is_quiet, output_path); + } else { + emocheck::WriteReport(scan_result, is_quiet, output_path); + } + } + if (!is_quiet) { + system("pause"); + } return 0; }