This document provides an overview of using virtualization and hypervisors for malicious purposes. It discusses hypervisors, how they work, and why they could be useful for malware. It then covers setting up a basic virtual machine using KVM on Linux, including initializing memory, injecting code, handling I/O, and converting the code to a shellcode. The presentation includes demos of creating a KVM-powered hypervisor and a hypervisor shellcode.
2. Who am I?
A security fella
I am fascinated by malware, and cryptography
I work on offensive side of research
Get in touch: me@adhokshajmishraonline.in
3. Agenda
Hypervisor: what, how and why?
Hypervisor in Linux
Capsule course on hypervisor (Intel VT-x, AMD-V, KVM)
Spawning a bare-bone VM
Injecting code in VM
I/O Between Host and Guest
Converting C Code to Shellcode
4. Hypervisor: What?
A hypervisor is computer software, firmware, or hardware, that creates and
runs virtual machines. A computer on which a hypervisor runs one or more
virtual machines is called a host machine, and each virtual machine is
called a guest machine.
Type-1 hypervisor: runs directly on bare metal. Example: ESXi
Type-2 hypervisor: runs on a conventional OS. Example: KVM
5. Hypervisor: How?
Software emulation: Hypervisor emulates instructions of guest machine
using instructions of host machine. These are slow, but can handle arbitrary
architecture for guest machine.
Hardware-assisted: Hypervisor runs the instructions of guest machine directly
on physical CPU using virtualization instructions provided by chip (vmenter,
vmexit etc). Naturally, these are much faster than emulation based; but
cannot handle architectures not supported by underlying chip.
8. Hypervisor: Why?
Hypervisor makes it harder to debug the code running inside it.
Debugging is even harder if guest architecture is custom and
undocumented.
9. Capsule Course on Hypervisor
A virtual CPU starts up just like an actual CPU, i.e., it will start in 8086 aka real
mode. You need to switch it to protected mode (x86), and then to long
mode (x64). Mode switch is not mandatory if you keep yourself limited to
instruction set of the mode you are going to use.
At startup, you need to allocate memory which will be used by guest
machine. Guest machine will see it as ram, while host will see it as a
memory buffer.
Registers must be set properly on startup, otherwise guest machine will fail.
You need to load code in guest memory, set registers (at least instruction
pointer) before running the guest.
10. Capsule Course on Hypervisor
During execution, guest machine will raise events, which must be handled
by the host.
For any I/O, virtual ports will be used by guest machine. Whenever guest
machine attempts a write to port, or read from port, it triggers an event,
which is passed to host. Host must handle the data transfer if any.
11. Spawning a Bare-bone VM
int kvm = open(“/dev/kvm”, O_RDWR | O_CLOEXEC);
int vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0);
int vcpufd = ioctl(vmfd, KVM_CREATE_CPU, (unsigned long)0);
All communication and configuration is done by IOCTL calls. These calls
follow the following pattern:
int ioctl(file_descriptor, command, parameter);
12. Spawning a Bare-bone VM
Setting up memory:
uint8_t *mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
The above call allocates a buffer of length 4KB, with read and write
permissions. The allocated page will be anonymous (as in, not backed by
any file).
13. Spawning a Bare-bone VM
Setting up guest memory:
struct kvm_userspace_memory_region region =
{
.slot = 0,
.guest_phys_addr = 0x1000,
.memory_size = 0x1000,
.userspace_addr = (uint64_t)mem;
};
int ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion);
14. Why was guest physical address set to
0x1000, and not at, say, 0x10 or 0x1?
15. Injecting Code in VM
It is as simple as copying code bytes into memory buffer before giving it to
guest machine.
Uint8_t code[] = {/*code bytes here*/};
memcpy(mem, code, sizeof(code));
NOTE: The code must be for 8086 CPU, otherwise guest machine will fail with
illegal instruction fault and/or with bizarre results. If you want to switch
modes, it must happen through 8086 code.
16. Injecting Code in VM
Setting up segment registers:
struct kvm_sregs sregs;
ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs);
sregs.cs.base = 0;
sregs.cs.selector = 0;
ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs);
17. Injecting Code in VM
Setting up registers:
struct kvm_regs regs =
{
.rip = 0x1000,
.rax = 4,
.rbx = 5,
.rflags = 0x2,
};
ret = ioctl(vcpufd, KVM_SET_REGS, ®s);
18. Kicking VM to Life
size_t mmap_size = ioctl(kvm, KVM_GET_CPU_MMAP_SIZE, NULL);
kvm_run *run = mmap(NULL, mmap_size, PROT_READ | PRO_WRITE,
MAP_SHARED, vcpufd, 0);
while(1){
ret = ioctl(vcpufd, KVM_RUN, NULL);
switch (run->exit_reason) {
/*handle events here*/
}
}
19. I/O Between Host and Guest
switch (run->exit_reason) {
case KVM_EXIT_IO:
if (run->io.direction == KVM_EXIT_IO_OUT && io.port == 0xabc)
{
char ch = *(((char*)run) + run->io.data_offset);
}
/*handle more cases here*/
}
21. Conversion to Shellcode
gcc kvm.c –o kvm –save-temps –masm=intel
kvm.s contains assembly listing.
Remove all the extra clutter generated by compiler
Resolve addresses of all hardcoded values using JMP-CALL-POP method
Profit!