A Aldeia Numaboa ancestral ainda está disponível para visitação. É a versão mais antiga da Aldeia que eu não quis simplesmente descartar depois de mais de 10 milhões de pageviews. Como diz a Sirley, nossa cozinheira e filósofa de plantão: "Misericórdia, ai que dó!"

Se você tiver curiosidade, o endereço é numaboa.net.br.

Leia mais...

Oficina

Bloco de Notas com linhas numeradas

Seg

28

Mai

2007


20:28

(22 votos, média 4.05 de 5) 


Maluco beleza Vitaminar um programa existente exige um bom conhecimento da estrutura e do funcionamento de executáveis. A proposta deste tutorial, baseado no texto publicado por razzia em 19.08.97, é reforçar as noções básicas através de um exercício de re-engenharia de um velho conhecido: o Bloco de Notas do Windows.

O Bloco de Notas (Notepad), entre outras limitações, não indica números de linha - algo que seria muito útil em diversas situações. Mesmo que você não veja vantagens em adicionar o número das linhas ao programa, talvez seja interessante saber como modificar um executável sem possuir seu código fonte.

Image Escrevi este tutorial em 2001. Na época, a versão do bloco de notas era de 08/06/2000. Sugiro que faça o download desta versão do executável para poder acompanhar o texto. Você o encontra na seção de downloads em Informática / Programação.

O procedimento WndProc

Para determinar o código que será adicionado precisamos investigar um pouco o programa alvo. O "coração" de qualquer programa Windows é o procedimento WndProc. Este procedimento é chamado pelo Windows toda vez que o usuário interage com a janela do programa. O Windows passa alguns parâmetros para este procedimento (por exemplo, a mensagem da janela) para que o WndProc possa saber o que ocorreu e efetuar a atualização da janela.

No caso do bloco de notas, o programa principal gera uma janela filha da classe "edit", que sempre ocupa o tamanho total da janela principal. Se quisermos imprimir nosso próprio texto na janela principal, será necessário reduzir o tamanho da janela filha para que ela não cubra a área total da janela principal. Cada vez que o usuário alterar o tamanho da janela principal, o Windows chama WndProc com a mensagem WM_SIZE. O procedimento WndProc reage a este evento e ajusta o tamanho da janela filha "edit" ao novo tamanho da janela principal.

Se localizarmos o procedimento WndProc será possível avaliar como ele lida com a mensagem WM_SIZE. Entre as várias maneiras de fazer isto, a mais fácil é através da função da API RegisterClass. O bloco de notas usa a função RegisterClassExA, função que registra uma classe janela para uso subsequente em chamadas para as funções CreateWindow ou CreateWindowEx.

ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx );

O parâmetro lpwcx é um ponteiro que aponta para uma estrutura do tipo WNDCLASSEX. Esta estrutura precisa ser preenchida com os atributos de classe apropriados antes do parâmetro ser passado para a função. Caso a função seja executada com sucesso, o valor de retorno é um atom que identifica unicamente a classe que está sendo registrada. Em caso de falha, o valor de retorno é zero.

Conhecendo a função e suas características, podemos usar o W32Dasm ou o IDA para obter o bloco de código que se referente à função. No W32Dasm, após carregar o programa, clique no item de menu Functions / Imports para abrir a janela com todas as funções importadas. Localize nesta janela a função USER32.RegisterClassExA e dê um duplo clique sobre a referência para ser levado ao código correspondente:

:0040318E C745F820504000 mov [ebp-08], 00405020 :00403195 C745D88C1C4000 mov [ebp-28], 00401C8C <---- WndProc ! :0040319C C745F006000000 mov [ebp-10], 00000006 :004031A3 C745D400100000 mov [ebp-2C], 00001000 :004031AA 50 push eax :004031AB 895DDC mov dword ptr [ebp-24], ebx :004031AE 895DE0 mov dword ptr [ebp-20], ebx * Reference To: USER32.RegisterClassExA, Ord:01DBh | :004031B1 FF154C644000 Call dword ptr [0040644C]

Caso você queira testar o IDA, carregue o programa, selecione o item de menu Jump / Jump to name (ou simplesmente Ctrl+L) para obter a janela de nomes. Selecione RegisterClassExA e digite [enter]. Você será levado ao seguinte trecho de código, que é a referência na seção .idata (seção das importações) da função procurada:

.idata:0040644C extrn RegisterClassExA:dword ; DATA XREF: sub_403140+71^r

Feche a janela de nomes e dê um duplo clique sobre a referência "sub_403140+71^r". Você será levado para a seção .text no seguinte ponto:

.text:0040318E mov [ebp+var_30.lpszClassName], offset aNotepad ; "Notepad" .text:00403195 mov [ebp+var_30.lpfnWndProc], offset sub_401C8C <---- WndProc ! .text:0040319C mov [ebp+var_30.hbrBackground], 6 .text:004031A3 mov [ebp+var_30.style], 1000h .text:004031AA push eax ; const WNDCLASSEXA * .text:004031AB mov [ebp+var_30.cbClsExtra], ebx .text:004031AE mov [ebp+var_30.cbWndExtra], ebx .text:004031B1 call ds:RegisterClassExA

Conhecendo a localização do WndProc, vamos analisar o código correspondente a esta chamada. No W32Dasm, selecione o item de menu Goto / Goto Code Location e indique o endereço 401C8C. No IDA, basta dar um duplo clique sobre "sub_401C8C" para ser levado à subrotina:

.text:00401C8C .text:00401C8C ; ¦¦¦ S U B R O U T I N E ¦¦¦ .text:00401C8C .text:00401C8C ; Attributes: bp-based frame .text:00401C8C .text:00401C8C sub_401C8C proc near ; DATA XREF: sub_403140+55o .text:00401C8C .text:00401C8C hWnd = dword ptr 8 .text:00401C8C arg_4 = dword ptr 0Ch .text:00401C8C wParam = dword ptr 10h .text:00401C8C lParam = dword ptr 14h .text:00401C8C .text:00401C8C push ebp .text:00401C8D mov ebp, esp .text:00401C8F push esi .text:00401C90 mov esi, [ebp+arg_4] .text:00401C93 cmp esi, 5 ;mensagem é WM_SIZE ? .text:00401C96 ja short loc_401CAC ; salte de for outra mensagem .text:00401C98 jz loc_401DA4 ; Salte se WM_SIZE = SIZE_RESTORED .text:00401C9E cmp esi, 2 .text:00401CA1 jz loc_401D97 .text:00401CA7 jmp loc_401D2F

Na linha 401C93 o programa checa o tipo de mensagem recebida e garante que seu valor esteja entre 1 e 5. Na linha seguinte, caso o valor seja maior do que 5 (nenhuma mensagem válida), salta para 401CAC. Caso a mensagem seja 5, salta para 401DA4, caso contrário continua determinando o tipo da mensagem. As mensagens válidas estão descritas em WM_SIZE, que representa o tipo da mensagem enviada para uma janela após seu tamanho ter sido modificado.

Parâmetros Observação>/td>ValorHexaSignificado
fwSizeType = wParam;// flag de redimensionamento SIZE_MAXHIDE1Mensagem enviada para todas as janelas popup quando qualquer outra é maximizada
SIZE_MAXIMIZED2Janela foi maximizada
SIZE_MAXSHOW3Mensagem enviada para todas as janelas popup quando qualquer outra tenha sido restaurada ao tamanho original.
SIZE_MINIMIZED4Janela foi minimizada.
SIZE_RESTORED5A janela foi redimensionada mas não minimizada ou maximizada.
nWidth = LOWORD(lParam);// largura da área cliente
nHeight = HIWORD (lParam);// altura da área cliente
Valor de retorno: Se a mensagem foi processada, o valor de retorno é 0 (zero).

De posse dessas informações, fica claro que o programa checa a validade e o valor da mensagem para decidir que caminho tomar. Nós queremos esclarecer o que é feito caso WM_SIZE seja igual a 5 (SIZE_RESTORED), ou seja, o tamanho da janela foi modificado sem que ela tenha sido maximizada ou minimizada. Para isto, precisamos rastrear o salto que o programa efetua para o endereço 401DA4. No IDA, basta um duplo clique sobre "loc_401DA4" (no W32Dasm selecione Goto):

.text:00401DA4 ; ---------------------------------- .text:00401DA4 .text:00401DA4 loc_401DA4: ; CODE XREF: sub_401C8C+Cj .text:00401DA4 mov eax, [ebp+wParam] .text:00401DA7 test eax, eax .text:00401DA9 jz short loc_401DB9 .text:00401DAB cmp eax, 1 .text:00401DAE jz short loc_401DCD .text:00401DB0 cmp eax, 2 .text:00401DB3 jnz loc_4020C4 .text:00401DB9 .text:00401DB9 loc_401DB9: ; CODE XREF: sub_401C8C+11Dj .text:00401DB9 movsx eax, word ptr [ebp+lParam+2] .text:00401DBD movsx ecx, word ptr [ebp+lParam] .text:00401DC1 push eax ; nHeight .text:00401DC2 push ecx ; nWidth .text:00401DC3 call sub_40121B .text:00401DC8 jmp loc_4020C4

Analisando este trecho de código, nos deparamos com uma chamada à subrotina 40121B:

.text:0040121B .text:0040121B ; ¦¦¦ S U B R O U T I N E ¦¦¦ .text:0040121B .text:0040121B ; .text:0040121B ; int __cdecl sub_40121B(int nWidth,int nHeight) .text:0040121B sub_40121B proc near ; CODE XREF: sub_401C8C+137p .text:0040121B .text:0040121B nWidth = dword ptr 4 .text:0040121B nHeight = dword ptr 8 .text:0040121B .text:0040121B push 1 ; bErase .text:0040121D mov eax, hWnd .text:00401222 push 0 ; lpRect .text:00401224 push eax ; hWnd Handle da janela edit .text:00401225 call ds:InvalidateRect .text:0040122B push 1 ; bRepaint .text:0040122D push [esp+4+nHeight] ; nHeight Altura da janela .text:00401231 push [esp+8+nWidth] ; nWidth .text:00401235 push 0 ; Y .text:00401237 push 0 ; X .text:00401239 push hWnd ; hWnd .text:0040123F call ds:MoveWindow ;Atualização do tamanho da janela edit .text:00401245 retn 8 .text:00401245 sub_40121B endp

Note que aparentemente o manipulador (handle) da janela filha "edit" é armazenado no endereço (virtual) de nome hWnd. Para encontrar o valor de hWnd usando o IDA, selecione o item de menu View / Open subviews / Names (Shift+F4) para obter a janela de nomes. Nesta, selecione Search / Search (Alt+T) e digite o nome do endereço (hWnd): você encontrará hWndMain = 405000 e hWnd = 405004. Anote cuidadosamente estes endereços pois ainda vamos precisar muito deles!

Também podemos verificar que, para cada mensagem WM_SIZE, a janela filha "edit" é atualizada com uma chamada para MoveWindow com o novo tamanho da janela principal.

Informações adicionais