Dev (Back & Front)ARTIGO

Tempo de execução de framework simples através de exemplo

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

Nos dias de hoje, nós estamos cercados por diferentes frameworks, como Java, .Net e outros tantos. Você já se perguntou como eles funcionam ou já quis/ precisou implementar um? Neste artigo vou abordar um tempo de execução de um framework simples ou até mesmo trivial.

Como de costume – nota para os nerds: O código fonte dado neste artigo é apenas para exemplo. Eu sei que este quadro está longe de ser perfeito, portanto, este artigo não é um COMO FAZER e sim uma explicação do princípio. Verificações de erro serão omitidas de propósito. Se você deseja implementar um framework real, faça você mesmo, incluindo as verificações de erro.

Agora, vamos ao que interessa.

Frameworks

A Wikipedia dá a seguinte identificação para o termo framework: “Um framework é uma plataforma universal de software reutilizável, usada para desenvolver aplicativos, produtos e soluções de frameworks, incluindo programas de apoio, compiladores, bibliotecas de código, uma interface de programação de aplicativo (API.) e conjuntos de ferramentas que reúnem todos os componentes diferentes para permitir o desenvolvimento de um projeto ou uma solução”. Como você pode ver, o framework de é uma coisa muito complexa. No entanto, vamos simplificá-lo e ver como ele funciona basicamente.

O diagrama abaixo pode te dar uma boa compreensão do que é o Framework de software e qual o papel que desempenha. Basta dizer, é um calço entre a aplicação do usuário e o sistema operacional. Existem pelo menos dois tipos de frameworks de software:

  1. Interface de programação de aplicativo (API) – se dermos uma olhada na API do Windows, podemos ver que é um framework também. No entanto, pode ser contornado ou, pelo menos, um programador pode escolher diminuir a interação com ele, por exemplo, utilizando as funções de ntdll.dll ao invés daquelas fornecidos pelo kernel32.dll ou mesmo “talk” para Windows kernel diretamente (não recomendado, mas pode ser inevitável algumas vezes) por meio de interrupções.
  2. .Net como framework – isolamento total do código do usuário com sistema operacional. Tais estruturas são, em sua maioria, máquinas virtuais isolando completamente o aplicativo do usuário do sistema operacional e hardware. No entanto, tal framework tem que fornecer o aplicativo com todos os serviços disponíveis no sistema operacional. Este é o tipo de framework que vamos criar neste artigo.

Virtual machine

Os princípios de construção de uma virtual machine simples são abordados neste artigo, por isso vou apenas dar uma breve explicação aqui. O nosso VM neste exemplo conterá os seguintes componentes:

  • CPU Virtual

A estrutura que representa um CPU – basicamente, tem seis registros e um ponteiro para a pilha:

typedef struct
{
unsigned int regs[6];
unsigned int* stack;
}CPU;

Os seis registros geralmente são A, B, C e D, em que A é também usado para armazenar sistema de valor de retorno e de chamadas e C é usado como um contador para instrução LOOP, stack pointer (SP) e ponteiro de instrução (IP).

  • Intérprete de instrução

Uma função ou um conjunto de funções que é responsável pela interpretação da pseudo assembly (ou chame de linguagem assembly intermediária) projetado para esta virtual machine (neste caso, 14 instruções).

  • Sistema tratador de chamada

Este componente fornece os meios para o aplicativo do usuário interagir com o sistema operacional (neste caso, duas chamadas de sistema: sys_write e sys_exit).

Função central

O nome da função fala por si. Esta é a primeira função da implementação do framework que ganha o controle. Neste caso em particular, ele não tem muitas coisas para fazer – inicialização da CPU virtual e execução do intérprete de comandos, até a saída do usuário do aplicativo (sinais do framework para terminar a execução).

Implementação

É uma prática comum implementar uma estrutura como uma DLL (dynamic link library), por exemplo, mscoree.dll – o núcleo do framework. Não vejo motivo para reinventar a roda, portanto, esse framework vai ser implementado como uma DLL também.

Está tudo bem, você pode dizer, mas como devemos passar o pseudo código assembly compilado para do framework? Bem, eu aposto que a maioria de vocês sabe como fazer isso. No caso de você não saber – não se preocupe, apenas continue lendo.

Em caso de um framework .NET (pelo menos tanto quanto eu sei), o gestor identifica o arquivo como um .NET executável, lê no cabeçalho meta e inicializa o mscoree.dll adequadamente. Não vamos passar por todas essas complicações e usaremos um arquivo PE regular:

  • PE Header – PE Header regular, nenhuma modificação necessária;
  • Seção de Código – simplesmente chama a função principal do Framework:
push pseudo_code_base_address
call [core]
  • Seção “Importar” – seção “importar” regular, que só importa uma função do framework.dll – framework.core (unsigned int);
  • Dados da Seção – esta seção contém o pseudo código compilado assembly atual  e todos os headers podem aparecer, que podem instruir o núcleo() para inicializar corretamente o aplicativo.

Exemplo de código fonte executável

A seguir está o código fonte do exemplo executável. Pode ser compilado com FASM (Assembler Flat).

include 'win32a.asm' ;we need the 'import' macro
include 'asm.asm' ;pseudo assembly commands and constants

format PE console
entry start

section '.text' readable executable
start:
push _base
call [core_func]

section '.idata' data import writeable
library framework, 'framework.dll'

import framework,\
core_func, 'Core'

section '.data' readable writeable
_base:
loadi A, _base
loadi B, 0x31
_add A, B
loadr B, A
loadi A, _data.string
loadi C, _data.string_len
_call _func
loadi A, 1
loadi B, _data.string
loadi C, _data.str_len
_int sys_write
loadi A, 1
loadi B, _data.msg
loadi C, _data.msg_len
_int sys_write
_int sys_exit


_func:
; A = string address
; B = key
; C = counter
.decode:
loadr D, A
xorr D, B
storr A, D
loadi D, 4
_add A, D
_loop .decode
_ret



_data:
.string db 'Hello, developer!', 10, 13
.str_len = $-.string
db 0
.string_len = ($-.string)/4
.msg db 'The program will now exit.', 10, 13
.msg_len = $-.msg

;Encrypt one string
load k dword from _base + 0x31
repeat 5
load a dword from _data.string + (% - 1) * 4
a = a xor k
store dword a at _data.string + (% - 1) * 4
end repeat

O código acima produz um pequeno arquivo executável, que invoca a função core() do framework. O pseudo código assembly simplesmente imprime duas mensagens (a primeira é decodificada antes de ser impressa). Fontes cheias são anexadas a este artigo (ver a primeira linha).

A coisa boa é que você não tem que iniciar o intérprete e carregar este arquivo executável (ou especificá-lo como um parâmetro de linha de comando). Você pode simplesmente executá-lo. O carregador do Windows irá vinculá-lo com o framework.dll automaticamente. A única coisa ruim é que você, provavelmente, teria que escrever seu próprio compilador, porque escrever assembly é divertido, assim como lidar com um pseudo assembly; mas, somente quando feito por diversão. Não é tão agradável quando se lida com código de produção.

Possíveis usos

A menos que você esteja tentando criar um framework que superasse estruturas de software existentes, você pode usar essa abordagem para aumentar a proteção de seus aplicativos, por exemplo, a virtualização de algoritmos de criptografia ou qualquer outra parte do seu programa que não for essencial, por meio de execução velocidade, mas representa uma propriedade intelectual sensível.

Espero que tenha achado este artigo útil.

Vejo vocês na próxima!

***

Artigo original disponível em: http://syprog.blogspot.com.br/2012/05/simple-runtime-framework-by-example.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