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) 


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

Informações adicionais