Dev (Back & Front)ARTIGO

Passando eventos para uma máquina virtual

O código fonte para este artigo pode ser encontrado aqui.

Máquinas virtuais e frameworks de software constituem uma parte inicial de nossa vida digital. Há VM complexa e frameworks de software simples. Estes dois artigos (virtual machine simples e tempo de execução de framework simples através de exemplo) mostram o quão fácil pode ser implementar você mesmo. Eu fiz o possível para descrever a forma como o código VM pode interagir com código nativo e o sistema operacional, no entanto, a interação ainda é deixada para trás sem explicação. Este artigo irá corrigir essa omissão.

Como de costume – nota para nerds:

O código fonte dado neste artigo é apenas para exemplo. Eu sei que este framework está longe de ser perfeito, portanto, este artigo não é um HOW TO ou tutorial – apenas uma explicação do princípio. Verificações de erro são omitidas de propósito. Você deseja implementar um quadro real – faça você mesmo, incluindo verificações de erro. Ao dizer código VM, não me refiro à implementação da máquina virtual, mas ao pseudo-código que corre dentro dela.

Visão geral da arquitetura

Desnecessário mencionar que a capacidade de transmitir eventos/sinais a um código executado pela máquina virtual implica em uma mais complexa arquitetura VM. Enquanto todos os exemplos anteriores foram baseados em uma única função responsável pela execução, a adição de eventos não significa apenas a adição de uma outra função, mas teremos que introduzir tópicos para nossa implementação.

Pelo menos dois segmentos são necessários:

  1. Actual VM – esta thread é responsável pela execução do código executável da VM e pelo envio de eventos da fila (processador);
  2. Event Listener – esta thread é responsável pela coleta de eventos relevantes para os sistemas operacionais e pela adição deles na fila de processos da da VM.

Você pode ver que a função Core(), no código fonte anexo, cria thread adicional.

Fig.1: Arquitetura VM com Event Listener

Event Listener: Essa thread coleta eventos do sistema operacional (movimento do mouse, key up/down etc.) e adiciona uma nova entrada para a lista de estruturas de eventos.

typedef struct _EVENT
{
struct _EVENT* next_event; // Pointer to the next event in the queue
int code; // Code of the event
unsigned int data; // Either unsigned int data or the address of the buffer
// containing information to be passed to the handler
}EVENT;

O código para o listener é bastante simples:

while(WAIT_TIMEOUT == WaitForSingleObject(processor_thread, 1))
{
// Check for events from the OS
if(event_present)
{
EnterCriticalSection(&cs);
event = (EVENT*)malloc(sizeof(EVENT));
event->code = whatever_code_is_needed;
event->data = whatever_data_is_relevant;
add_event(event_list, event);
event->next_event = NULL;
LeaveCriticalSection(&cs);
}
}

O código é suficientemente auto-explicativo. Antes de tudo, ele verifica eventos disponíveis (essa parte é omitida e substituída por um comentário). Se houver um novo evento para transmitir à VM, ele o adiciona à fila. Enquanto neste exemplo a coleta de eventos é implementada como um loop, na vida real, você pode fazê-la na forma de callbacks e usar o loop acima apenas para esperar a thread do processador para sair.

Processador

Obviamente, o “processador” thread vai ser um pouco mais complicado, então, no artigo anterior (tempo de execução de framework simples através de exemplo), como além de executar a função run_opcode (CPU **), ele tem que verificar se há eventos pendentes e passar o controle de fluxo para o handler associado no código VM.

typedef struct _EVENT_HANDLER
{
struct _EVENT_HANDLER* next_handler; // Pointer to the next handler
int event_code; // Code of the event
unsigned int handler_base; // Address of the handler in the VM's code
}EVENT_HANDLER;

DWORD WINAPI RunningThread(void* param)
{
CPU* cpu = (CPU*)param;
EVENT* event;
EVENT_HANDLER* handler;

do{
EnterCriticalSection(&cs);
if(NULL != events)
{
event = events;
events = events->next_event;

// Save current context by pushing VM registers to VM's stack

cpu->regs[REG_A] = (unsigned int)event->code;
cpu->regs[REG_B] = event->data;

handler = handlers;
while(NULL != handler && event->code != handler->event_code)
handler = handler->next_handler;

cpu->regs[REG_IP] = handler->handler_base;

free(event);
}
LeaveCriticalSection(&cs);

}while(0 != run_opcode(&cpu));
return cpu->regs[REG_A];
}

Estamos quase terminando. Nosso framework já sabe como passar eventos para um handler correto no código da VM. Duas coisas ainda precisam ser descobertas – registrar um handler e retornar de um handler.

Retornando do handler

Devido ao fato de que o Event Handler não é uma rotina regular, não podemos retornar dele usando a instrução regular RET, em vez disso, vamos introduzir uma outra instrução – IRET. Como o evento realmente interrompe o fluxo de execução do programa, IRET – retorno de interrupção é exatamente o que precisamos. O código fonte que manipula essa instrução é tão simples, que não há necessidade de fornecê-lo aqui no texto do artigo. Tudo o que ele faz é simplesmente restaurar o contexto do código da VM disparando os registos previamente inseridos na pilha.

Registrando um Event Handler

A última coisa que resta é “ensinar” o programa escrito em pseudo assembly a registrar um handler para um tipo determinado de evento. Para fazer isso, precisamos adicionar uma chamada de sistema simples – SYS_ADD_LISTENER. Essa chamada de sistema aceita dois parâmetros:

  1. Código do evento para handler.
  2. Endereço da função handler.
loadi  A, 0             ;Code of the event
loadi B, handler ;Address of the handler subroutine
_int sys_add_listener ;Register the handler

Código de exemplo

O código de exemplo anexado a este artigo é a implementação de todos os itens acima. Ele faz o seguinte:

  1. Registra em event handler;
  2. Entra em um loop infinito imprimindo  ‘.’ a cada vários milissegundos;
  3. A primeira thread espera um pouco e gera um evento;
  4. Event Handler encerra o loop infinito e retorna;
  5. O programa imprime uma mensagem e sai.

Espero que este artigo tenha sido útil ou, pelo menos, interessante.

Vejo vocês na próxima.

?

Texto original disponível em http://syprog.blogspot.com.br/2012/05/passing-events-to-virtual-machine.html

trabalha com engenharia reversa e consultoria em anti-pirataria na Irdeto USA. Já trabalhou com detecção de malware, pesquisa de vírus e como engenheiro de suporte para cliente. Gosta de C, Assembler, Programação de Sistemas e Engenharia Reversa. É israelita e mora em Moscou.

Ver perfil