quarta-feira, 25 de abril de 2012

Injetando funções em aplicações

Veremos aqui uma técnica muito comum entre programadores de trainers/bots/macros que é a injeção de códigos em aplicações já compiladas sem alterar o estado fisíco do arquivo da aplicação. Caso queira pesquisar mais sobre o assunto procure por "inject dll", "hook function" e ainda "hooking function" tendo em base o "hook".

A aplicação que iremos modificar têm o seguinte código fonte:
#include <stdio.h>
#include <stdlib.h>

int T;

void get_number()
{
    printf("Enter Number: ");
    scanf("%d", &T);
}
int main()
{
    system("title my program");
    do
    {
        get_number();
    }
    while (T);
    return 0;
}

Nosso programa pede um número de entrada, caso esse número seja 0 ele sai, do contrário repete, e assim sucessivamente. Certo?
Continuando...
Iremos injetar um código que imprima na tela "You number entered are: <numero digitado>" toda vez que a get_number() for chamado. Para isso precisaremos saber os endereços da printf e a variável T na memória (O depurador usado aqui é o ollydbg).

Descobrindo os endereços

Para descobrirmos o endereço da função printf, vá em Search for > All intermodular calls,

Na janela "search" procure por printf e siga sua referência.

Iremos nos deparar com nossa função get_number(), apertando a tecla espaço iremos ver o endereço que se refere a printf, que no nosso caso é 0x00401A8C.

Agora falta só o endereço da variável T. Se olharmos direito veremos o endereço após a chamada da printf, onde começa as instruções da scanf, que é a referência da váriavel e o formato que iremos extrair. Então, BINGO! Temos também o endereço da variável T que no nosso caso é 0x00404008.

Procurando um local para injetar

Precisamos saber também onde iremos injetar nosso código para que fique similar a:
do
{
    get_number();
    printf("You number entered are: %d", T);
}
while (T);

Analizando as instruções

Encontramos um lugar perfeito para injetar nosso código, olhe bem no endereço 0x00401B4D a instrução MOV EAX, DWORD PTR DS:[program.404008] ela ocupa 5 bytes, a quantidade que precisamos.

Nota: Iremos sobrepor a instrução e adiciona-la em nosso código para não quebrar o fluxo original do programa.

Fazendo os códigos

Agora que temos os endereços e o local onde iremos injetar, só codificar!

Primeiro nosso código a ser injetado.
typedef void t_call(...);

#define _printf(...) ((t_call*)0x00401A8C)(__VA_ARGS__) // nossa printf

int get_number_hook()
{
    asm(".intel_syntax noprefix\n"); // muda a sintaxe pro intel
                                     // adicionar -masm=intel no build
    _printf((char*)0xFFFFFFFF, *((int*)0x00404008)) // nossa chamada, com a variavel T

    asm("mov eax, dword ptr ds:[0x404008]"); // instrução sobreposta pelo nosso jmp
    asm("mov edx, 0x00401B52");
    asm("jmp edx");
}
void get_number_hook_end(){}; // apenas para saber onde o get_number_hook termina

Nossas funções de injetar e alocar os dados na memória.
void hook(HANDLE hProc, DWORD address, DWORD funaddr) // injeta nosso código
{
    char JMP[5] = {0};
    JMP[0] = 0xE9;
    *(PDWORD)&JMP[1] = funaddr - address - 5;
    DWORD OldProtect;
    VirtualProtectEx(hProc, address, 5, PAGE_EXECUTE_WRITECOPY, &OldProtect);
    WriteProcessMemory(hProc, address, &JMP, 5, NULL);
    VirtualProtectEx(hProc, address, 5, OldProtect, NULL);
}
DWORD alloc(HANDLE hProcess, PVOID pointer, DWORD size) // aloca os dados
{
    LPVOID addr = VirtualAllocEx(hProcess, 0, size, MEM_COMMIT | MEM_RESERVE ,PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess, addr, pointer, size, NULL);
    return addr;
}

Dando uma atenção em *(PDWORD)&JMP[1] = funaddr - address - 5. Como o JMP é relativo a sua posição atual, é subtraido da posição de destimo a posição atual mais os 5 bytes da instrução. O resto do código é alto explicativo.

Faltando apenas nossa função principal, o main:
int main()
{
    DWORD pID;
    HWND hWnd = FindWindow(NULL, "my program");

    if (hWnd && GetWindowThreadProcessId(hWnd, &pID))
    {
        HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, pID);
        if (hProc)
        {
            DWORD straddr, funaddr;

            printf("HWND: %d\n"  , hWnd );
            printf("PID: %d\n"   , pID  );
            printf("HANDLE: %d\n", hProc);

            printf("Injetando get_number_hook()");

            char *my_str = "You number entered are: %d\n";
            // aloca nosso texto e retorna o endereço 
            straddr = alloc(hProc, my_str, strlen(my_str));

            // aloca nosso código e retorna o endereço
            funaddr = alloc(hProc, &get_number_hook, (DWORD)((DWORD)&get_number_hook_end - (DWORD)&get_number_hook));

            // lembra do _printf((char*)0xFFFFFFFF...) ?
            // muda ao endereço do 0xFFFFFFFF para nosso texto
            WriteProcessMemory(hProc, funaddr + 0x0F, &straddr, 4, NULL);

            // injeta nosso código em 0x00401B4D
            hook(hProc, 0x00401B4D, funaddr);

            CloseHandle(hProc);
        }

    }

    return 0;
}

Dando a atenção devida em WriteProcessMemory(hProc, funaddr + 0x0F, &straddr, 4, NULL), onde mudaremos o endereço para nosso texto alocado anteriormente. Para entender o porque o do funaddr + 0x0F veja a imagem do nosso código compilado e alocado:

Olhe a quantidade de bytes que temos que pular até chegar no endereço que queremos mudar. 15 bytes sendo 0x0F o mesmo que 15 em hexadecimal. Sendo assim temos a posição da função alocada mais 15 bytes.

Downloads

Código fonte e executáveis (5kb)

Conclusão

Muitas coisas podem ser feita com essa técnica, o que mostrei aqui foi apenas o básico. Boa sorte em suas aplicações. ;)

Att, Gilson Fabiano

Nenhum comentário:

Postar um comentário