/*
 * Honey-VMware patch
 * (c) Kostya Kortchinsky <kostya(dot)kortchinsky[at]renater(dot)fr>
 *
 * French Honeynet Project <http://www.frenchhoneynet.org/>
 * CADHo Project <http://www.eurecom.fr/~dacier/CADHO/>
 *
 * BACKUP YOUR VMWARE-VMX BINARY BEFORE USING THIS PATCH !
 * 
 * gcc -Wall -lz -o NEW_VMpatch NEW_VMpatch.c # ZLib is needed !
 *
 * Here are a few considerations on how to increase furtivity of VMware in
 * the context on honeypots. Of this is far from perfect as there still
 * remain a lot of ways to fingerprint a virtual host.
 *
 * 1) The I/O backdoor
 *    Just check "VMware's back" page, it is well documented there.
 *    This patch can disable it, or if you are smart enough, you can change
 *    the magic number to hide it.
 * 2) The MAC address
 *    VMware has 3 registered OUIs that will allow anyone to easily
 *    fingerprint a NIC (locally, on a local network, or through SMB).
 *    This patch will allow you to change the default OUI 00:0c:29 to the
 *    one of your choice. Keep in mind that the NIC is supposed to be an
 *    AMD PCNet32.
 * 3) The video adapter
 *    Well since the emulated video adapter has its PCI IDs related to
 *    VMware, we will fix that. We won't only change the IDs, we will 
 *    fully replace the video adapter bios. In order to do so, you must
 *    dump a working video bios. Of course, not all the bioses will work
 *    in VMware, you will have to test. You can use for example :
 *    - S3_Inc._ViRGE_DX_or_GX.bios
 *    - ...
 * 4) The CDROM device
 *    There is no need to patch anything for that. Just set up a generic
 *    SCSI device (/dev/sg*) linked to your physical CDROM device (use SCSI
 *    emulation if needed), choose it as your CDROM device and it will do
 *    the job.
 *
 * You must not use this patch if you have already installed virtual hosts
 * since it will probably screw some stuff. It is a lot wiser to freshly
 * install new hosts after having applied the patch.
 * 
 * PLEASE READ THE CODE AND COMMENTS !
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/elf.h>
#include <zlib.h>
#include <fcntl.h>
#include <sys/mman.h>

#define NEW_VMX86_OUI0 0x00 // NOT USED
#define NEW_VMX86_OUI1 0x60
#define NEW_VMX86_OUI2 0xb0

#define PATCH_IO_BACKDOOR 1
#define PATCH_VIDEO_BIOS 2
#define PATCH_MAC_ADDRESS 4

#define VERSION 0.2.0alpha1

typedef struct
{
	char *name;
	unsigned long int crc32;
} vmwareVersion;

// The patch has been tested on these versions
vmwareVersion vmwareVersions[] =
{
	{ "VMware Workstation 5.0.0 build-13124", 0xa222c2e7 }
};

int version = -1;
unsigned char *vmxBinary = NULL, *vmmBinary = NULL;
Elf32_Ehdr *vmxEhdr = NULL, *vmmEhdr = NULL;
Elf32_Shdr *vmxShdr = NULL, *vmmShdr = NULL;
char *vmxShstrtab = NULL, *vmmShstrtab = NULL;
int indexText = -1, indexVbios = -1, indexVmm = -1, indexZtext = -1, indexZrodata = -1;
unsigned char *sectionVbios = NULL, *sectionZtext = NULL, *sectionZrodata = NULL, *sectionVmm = NULL;

char *memstr(char *haystack, unsigned int h_size, const char *needle, unsigned int n_size)
{
	char *p;

	for (p = haystack; p <= (haystack - n_size + h_size); p++)
		if (memcmp(p, needle, n_size) == 0)
			return p;
	return NULL;
}

void usage(char *program)
{
	printf("[!] Usage: %s [-d BIOS | [-b] [-m] [-v BIOS]]\n", program);
	printf("[!] -d: dumps current video adapter bios to file BIOS\n");
	printf("[!] -b: disables the I/O backdoor\n");
	printf("[!] -m: patches the MAC address generation routine\n");
	printf("[!] -v: replaces VMware video adapter bios with the one in file BIOS\n");
	exit(EXIT_FAILURE);
}

/*
 * int patchIOBackdoor(void)
 * 
 * This function will disable the I/O backdoor by noping the conditional jump coming
 * shortly after the comparison with the magic number (0x564D5868). This comparison
 * is located in the VMM binary within its .ztext section. The section has to be
 * uncompressed, patched, then compressed again, thanks to zlib.
 * 
 */
int patchIOBackdoor(void)
{
	int error = EXIT_FAILURE;
	unsigned long int length, newLength;
	unsigned char *p, *data = NULL;
	const unsigned char instr_cmp[] = { 0x81, 0x7d, 0x08, 0x68, 0x58, 0x4d, 0x56 }; // cmp [ebp+arg_0],'VMXh'
	const unsigned char instr_jz[] = { 0x74, 0x5c }; // jz short loc_XXXXXX

	printf("[!] Disabling I/O backdoor\n");
	length = 320 * 1024;
	if ((data = malloc(length)) == NULL)
		goto end;
	if (uncompress(data, &length, &vmmBinary[vmmShdr[indexZtext].sh_offset], vmmShdr[indexZtext].sh_size) != Z_OK)
		goto end;

	// Look for instr_cmp in uncompressed .ztext
	if ((p = memstr(data, length, instr_cmp, sizeof(instr_cmp))) == NULL)
		goto end;
	p += 20;
	// instr_jz should be 20 bytes further
	if (memcmp(p, instr_jz, sizeof(instr_jz)) != 0)
	{
		//printf("[-] (Put a fancy error message here)\n");
		goto end;
	}
	memset(p, 0x90, sizeof(instr_jz)); // NOP out the jump

	// Compress the section
	newLength = 192 * 1024;
	if ((sectionZtext = malloc(newLength)) == NULL)
		goto end;
	if (compress2(sectionZtext, &newLength, data, length, Z_BEST_COMPRESSION) != Z_OK)
		goto end;
	vmmShdr[indexZtext].sh_size = newLength;
	vmmShdr[indexZtext].sh_entsize = length;

	printf("[+] I/O backdoor succesfully disabled\n");
	error = EXIT_SUCCESS;
end:
	free(data);
	return error;
}

/*
 * int patchMACAddress(void)
 *
 * This function will patch the default generated OUI (00:0C:29) with the one of
 * your choice 00:XX:YY (check defines at the top of the program). There are in
 * fact two places that need patching, the generation routine (mov), and the
 * verification routine (cmp).
 *
 * If you want to use vmware-natd, you will have to enable the AllowAnyOUI option.
 * 
 * It will result in a conflict for existing virtual hosts, that you can solve
 * by removing the ethernet0.* lines in the configuration file of the virtual
 * machine.
 *
 */
int patchMACAddress(void)
{
	unsigned char *p, *data = &vmxBinary[vmxShdr[indexText].sh_offset];
	unsigned int length = vmxShdr[indexText].sh_size;
	const unsigned char instr_mov1[] = { 0xc6, 0x45, 0xc8, 0x00 }; // mov byte ptr [ebp+var_38],0
	const unsigned char instr_mov2[] = { 0xc6, 0x45, 0xc9, 0x0c }; // mov byte ptr [ebp+var_38+1],0ch
	const unsigned char instr_mov3[] = { 0xc6, 0x45, 0xcA, 0x29 }; // mov byte ptr [ebp+var_38+2],29h
	const unsigned char instr_cmp1[] = { 0x80, 0x7b, 0x01, 0x0c }; // cmp byte ptr [ebx+1],0ch
	const unsigned char instr_cmp2[] = { 0x80, 0x7b, 0x02, 0x29 }; // cmp byte ptr [ebx+2],29h

	printf("[!] Patching MAC address generation\n");
	if ((p = memstr(data, length, instr_mov1, sizeof(instr_mov1))) == NULL)
		return EXIT_FAILURE;
	p += 4;
	if (memcmp(p, instr_mov2, sizeof(instr_mov2)) != 0)
		return EXIT_FAILURE;
	*(p + 3) = NEW_VMX86_OUI1;
	p += 4;
	if (memcmp(p, instr_mov3, sizeof(instr_mov3)) != 0)
		return EXIT_FAILURE;
	*(p + 3) = NEW_VMX86_OUI2;
	p += 4;
	length = p - data;
	if ((p = memstr(p, length, instr_cmp1, sizeof(instr_cmp1))) == NULL)
		return EXIT_FAILURE;
	*(p + 3) = NEW_VMX86_OUI1;
	p += 22;
	// instr_cmp2 should be 22 bytes further
	if (memcmp(p, instr_cmp2, sizeof(instr_cmp2)) != 0)
		return EXIT_FAILURE;
	*(p + 3) = NEW_VMX86_OUI2;
	printf("[+] MAC address generation succesfully patched\n");

	return EXIT_SUCCESS;
}

/*
 * int patchVideoAdapter(char *filename)
 *
 * This routine will replace the video adapter bios shipped with VMware with
 * the one of your choice. It will also replace the PCI IDs hardcoded in the
 * .text section of the VMX binary. Of course not any BIOS can do, usually the
 * one of simple-and-not-too-recent video cards will work fine.
 *
 */
int patchVideoAdapter(char *filename)
{
	int error = EXIT_FAILURE;
	unsigned long int length, newLength;
	unsigned char *p, *text, *data = NULL;
	unsigned short int offset;
	FILE *file;
	unsigned short int vendor, device;
	const unsigned char instr_mov[] = { 0x66, 0xc7, 0x03, 0xad, 0x15 }; // mov word ptr [ebx],15adh
	const unsigned char instr_const[] = { 0x05, 0x04 }; // 405h

	printf("[!] Replacing video adapter bios\n");
	if ((file = fopen(filename, "rb")) == NULL)
		return error;
	fseek(file, 0, SEEK_END);
	length = ftell(file);
	fseek(file, 0, SEEK_SET);
	if ((data = malloc(length)) == NULL)
		goto end;
	if (fread(data, 1, length, file) != length)
		goto end;

	if (data[0] != 0x55 || data[1] != 0xaa)
		goto end;
	offset = *(unsigned short int *)(&data[24]);
	if (memcmp(&data[offset], "PCIR", 4) != 0)
		goto end;
	vendor = *(unsigned short int *)(&data[offset + 4]);
	device = *(unsigned short int *)(&data[offset + 6]);
	printf("[?] VendorID 0x%04x\n[?] DeviceID 0x%04x\n", vendor, device);

	newLength = 32 * 1024;
	if ((sectionVbios = malloc(newLength)) == NULL)
		goto end;
	if (compress2(sectionVbios, &newLength, data, length, Z_BEST_COMPRESSION) != Z_OK)
		goto end;
	vmxShdr[indexVbios].sh_size = newLength;
	vmxShdr[indexVbios].sh_entsize = length;

	text = &vmxBinary[vmxShdr[indexText].sh_offset];
	length = vmxShdr[indexText].sh_size;
	if ((p = memstr(text, length, instr_mov, sizeof(instr_mov))) == NULL)
		return EXIT_FAILURE;
	p += 5;
	length = p - text;
	if ((p = memstr(p, length, instr_mov, sizeof(instr_mov))) == NULL)
		return EXIT_FAILURE;
	*(unsigned short int *)(p + 3) = vendor;
	p += 5;
	length = p - text;
	if ((p = memstr(p, length, instr_const, sizeof(instr_const))) == NULL)
		return EXIT_FAILURE;
	*(unsigned short int *)p = device;

	printf("[+] Video adapter bios successfully replaced\n");
	error = EXIT_SUCCESS;
end:
	fclose(file);
	free(data);
	return error;
}

/*
 * int dumpVideoBios(char *filename)
 *
 * This function will allow you to dump the video adapter bios on the current
 * machine to a file. The BIOS is usually mapped at 0xc0000, but you can have
 * a look at /proc/iomem to be sure.
 *
 */
int dumpVideoBios(char *filename)
{
	int error = EXIT_FAILURE;
        unsigned char *mem;
        int fd1, fd2, length;

	printf("[!] Dumping video adapter bios\n");
        if ((fd1 = open(filename, O_CREAT | O_WRONLY, 0600)) == 0)
                return error;
        if ((fd2 = open("/dev/mem", O_RDONLY)) == 0)
        {
                printf("[-] Error opening /dev/mem\n");
                close(fd1);
                return error;
        }
#define START 0xc0000
#define LENGTH 0x20000
        if ((mem = mmap(0, LENGTH, PROT_READ, MAP_SHARED, fd2, START)) == MAP_FAILED)
        {
                printf("[-] Error mapping /dev/mem\n");
		goto end;
        }
        length = mem[2] * 512;
        if (write(fd1, mem, length) == length)
        {
		printf("[+] Video adapter bios successfully dumped (%d bytes)\n", length);
		error = EXIT_SUCCESS;
        }
        munmap(mem, LENGTH);
end:
        close(fd2);
        close(fd1);

        return error;
}

int main(int argc, char *argv[])
{
	FILE *file, *process;
	char buffer[64], *videoBios = NULL;
	int i, error = EXIT_FAILURE;
	unsigned int fileSize, size;
	unsigned char *newBinary = NULL;
	int c, options = 0;

	if (argc < 2)
		usage(argv[0]);
	while ((c = getopt(argc, argv, "bd:mv:")) != EOF)
	{
		switch (c)
		{
			case 'b':
				options |= PATCH_IO_BACKDOOR;
				break;
			case 'd':
				exit(dumpVideoBios(optarg));
				break;
			case 'm':
				options |= PATCH_MAC_ADDRESS;
				break;
			case 'v':
				options |= PATCH_VIDEO_BIOS;
				videoBios = optarg;
				break;
			default:
				usage(argv[0]);
				break;
		}
	}

	if ((process = popen("/usr/bin/vmware -v", "r")) == NULL)
		return error;
	if (fgets(buffer, sizeof(buffer), process) == NULL)
		return error;
	if (pclose(process) == -1)
		return error;
	for (i = 0; i < sizeof(vmwareVersions) / sizeof(vmwareVersions[0]); i++)
		if (strncmp(buffer, vmwareVersions[i].name, strlen(vmwareVersions[i].name)) == 0)
			version = i;
	if (version == -1)
	{
		printf("[-] Unknown VMware version\n");
		return error;
	}
	printf("[+] Detected %s\n", vmwareVersions[version].name);

	if ((file = fopen("/usr/lib/vmware/bin/vmware-vmx", "rb")) == NULL)
		return error;
	fseek(file, 0, SEEK_END);
	fileSize = ftell(file);
	if ((vmxBinary = malloc(fileSize)) == NULL)
		goto end;
	fseek(file, 0, SEEK_SET);
	if (fread(vmxBinary, 1, fileSize, file) != fileSize)
		goto end;

	printf("[?] CRC32 0x%08lx\n", crc32(0xffffffffL, vmxBinary, fileSize));
	if (crc32(0xffffffffL, vmxBinary, fileSize) != vmwareVersions[version].crc32)
	{
		printf("[-] The vmware-vmx binary is not the original one\n");
		goto end;
	}

	vmxEhdr = (Elf32_Ehdr *)(&vmxBinary[0]);
	vmxShdr = (Elf32_Shdr *)(&vmxBinary[vmxEhdr->e_shoff]);
	vmxShstrtab = &vmxBinary[vmxShdr[vmxEhdr->e_shstrndx].sh_offset];
	for (i = 1; i < vmxEhdr->e_shnum; i++)
	{
		if (!strcmp(&vmxShstrtab[vmxShdr[i].sh_name], ".text"))
			indexText = i;
		else if (!strcmp(&vmxShstrtab[vmxShdr[i].sh_name], ".vbios"))
			indexVbios = i;
		else if (!strcmp(&vmxShstrtab[vmxShdr[i].sh_name], ".vmm"))
			indexVmm = i;
		if (indexText != -1 && indexVbios != -1 && indexVmm != -1)
			break;
	}
	if (i == vmxEhdr->e_shnum)
		goto end;

	if ((options & PATCH_MAC_ADDRESS) != 0)
	{
		if (patchMACAddress() != EXIT_SUCCESS)
			goto end;
	}
	if ((options & PATCH_VIDEO_BIOS) != 0)
	{
		if (patchVideoAdapter(videoBios) != EXIT_SUCCESS)
			goto end;
	}

	vmmBinary = &vmxBinary[vmxShdr[indexVmm].sh_offset];
	vmmEhdr = (Elf32_Ehdr *)(&vmmBinary[0]);
	vmmShdr = (Elf32_Shdr *)(&vmmBinary[vmmEhdr->e_shoff]);
	vmmShstrtab = &vmmBinary[vmmShdr[vmmEhdr->e_shstrndx].sh_offset];
	for (i = 0; i < vmmEhdr->e_shnum; i++)
	{
		if(!strcmp(&vmmShstrtab[vmmShdr[i].sh_name], ".ztext"))
			indexZtext = i;
		else if (!strcmp(&vmmShstrtab[vmmShdr[i].sh_name], ".zrodata"))
			indexZrodata = i;
		if (indexZtext != -1 && indexZrodata != -1)
			break;
	}
	if (i == vmmEhdr->e_shnum)
		goto end;

	if ((options & PATCH_IO_BACKDOOR) != 0)
	{
		if (patchIOBackdoor() != EXIT_SUCCESS)
			goto end;
	}

	if ((sectionVmm = malloc(512 * 1024)) == NULL)
		goto end;
	memset(sectionVmm, '\0', 512 * 1024);
	size = sizeof(Elf32_Ehdr);
	for (i = 0; i < vmmEhdr->e_shnum; i++)
	{
		if(strcmp(&vmmShstrtab[vmmShdr[i].sh_name], ".bss"))
		{
			if (i == indexZtext && sectionZtext)
				memcpy(&sectionVmm[size], sectionZtext, vmmShdr[i].sh_size);
			else if (i == indexZrodata && sectionZrodata)
				memcpy(&sectionVmm[size], sectionZrodata, vmmShdr[i].sh_size);
			else
				memcpy(&sectionVmm[size], &vmmBinary[vmmShdr[i].sh_offset], vmmShdr[i].sh_size);
			vmmShdr[i].sh_offset = size;
			size += vmmShdr[i].sh_size;
		}
	}
	vmmEhdr->e_shoff = size;
	memcpy(&sectionVmm[0], vmmEhdr, sizeof(Elf32_Ehdr));
	memcpy(&sectionVmm[size], vmmShdr, vmmEhdr->e_shentsize * vmmEhdr->e_shnum);
	size += vmmEhdr->e_shentsize * vmmEhdr->e_shnum;
	vmxShdr[indexVmm].sh_size = size;

	if ((file = freopen("/usr/lib/vmware/bin/vmware-vmx", "wb", file)) == NULL)
	{
		//printf("[-] (Put a fancy error message here)\n");
		goto end;
	}
	if ((newBinary = malloc(4096 * 1024)) == NULL)
		goto end;
	memset(newBinary, '\0', 4096 * 1024);
	i = (indexVbios < indexVmm) ? indexVbios : indexVmm;
	size = vmxShdr[i].sh_offset;
	memcpy(&newBinary[0], &vmxBinary[0], size);
	for (; i < vmxEhdr->e_shnum; i++)
	{
		if (i == indexVbios && sectionVbios)
			memcpy(&newBinary[size], sectionVbios, vmxShdr[i].sh_size);
		else if (i == indexVmm && sectionVmm)
			memcpy(&newBinary[size], sectionVmm, vmxShdr[i].sh_size);
		else
			memcpy(&newBinary[size], &vmxBinary[vmxShdr[i].sh_offset], vmxShdr[i].sh_size);
		vmxShdr[i].sh_offset = size;
		size += (((vmxShdr[i].sh_size - 1) / vmxShdr[i].sh_addralign) + 1) * vmxShdr[i].sh_addralign;
	}
	vmxEhdr->e_shoff = size;
	memcpy(&newBinary[0], vmxEhdr, sizeof(Elf32_Ehdr));
	memcpy(&newBinary[size], vmxShdr, vmxEhdr->e_shentsize * vmxEhdr->e_shnum);
	size += vmxEhdr->e_shentsize * vmxEhdr->e_shnum;

	fseek(file, 0, SEEK_SET);
	if (fwrite(newBinary, 1, size, file) != size)
		goto end;

	error = EXIT_SUCCESS;
end:
	free(newBinary);
	free(sectionVmm);
	free(sectionZrodata);
	free(sectionZtext);
	free(sectionVbios);
	free(vmxBinary);
	fclose(file);

	return error;
}
