/* dcanalyzer.c -- Analyzer for DarkComet Servers, make sure the server is fully unpacked before using this tool. It will decrypt all known data, if it doesn't work you may be working with a very old version of darkcomet and you should have a look at the PE resources for the unencrypted data. This tool is public domain. Tested under GCC and VS2010 with many darkcomet versions. */ #define _BSD_SOURCE /* make gcc happy with snprintf as ansi */ #include #include #include #include #ifdef WIN32 #include #endif #if _MSC_VER #define snprintf _snprintf /* work around for microsoft's "C" compiler */ #endif /* We search the entire file for this string to find the RC4 key */ #define KEY_PREFIX "#KCMDDC" int process(char * file); void ShowOutput(char * str); void process_resource_table(int pos, char found, char * goal); FILE * fh; int32_t rsrc_pos; int32_t rsrc_virtual; char * dcstr = NULL; /* RC4 borrowed from FreeBSD */ struct rc4_state { uint8_t perm[256]; uint8_t index1; uint8_t index2; }; static __inline void swap_bytes(uint8_t *a, uint8_t *b) { uint8_t temp; temp = *a; *a = *b; *b = temp; } void rc4_init(struct rc4_state * state, uint8_t *key, int keylen) { uint8_t j; int i; /* Initialize state with identity permutation */ for (i = 0; i < 256; i++) state->perm[i] = (uint8_t)i; state->index1 = 0; state->index2 = 0; /* Randomize the permutation using key data */ for (j = i = 0; i < 256; i++) { j += state->perm[i] + key[i % keylen]; swap_bytes(&state->perm[i], &state->perm[j]); } } void rc4_crypt(struct rc4_state * state, uint8_t *inbuf, uint8_t *outbuf, int buflen) { int i; uint8_t j; for (i = 0; i < buflen; i++) { /* Update modification indicies */ state->index1++; state->index2 += state->perm[state->index1]; /* Modify permutation */ swap_bytes(&state->perm[state->index1], &state->perm[state->index2]); /* Encrypt/decrypt next byte */ j = state->perm[state->index1] + state->perm[state->index2]; outbuf[i] = inbuf[i] ^ state->perm[j]; } } /* end FreeBSD RC4 code */ /* my PE header structs */ struct DATA_DIR { uint32_t addr; uint32_t size; }; struct RES_DIR_TABLE { uint32_t characteristics; uint32_t timestamp; uint16_t major; uint16_t minor; uint16_t num_name_elems; uint16_t num_id_elems; }; struct RES_DIR_ENTRY { union { uint32_t name_rva; uint32_t id; } identifier; union { uint32_t data_entry_rva; uint32_t subdir_rva; } child; }; struct RES_DATA { uint32_t data_rva; uint32_t size; uint32_t codepage; uint32_t reserved; }; void process_resource_entry(struct RES_DIR_ENTRY entry, char found, char * goal) { struct RES_DATA data; if (entry.child.data_entry_rva & 0x80000000) { /* process subdir entry */ process_resource_table(entry.child.subdir_rva & 0x7FFFFFFF, found, goal); } else { if (found) { fseek(fh, rsrc_pos + entry.child.data_entry_rva, SEEK_SET); fread(&data, sizeof(data), 1, fh); dcstr = (char*) malloc(data.size+1); dcstr[data.size] = '\0'; fseek(fh, data.data_rva - rsrc_virtual + rsrc_pos, SEEK_SET); fread(dcstr, 1, data.size, fh); } /* process data entry */ } } void process_resource_entry_id(struct RES_DIR_ENTRY entry, char found, char * goal) { process_resource_entry(entry, found, goal); } char check_unicode_str(char * goal, char * input, unsigned int len) { unsigned int i; char next; if (strlen(goal) == len) { for (i = 0; i < len*2; i++) { next = (i % 2) == 0 ? goal[i/2] : 0; if (input[i] != next) return 0; } } else { return 0; } return 1; } void process_resource_entry_name(struct RES_DIR_ENTRY entry, char found, char * goal) { uint16_t name_len; char * name; fseek(fh, rsrc_pos + (entry.identifier.name_rva & 0x7FFFFFFF), SEEK_SET); fread(&name_len, sizeof(name_len), 1, fh); name = (char*) malloc(name_len*2 + 1); fread(name, 1, name_len*2, fh); if (check_unicode_str(goal, name, name_len)) { process_resource_entry(entry, 1, goal); } else { process_resource_entry(entry, found, goal); } } void process_resource_table(int pos, char found, char * goal) { struct RES_DIR_TABLE table; int i; int length; struct RES_DIR_ENTRY * entries; fseek(fh, rsrc_pos + pos, SEEK_SET); fread(&table, sizeof(table), 1, fh); length = sizeof(struct RES_DIR_ENTRY) * (table.num_id_elems + table.num_name_elems); entries = (struct RES_DIR_ENTRY*) malloc(length); fread(entries, length, 1, fh); for (i = 0; i < table.num_name_elems; i++) { process_resource_entry_name(entries[i], found, goal); } for (i = 0; i < table.num_id_elems; i++) { process_resource_entry_id(entries[i], found, goal); } } char * decrypt_dcdata(char * instr, char * key) { struct rc4_state rc4state; int i; int len = strlen(instr)/2; char * data; unsigned int tempdata; data = (char *)malloc(len); for (i = 0; i < len; i++) { sscanf(instr+(i*2), "%2X", &tempdata); data[i] = (char) tempdata; } rc4_init(&rc4state, (uint8_t*)key, strlen(key)); rc4_crypt(&rc4state, (uint8_t*)data, (uint8_t*)data, len); data[len] = '\0'; return data; } int main(int argc, char** argv) { if (argc < 2) { ShowOutput("Please specify a file!"); return EXIT_FAILURE; } return process(argv[1]); } #ifdef WIN32 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { char buffer[8192]; OPENFILENAME ofn; if (__argc > 1) { return main(__argc, __argv); } else { ZeroMemory( &ofn , sizeof( ofn)); ofn.lStructSize = sizeof(ofn); ofn.lpstrFile = buffer; ofn.lpstrFile[0] = '\0'; ofn.nMaxFile = sizeof(buffer); ofn.lpstrFilter = "DarkComet Server Executables\0*.exe"; ofn.nFilterIndex = 1; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; GetOpenFileName(&ofn); return process(ofn.lpstrFile); } } #endif void ShowOutput(char * str) { #ifdef WIN32 MessageBoxExA(NULL, str, "DCAnalyzer", 0, 0); #else printf("%s\n", str); #endif } /* simple scanner to parse key out of executable */ char * find_key_by_force() { int pos = 0; char * prefix = KEY_PREFIX; int max = strlen(prefix); char * keybuf = (char*) malloc(4096); char cur; fseek(fh, 0, SEEK_SET); /* go to start of file */ do { fread(&cur, 1, 1, fh); if (pos >= max) { keybuf[pos] = cur; if (cur == '\0') break; pos++; } else if (cur == prefix[pos]) { keybuf[pos] = cur; pos++; } else { pos = 0; } if (pos == 4096) /* no heap overflow for you! */ break; } while (!feof(fh)); strncat(keybuf, "890", 4096); /* comes from a separate function */ return keybuf; } int process(char * file) { uint16_t mz_hdr; uint32_t pe_hdr_loc; uint32_t pe_hdr; uint16_t opt_hdr; uint32_t num_rva_sizes; uint32_t image_base; unsigned int opt_hdr_pos; unsigned int i; char * output; char buffer[1024]; char final_buffer[8192]; char section_name[9]; char found; int successful; char * key; /* We search for these when we can't find the new DCDATA resource. Used for old versions of DarkComet */ char * fallbacks[] = {"FWB", "GENCODE", "MUTEX", "NETDATA", "OFFLINEK", "SID", "FTPUPLOADK", "FTPHOST", "FTPUSER", "FTPPASS", "FTPPORT", "FTPSIZE", "FTPROOT", "PWD"}; fh = fopen(file, "rb"); if (fh == NULL) { ShowOutput("File open failed"); return EXIT_FAILURE; } key = find_key_by_force(); fseek(fh, 0, SEEK_SET); /* go to start of file */ section_name[8] = '\0'; fread(&mz_hdr, sizeof(int16_t), 1, fh); /* read first 2 bytes of file */ if (mz_hdr != *(uint16_t*)"MZ") { ShowOutput("Not an MZ Executable!"); return EXIT_FAILURE; } fseek(fh, 0x3C, SEEK_SET); fread(&pe_hdr_loc, sizeof(pe_hdr_loc), 1, fh); /* offset of PE header */ fseek(fh, pe_hdr_loc, SEEK_SET); fread(&pe_hdr, sizeof(pe_hdr), 1, fh); if (pe_hdr != *(uint32_t*)"PE\0\0") { ShowOutput("Not a PE Executable!"); return EXIT_FAILURE; } opt_hdr_pos = pe_hdr_loc + 0x18; fseek(fh, opt_hdr_pos, SEEK_SET); fread(&opt_hdr, sizeof(opt_hdr), 1, fh); if (opt_hdr != 0x010B) { ShowOutput("Invalid optional header!"); return EXIT_FAILURE; } fseek(fh, opt_hdr_pos + 28, SEEK_SET); /* ImageBase so we can subtract it later */ fread(&image_base, sizeof(image_base), 1, fh); fseek(fh, opt_hdr_pos + 92, SEEK_SET); /* NumberOfRvaAndSizes */ fread(&num_rva_sizes, sizeof(num_rva_sizes), 1, fh); if (num_rva_sizes > 0x10) num_rva_sizes = 0x10; /* enforce this so dumb tricks cannot work */ fseek(fh, num_rva_sizes*8, SEEK_CUR); /* we should now be at the section headers */ found = 0; do { fread(section_name, 1, 8, fh); if (strncmp(".rsrc", section_name, 8) == 0) { fseek(fh, 4, SEEK_CUR); fread(&rsrc_virtual, 4, 1, fh); fseek(fh, 4, SEEK_CUR); fread(&rsrc_pos, 4, 1, fh); found++; fseek(fh, 16, SEEK_CUR); /* skip rest of this section header. */ } else { fseek(fh, 40-8, SEEK_CUR); /* skip rest of this section header. */ } } while (found != 1); process_resource_table(0,0, "DCDATA"); if (dcstr != NULL) { ShowOutput(decrypt_dcdata(dcstr, key)); } else { final_buffer[0] = '\0'; successful = 0; for (i = 0; i < (sizeof(fallbacks)/sizeof(*fallbacks)); i++) { process_resource_table(0,0, fallbacks[i]); if (dcstr != NULL) { output = decrypt_dcdata(dcstr, key); if (final_buffer[0] == '\0') snprintf(buffer, 1024, "%s: %s", fallbacks[i], output); else snprintf(buffer, 1024, "\n%s: %s", fallbacks[i], output); strncat(final_buffer, buffer, 8192); successful++; dcstr = NULL; /* null it every round */ } } if (successful) ShowOutput(final_buffer); else ShowOutput("Could not find any DarkComet resource!\n"); } return EXIT_SUCCESS; }