Oficina

Bloco de Notas com linhas numeradas

Seg

28

Mai

2007


20:28

  • Imprimir
(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.


Criando o código adicional

Vimos que, para cada mensagem WM_SIZE, a janela-filha "edit" é atualizada através de uma chamada a MoveWindow de acordo com o novo tamanho da janela principal. Porém, queremos evitar que a janela-filha "edit" ocupe a área total da janela mãe, ou seja, teremos que criar uma rotina que gerencie a altura das janelas com valores diferentes. Lembrando do trecho de código onde estes parâmetros são utilizados:

... .text:00401224 push eax ; hWnd <--- Handle da janela edit .text:00401225 call ds:InvalidateRect .text:0040122B push 1 ; bRepaint .text:0040122D push [esp+0C] ; nHeight <--- Altura da janela .text:00401231 push [esp+0C] ; nWidth <--- Largura da janela .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

Colocando um salto (jump) na linha 40122D, podemos fazer com que o programa faça um "desvio" para o nosso código, o qual irá alterar nHeight de acordo com nossas instruções antes de alcançar a chamada que atualiza o tamanho da janela-filha "edit". Para gerenciar o tamanho da janela-filha "edit", nosso novo código altera o valor original da altura para repassá-lo à função MoveWindow:

Código para alterar a altura da janela-filha "edit"

Codigo_Altura: MOV EAX,[ESP+C] ; Obtém o valor da altura (nHeight) SUB EAX,14 ; Subtrai 14 hexa = 20 dec da altura (3) MOV novaAltura,EAX ; Guarda valor calculado em 40DB70 (1) PUSH EAX ; Põe valor da altura no topo da pilha (2) PUSH [ESP+C] ; Põe valor da largura no topo da pilha (nWidth) JMP 00401235 ; Retorna ao local da chamada

Observação: o desvio para nosso bloco de código vai ser obtido a partir de uma instrução jump que ocupará 5 bytes. Com isto, existem duas linhas do código original que serão "comidas" pelo jump e que precisam ser substituídas em algum ponto do nosso novo código. As duas linhas estão em 40122D e 401231. A primeira coloca no topo da pilha a altura (nHeight) da janela-filha com push [esp+C] e a segunda coloca no topo da pilha a largura (nWidth) da mesma janela-filha, também com um push [esp+C]. Como utilizamos o registrador EAX para calcular o novo valor da altura da janela-filha "edit", substituímos a linha 40122D push [esp+C] pela linha indicada em (1), colocando diretamente o valor de EAX no topo da pilha. A linha 401231 é substituída pela linha indicada em (2), mantendo a instrução push [esp+C].

Além disso, na linha indicada em (3), o valor calculado da nova altura é salvo no endereço 40DB70 porque vai ser necessário no módulo de código referente à impressão, citado logo adiante. A escolha do endereço das variáveis encontra-se logo abaixo na tabela de variáveis.

Após ter projetado o código para gerenciar o tamanho da janela-filha "edit", precisamos imprimir algum texto na janela principal (os números das linhas). Toda vez que uma janela precisa ser "repintada" (por exemplo, quando esteve encoberta por outra janela ou quando o usuário alterar seu tamanho), o Windows chama o procedimento WndProc com WM_PAINT. Isto significa que teremos que "caçar" a mensagem WM_PAINT e, novamente, forçar um salto para a nossa rotina que, desta vez, será um módulo que imprima texto.

Também precisamos imprimir texto toda vez que o cursor muda a sua posição vertical. A melhor maneira para conseguir isto é ignorar todas as mensagens possíveis que informem qualquer mudança na posição do cursor e passar a comparar a nova posição do cursor com a antiga.

Examinando o início do código do WndProc, nota-se que nada é feito com a mensagem WM_PAINT. Desta forma, no início do procedimento WndProc podemos inserir um salto (jump) para nosso módulo de código:

... .text:00401C8C push ebp .text:00401C8D mov ebp, esp <--- Local indicado para o salto .text:00401C8F push esi .text:00401C90 mov esi, [ebp+0C] .text:00401C93 cmp esi, 5 <--- Retornando para este endereço

Novamente precisaremos de 5 bytes para forçar o salto. Temos espaço suficiente entre os endereços 401C8C e 401C90. O endereço para o retorno no nosso módulo de código será 401C93.


Código para imprimir números de linhas

Código_Imprime: mov ebp, esp ; Substituindo a linha 401C8D push esi ; Substituindo a linha 401C8F mov esi, [ebp+0C] ; Substituindo a linha 401C90 (4) push 0 ; lParam push -1 ; wParam push C9 ; EM_LINEFROMCHAR push [405004] ; Handle da janela-filha "edit" call SendMessageA ; Após esta chamada, EAX contém a ; posição Y do cursor (5) cmp eax,linhaAtual ; Posição do cursor mudou ? jnz atualiza_nro_linha ; Sim, imprimir novo número de linha cmp esi,0Fh ; A mensagem atual é WM_PAINT ? jnz nao_atualiza ; Não, retorne para WndProc atualiza_nro_linha: mov linhaAtual,eax ; Salva a posição Y do cursor em 40DB78 (6) inc eax ; Ajusta o número da linha push eax ; Põe número ajustado no topo da pilha (7) lea eax,stringFormatação ; Obtém a string de formatação push eax ; Põe string de formatação no topo da pilha (8) lea eax,offsetNroAscii ; Obtém posição para guardar número push eax ; Põe posição no topo da pilha (9) call wsprintfA ; Transforma número em ASCII e transfere ; resultado para a posição offsetNroAscii push [405000] ; Handle da janela principal (10) call GetDC ; Obtém contexto do ambiente (11) mov hContexto,eax ; Salva Ambiente push 1 ; Coordenada X para expansão de Tabs push 0 ; Array de posições de Tabs push 0 ; Número de Tabs no array push F ; Número de caracteres da string (nCount) (12) lea eax,stringNroLinha ; Endereço da String push eax ; Põe string no topo da pilha push novaAltura ; Coordenada Y da posição inicial push 0 ; Coordenada X da posição inicial push [hContexto] ; Põe handle de contexto no topo da pilha (13) call TabbedTextOut ; Imprime o texto push [hContexto] ; Põe handle de contexto no topo da pilha push [405000] ; Põe handle da janela principal na pilha (14) call ReleaseDC ; Libera o Ambiente add esp,C ; Corrige o Ponteiro da Pilha nao_atualiza: jmp 00401C93 ; Volta para WndProc

Note que foram utilizadas algumas variáveis. Cada uma destas variáveis possui um endereço próprio. Alguns dos valores são alterados pelo próprio programa, outros precisam ser introduzidas através de um editor hexadecimal. Os endereços das variáveis foram escolhidos num bloco logo após o final do código:

Referência NomeEndereçoValor
(3)novaAltura 40DB70
(5)linhaAtual 40DB78
(7)stringFormatação 40DB80%#05d
(12)stringNroLinha 40DB90linha :
(8)offsetNroAscii 40DB9A
(11)hContexto 40DBA0

Foram usadas somente funções da API do Windows já importadas pelo notepad.exe (verifique a tabela de importações). O que fazer caso seja necessário lançar mão de funções da API que não constem da tabela de importações? No final do tutorial há uma explicação de como proceder nestes casos.

Algumas funções necessitam do handle da janela principal como parâmetro. Já foi descrito anteriormente como encontrar este valor.

  • (4) Nesta linha e nas três seguintes a chamada para a função SendMessageA é preparada. Esta função obtém o valor da coordenada Y do cursor e o retorna no registrador EAX.
  • (6) Se a posição do cursor for 0 (zero), estamos na linha 1. Se for 1, estamos na linha 2, etc. Portanto, precisamos ajustar o valor da coordenada Y do cursor somando 1.
  • (7) Preparamos os parâmetros da função wsprintfA.
  • (9) A função wsprintfA transforma o número enviado como parâmetro em caracteres ASCII de acordo com a string de formatação e coloca o resultado no endereço especificado. Por exemplo, transforma 1 em "00001".
  • (10) Para utilizar a função TabbedTextOut, precisamos primeiro obter o handle do contexto. Para isto chamamos a função GetDC com o parâmtero da janela principal (onde o texto deve ser impresso).
  • (11) A função GetDC devolve o handle do contexto no registrador EAX. Como precisaremos deste handle para liberar o contexto, armazenamos seu valor na variável hContexto.
  • (13) A função TabbedTextOut exige muitos parâmetros para imprimir a string enviada no local especificado. As 9 linhas anteriores à chamada preparam os parâmetros necessários.
  • (14) Finalmente, após a impressão da string na janela mãe, precisamos liberar o contexto que foi bloqueado pela função GetDC. Para isto utilizamos a função ReleaseDC.

Enxertando o código no executável

Já temos um projeto de código, agora é preciso incluí-lo no executável. Mais uma vez, vamos por partes para não nos perdermos.

Inserindo o código da altura da janela

É preciso encontrar espaço ocioso no executável que possa ser utilizado para incluir o código. Usando o PE Explorer (faça o download do trial na Heaven Tools), verificamos o seguinte nos cabeçalhos das seções:

NomeTam.Virtual End.VirtualTam.DadosPonteiro CaracterísticasSobra
.text00003EFC 004010000000400000001000 60000020104 (260)
.data0000083C 004050000000100000005000 C00000407C4 (1988)
.idata00000E22 004060000000100000006000 400000401DE (478)
.rsrc00006000 004070000000600000007000 40000040-0-
.reloc00000AAE 0040D000000010000000D000 42000040552 (1362)
  • Procurando espaço livre

Vamos tomar a seção .data como exemplo. Esta seção foi dimensionada para ocupar um espaço de 1000 (tamanho dos dados) a partir do endereço 405000. Seu tamanho virtual é 83C e o espaço total disponível é de 1000, indicando que ainda há 7C4 bytes livres (1000 - 83C = 7C4). Tudo isto no sistema hexadecimal. Transformando os números para o sistema decimal, apenas por curiosidade, sabemos que o espaço reservado é de 4096, o espaço ocupado é de 2108, restando portanto 1988 bytes livres.

A segunda maior sobra de espaço se encontra na seção .reloc, onde há 1362 (552 em hexa) bytes disponíveis. Vamos escolher a seção .reloc para abrigar nosso código adicional. Ele cabe perfeitamente neste espaço livre, portanto, não vai haver a necessidade de ampliar o tamanho da seção escolhida.

  • Calculando o tamanho do salto

Segue-se agora uma longa jornada de cálculos. O primeiro deles será determinar o endereço do primeiro salto. Conforme visto anteriormente, foi decidido fazer um desvio no ponto onde o programa põe na pilha o valor da altura e da largura da janela-filha "edit". Reveja o trecho de código obtido através do debugador OllyDbg (note as referências preciosas que o programa fornece!):

0040121B 6A 01 PUSH 1 ;Erase = TRUE 0040121D A1 04504000 MOV EAX,DWORD PTR DS:[405004] 00401222 6A 00 PUSH 0 ;pRect = NULL 00401224 50 PUSH EAX ;hWnd => 000002A8 (class='Edit', ;wndproc=80573CEC, parent=00000414) 00401225 FF15 F4644000 CALL DWORD PTR DS:[<&USER32.InvalidateRect>] 0040122B 6A 01 PUSH 1 ;Repaint = TRUE 0040122D FF7424 0C PUSH DWORD PTR SS:[ESP+C] ;Height 00401231 FF7424 0C PUSH DWORD PTR SS:[ESP+C] ;Width 00401235 6A 00 PUSH 0 ;Y = 0 00401237 6A 00 PUSH 0 ;X = 0 00401239 F35 04504000 PUSH DWORD PTR DS:[405004] ;hWnd = 000002A8 (class='Edit', ;wndproc=80573CEC, parent=00000414) 0040123F FF15 F8644000 CALL DWORD PTR DS:[<&USER32.MoveWindow>] MoveWindow 00401245 C2 0800 RETN 8

Image O OllyDbg está disponível na seção de downloads em Informática / Debuggers.

Portanto, faremos um salto incondicional - jmp do endereço 40122D para o início do espaço vago da seção .reloc. Pelo cabeçalho de seções verificamos que o endereço virtual inicial de .reloc é 40D000 e nós queremos colocar o código adicional AAEh bytes adiante. Basta somar ao endereço virtual da seção o tamanho do código para obter o endereço inicial do código adicional, ou seja, 40D000 + AAE = 40DAAE. Nosso salto, então, deverá ser de 40122D para 40DAAE, o que corresponde a um salto de C881 bytes. Como sabemos que nossa instrução, por ser um salto longo, conterá 5 bytes, precisamos descontar os mesmos para calcular o valor correto para o salto: 40DAAE - 40122D - 5 = C87C.

De posse deste valor, vamos compor a instrução lembrando que a distância do salto é indicada em 4 bytes na ordem inversa, ou seja, 00 00 C8 7C será indicado como 7C C8 00 00:

Instrução Código hexadecimalObservação
JMP E9Salto incondicional longo
byte 1 7C
byte 2 C8
byte 3 00
byte 4 00Resultado: E9 7CC80000

Para o salto de retorno é preciso pular alguns bytes "para trás". No exemplo anterior, pulamos 51324 bytes "para frente", no retorno teremos que saltar 51343 bytes "para trás", ou seja, -51343 (menos 51343). Em hexadecimal, 401235 - 40DABF - 5 = FFFF3771.

  • Alterando o código para o salto

O programa original deve sofrer as seguintes alterações para a introdução do salto:

Endereço Código originalAlterar para
0040122D FF7424 0C PUSH DWORD PTR SS:[ESP+C]E9 7C C8 00
00401231 FF7424 0C PUSH DWORD PTR SS:[ESP+C]00 90 90 90
00401235 6A 00 PUSH 0

Observe que ocupamos apenas o primeiro byte (FF) da instrução no endereço 401231 para poder completar nossa instrução de salto. Restaram os códigos 74, 24 e 0C que foram anulados com três instruções NOP (90) para manter íntegro o tamanho do código original e não desestruturar outros endereçamentos existentes. Com o salto devidamente inserido, este bloco de código passa a ser:

Endereço Salto adicionado
0040122D E9 7CC80000JMP NOTEPAD.0040DAAE
00401232 90NOP
00401233 90NOP
00401234 90NOP
00401235 6A 00PUSH 0
  • Adicionando o código para alterar o tamanho da janela "edit"

Quando alcançar a linha 40122D, o salto é efetuado e a execução do código continua no endereço 40DAAE, exatamente o ponto onde vamos inserir o bloco 1 do código adicional. Nele, antes de refazermos as operações das linhas 40122D e 401231 que foram suprimidas para poder inserir o salto, a altura da janela "edit" é diminuída em 20 pixels. Para tanto, utilizamos o registrador EAX:

Endereço HexaOperaçãoObservações
0040DAAEE9 7CC80000 MOV EAX,DWORD PTR SS:[ESP+C]; Obtém o valor da altura
0040DAB283E8 14 SUB EAX,14; Subtrai 14h = 20d da altura
0040DAB5A3 70DB4000 MOV DWORD PTR DS:[40DB70],EAX; Guarda nova altura em 40DB70
0040DABA50 PUSH EAX; Nova altura na pilha
0040DABBFF7424 0C SS:[ESP+C]; Largura no topo da pilha
0040DABFE9 7137FFFF JMP NOTEPAD.00401235; Retorna ao ponto de chamada

Use um editor hexadecimal (Hackman ou Hacker's View, por exemplo) e faça as alterações propostas numa cópia do programa notepad.exe (o Hacker's View permite a entrada de código assembly e calcula os jumps, o que facilita muito o trabalho). Qualquer um dos programas sugeridos mostra o código a partir da posição 0 (zero) e NÃO pelo endereço virtual. Portanto, é preciso conhecer a posição dos endereços virtuais pelo seu deslocamento (offset) a partir da posição zero. Isto se consegue subtraindo a base da imagem (00400000) do endereço pretendido. Por exemplo, o deslocamento do endereço 40122D é 122D (40122D - 400000 = 122D). Caso você encontre dificuldades, o W32Dasm indica no rodapé o offset de cada linha de código wink

  • Testando a primeira etapa

Após fazer as modificações sugeridas é aconselhável testar o programa modificado. Tudo que esperamos é que a janela-filha "edit" tenha uma altura menor do que a janela principal. Se tudo correu bem, a janela do novo bloco de notas tem um "rodapé" de 20 pixels (a diferença de altura entre as duas janelas), o qual será usado para imprimir o texto com a indicação dos números das linhas.

notepad
Nova janela do bloco de notas

Inserindo o código de impressão

Já identificamos um espaço ocioso no executável onde é possível incluir o código, já sabemos como calcular a distância de saltos e onde inseri-los, então... vamos ao trabalho.

  • Inserindo o segundo salto

Conforme já visto, decidimos fazer um desvio no início do procedimento WndProc. Reveja o trecho de código citado:

... .text:00401C8D mov ebp, esp ;Local indicado para o salto .text:00401C8F push esi .text:00401C90 mov esi, [ebp+arg_4] .text:00401C93 cmp esi, 5 ;Retornando para este endereço

Portanto, faremos um salto incondicional - jmp do endereço 401C8D para um endereço após o final do nosso primeiro bloco de código. Deixamos alguns bytes zerados e iniciamos o segundo bloco de código em 40DACD (o primeiro bloco terminou em 40DABF). O salto, então, será de BE3B bytes (40DAC8 - 401C8D - 5 = BE3B).

De posse deste valor, vamos compor a instrução, lembrando novamente que a distância do salto é indicada em 4 bytes na ordem inversa, ou seja, 00 00 BE 3B será indicado como 3B BE 00 00

O programa original deve sofrer as seguintes alterações para a introdução do salto:

Endereço Código originalAlterar para
00401C8D 8BEC MOV EBP,ESPE9 3B
00401C8F 56 PUSH ESIBE
00401C90 8B75 0C MOV ESI,DWORD PTR SS:[EBP+C]00 00 90
00401C93 83FE CMP ESI,5

Observe que ocupamos apenas os dois primeiros bytes (8B e 75) da instrução no endereço 401C90 para poder completar a instrução de salto. Restou o código 0C, o qual foi anulado com uma instrução NOP (90) para manter íntegro o tamanho do código original e não desestruturar outros endereçamentos existentes. Com o salto devidamente inserido, este bloco de código passa a ser:

Endereço Salto adicionado
00401C8D E9 3EBE0000JMP NOTEPAD.0040DACD
00401C92 90NOP
00401C93 83FECMP ESI,5
  • Adicionando o código da impressão dos números de linha

Quando alcançar a linha 401C8D, o salto é efetuado e a execução do código continua no endereço 40DACD, exatamente onde vamos inserir o bloco 2 do código adicional. Nele, antes de qualquer outra coisa, vamos refazer as operações suprimidas pela introdução do salto incondicional repetindo o código original:

Endereço HexaOperaçãoObservações
0040DACD8BEC MOV EBP,ESP
0040DACF56 PUSH ESI
0040DAD08B75 0C MOV ESI,DWORD PTR SS:[EBP+C]; ESI contém a mensagem

A seguir preparamos os parâmetros para a função SendMessageA da API do Windows e a chamamos. Lembre-se de que os parâmetros precisam ser colocados na pilha na ordem inversa (para que a função possa acessá-los na ordem correta). O endereço da função pode ser obtido através do PE Explorer, na janela "Imports", sob a rubrica USER32:

004064E4 function SendMessageA(
   hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM):
   LRESULT; stdcall; external 'user32.dll' name 'SendMessageA' index 537
Endereço HexaOperaçãoObservações
0040DAD36A 00 PUSH 0; lParam
0040DAD56A FF PUSH -1; wParam
0040DAD768 C9000000 PUSH C9; Mensagem
0040DADCFF35 04504000 PUSH DWORD PTR DS:[405004]; Handle da janela filha "edit"
0040DADEFF15 E4644000 CALL DWORD PTR DS:[<&USER32.SendMessage>]; Após esta chamada, eax contém a posição Y do cursor

No retorno da função SendMessageA, o registrador EAX contém a posição Y do cursor na janela-filha "edit", ou seja, a linha atual da janela. Como já designamos o local de armazenamento do valor da variável dwLinha (40DB78), podemos comparar o valor de ambos. Se houver diferença, significa que o cursor mudou de linha e o rodapé precisa ser atualizado. Se ambos forem iguais, podemos voltar ao código original.

Endereço HexaOperaçãoObservações
0040DAE83B05 78DB4000 CMP EAX,DWORD PTR DS:[40DB78]; Posição do cursor mudou?
0040DAEE75 05 JNZ SHORT 0040DAF5; Sim, imprimir novo número de linha
0040DAF083FE 0F CMP ESI,0F; A mensagem atual é WM_PAINT?
0040DAF375 64 JNZ SHORT 0040DB59; Não, vá para o fim da rotina e retorne para WndProc

Caso o cursor tenha trocado de linha, algumas medidas precisam ser tomadas: atualizar a variável dwLinha em 40DB78 com o novo valor, somar 1 ao número da linha que deve ser impresso porque as linhas iniciam sua numeração em 0 (linha 0 - imprimimos linha 1, linha 1 - imprimimos linha 2, etc) e fazer uma chamada à função wsprintfA para "montar" a string que deve aparecer no rodapé:

0040641C function wsprintf(
   Output: PAnsiChar; Format: PAnsiChar):
      Integer; stdcall; external 'user32.dll' name 'wsprintfA' index 692;

Esta função recebe o valor que deve ser "stringado" (o número ajustado da linha), a string "%#05d" que corresponde ao formato de número precedido de zeros em 5 casas (1 será 00001) e o endereço onde deve ser armazenado o resultado (40DB90 + 0A = 40DB9A). O endereço da nossa "variável" para a string do rodapé é 40DB90. Os caracteres " Linha : " ocupam 10 posições, portanto, queremos que, a partir da 11a. posição seja armazenado o número transformado em ASCII no formato indicado pela string de formatação:

Endereço HexaOperaçãoObservações
0040DAF5A3 78DB4000 MOV DWPRD PTR DS:[40DB78],EAX; Linha mudou, guarde o novo valor
0040DAFA40 INC EAX; Ajusta número de linha
0040DAFB50 PUSH EAX; Põe o novo número de linha na pilha
0040DAFC8D05 80DB4000 LEA EAX,DWORD PTR DS:[40DB80]; String de formatação
0040DB0250 PUSH EAX; Põe string na pilha
0040DB038D05 9ADB4000 LEA EAX,DWROD PTR DS:[40DB9A]; Endereço para o resultado (ASCII)
0040DB0950 PUSH EAX; Põe endereço na pilha
0040DB0AFF15 1C644000 CALL DWORD PTR DS:[<&USER32.wsprintf>]; Transforma número em ASCII

Agora precisamos obter o contexto do ambiente da janela principal para prepararmos a impressão da string que se encontra pronta no endereço 40DB90 na posição que definimos como rodapé. Para obter e bloquear o contexto utilizamos a função GetDC. Logo a seguir, armazenamos o handle do contexto no endereço 40DBA0 porque precisaremos do mesmo para imprimir a string e para liberar o contexto bloqueado depois de efetuarmos a impressão.

004064D8 function GetDC(hWnd: HWND):
   HDC; stdcall; external 'user32.dll' name 'GetDC' index 257;
Endereço HexaOperaçãoObservações
0040DB10FF35 00504000 PUSH DWORD PTR DS:[405000]; Handle da janela principal
0040DB16FF15 D8644000 CALL DWORD PTR DS:[<&USER32.GetDC>]; Obtém contexto do ambiente
0040DB1CA3 A0DB4000 MOV DWORD PTR DS:[40DBA0],EAX; Salva Handle do Contexto

Tudo pronto para imprimir! A função utilizada é a TabbedTextOutA que se encarregará de transferir a string " Linha : 0000x" localizada no endereço 40DB90 para o rodapé da janela principal. Esta função exige uma pá de parâmetros:

00406468 function TabbedTextOut
  (hdc: HDC; X, Y: Integer; lpString: PAnsiChar; nCount, nTabPositions: Integer;
   var lpnTabStopPositions; nTabOrigin: Integer):
    Longint; stdcall; external 'user32.dll' name 'TabbedTextOutA' index 633;
Endereço HexaOperaçãoObservações
0040DB216A 01 PUSH 1; Origem do Tab
0040DB236A 00 PUSH 0;
0040DB256A 00 PUSH 0;
0040DB276A 0F PUSH F; Nro de caracteres (nCount)
0040DB298D05 90DB4000 LEA EAX,DWORD PTR DS:[40DB90]; String que deve ser impressa (lpString)
0040DB2F50 PUSH EAX;
0040DB30FF35 70DB4000 PUSH DWORD PTR DS:[40DB70]; Posição Y da string (= altura da janela edit)
0040DB366A 00 PUSH 0; Posição X da string
0040DB38FF35 A0DB4000 PUSH DWORD PTR DS:[40DBA0]; Handle do Contexto
0040DB3EFF15 68644000 CALL DWORD PTR DS:[<&USER32.TabbedTextOut>]; Imprime a string

Ufa ! Está por pouco... só falta liberar o contexto para poder voltar ao código original. A função que utilizamos é a ReleaseDC:

004064DC function ReleaseDC(hWnd: HWND; hDc: HDC):
  Integer; stdcall; external 'user32.dll' name 'ReleaseDC' index 520;
Endereço HexaOperaçãoObservações
0040DB44FF35 A0DB4000 PUSH DWORD PTR DS:[40DBA0]; Handle do Contexto
0040DB4AFF35 00504000 PUSH DWORD PTR DS:[405000]; Handle da Janela Principal
0040DB50FF15 DC644000 CALL DWORD PTR DS:[<&USER32.ReleaseDC>]; Libera o Handle do Contexto

Hora de voltar para o código principal. Porém, observando o Stack Pointer (ESP - Ponteiro da Pilha), verificamos que fizemos uma salada com tantos pushes. Avançamos 12 bytes que precisam ser "corrigidos" para evitar erros em referências posteriores. Como sabemos que a pilha "anda ao contrário", para DESCER 12 bytes precisamos SOMAR estas posições (12 dec = 0C hexa).

Endereço HexaOperaçãoObservações
0040DB5683C4 0C ADD ESP,C; Corrige o Stack Pointer
0040DB59E9 3541FFFF JMP 00401C93; Volta para WndProc

Fazendo os remendos (patches)

Para não perder tempo é preciso planejar. Planejamos o perfil do código, planejamos sua inserção no executável e agora vamos por a mão na massa. Para inserir os bytes referentes às instruções do primeiro bloco de código, podemos usar (se é que você já não usou) qualquer editor hexadecimal. Afinal, não é tanto código assim.

Para o segundo bloco de código sugiro o uso do Hacker's View (HIEW), pois permite a inserção direta do código assembly, além de calcular os deslocamentos dos jumps. Isto facilita muito o trabalho. Faça as alterações propostas numa cópia do programa notepad.exe.

Os editores mais conhecidos, como você já sabe, mostram o código a partir da posição 0 (zero) e NÃO pelo endereço virtual. Portanto, precisamos conhecer a posição dos endereços virtuais pelo seu deslocamento (offset) a partir da posição zero, o que se consegue subtraindo a base da imagem (00400000) do endereço pretendido. Por exemplo, o deslocamento do endereço 40DACD é DACD (40DACD - 400000 = DACD). Caso você encontre dificuldades, o W32Dasm indica no rodapé o offset de cada linha de código.

Após incluir os dois blocos de código, NÃO SE ESQUEÇA das variáveis. Duas delas precisam ser definidas: a string de formatação e a string do número de linha (veja em tabela de variáveis). Ainda com o editor hexadecimal da sua preferência vá até o offset DB80 e digite o valor da string de formatação. No offset DB90 digite a string do número de linha. Observe que há dois espaços (20 20) antes da palavra linha.

Endereço HexadecimalASCII
0040DB7000 00 00 00 00 00 00 00 ........
0040DB7800 00 00 00 00 00 00 00 ........
0040DB8025 23 30 35 64 00 00 00 %#05d...
0040DB8800 00 00 00 00 00 00 00 ........
0040DB9020 20 6C 69 6E 68 61 20 linha
0040DB983A 20 00 00 00 00 00 00 : ......
0040DBA000 00 00 00 00 00 00 00 ........

Testando o novo bloco de notas

Finalmente podemos testar o programa modificado. Já sabemos que a janela-filha "edit" tem uma altura menor do que a janela principal, formando um "rodapé" de 20 pixels (a diferença de altura entre as duas janelas). Este rodapé é usado para imprimir o texto com a indicação dos números de linha. Se tudo estiver em ordem, o aspecto do novo notepad será este:

Notepad
Bloco de notas com número de linha

Recado da vó Vicki vovo

Se você conseguiu chegar até o final deste artigo sem perder o fôlego, considere-se um herói da resistência. Agora, se além de acompanhar o texto ainda conseguiu reproduzir a experiência desta oficina de informática e criar seu bloco de notas com números de linha, seja bem vindo ao clube dos "malucos beleza", os escovadores de bits adeptos da co-engenharia que conseguiram se safar da camisa de força smile

казино бесплатные игрысумка для визажиста купитьtopodinноутбукпосудавидео востокMFX Broker