# Importando tipos de outros projetos

2010-01-11 tag_coding ^

A engenharia reversa das entranhas do kernel não tem limites se você sabe o que está fazendo. No entanto, algumas facilidades do depurador podem ajudar a minimizar o tempo que gastamos para analisar uma simples estrutura. Por exemplo, o Process Environment Block de um processo específico.

   windbg -kl
   
   Microsoft (R) Windows Debugger Version 6.9.0003.113 X86
   Copyright (c) Microsoft Corporation. All rights reserved.
   
   Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
   Symbol search path is: SRV*c:\tools\symbols*http://msdl.microsoft.com/download/symbols
   Executable search path is:
   *******************************************************************************
   WARNING: Local kernel debugging requires booting with kernel
   debugging support (/debug or bcdedit -debug on) to work optimally.
   *******************************************************************************
   Windows XP Kernel Version 2600 (Service Pack 3) MP (2 procs) Free x86 compatible
   Product: WinNt, suite: TerminalServer SingleUserTS
   Built by: 2600.xpsp_sp3_gdr.090804-1435
   Kernel base = 0x804d7000 PsLoadedModuleList = 0x8055d720
   Debug session time: Mon Jan 11 10:36:50.061 2010 (GMT-2)
   System Uptime: 5 days 1:05:24.958
   
   Microsoft (R) Windows Debugger Version 6.9.0003.113 X86
   Copyright (c) Microsoft Corporation. All rights reserved.
   
   lkd> !process 0 0 notepad.exe
   PROCESS 89068700  SessionId: 0  Cid: 0ec4    Peb: 7ffda000  ParentCid: 0b0c
       DirBase: 0ac80a80  ObjectTable: e143a7d8  HandleCount: 152.
       Image: notepad.exe

O comando !peb traz inúmeras informações sobre essa estrutura. Mas talvez estivéssemos interessados em coisas não mostradas por esse comando, mas que existem na estrutura.

Nesse caso, podemos criar um projeto vazio que contenha a definição da estrutura **como acreditamos** que esteja na versão do kernel que estamos depurando.

Compilamos e geramos um PDB (arquivo de símbolos) que contém a definição desse tipo. Tudo que precisamos fazer agora é carregar esse símbolo na sessão que estivermos depurando.

É claro que nosso executável não vai existir na sessão de kernel local, mas isso não importa. Podemos usar qualquer módulo carregado e usá-lo como _host _de nosso conjunto de símbolos:

   
   lkd> lm
   start    end        module name
   804d7000 806e5000   nt         (pdb symbols)          c:\tools\symbols\ntkrpamp.pdb\D8743252F83B4F59985D6E19F33BFCAF1\ntkrpamp.pdb
   
   Unloaded modules:
   a5513000 a553e000   kmixer.sys
   bac50000 bac57000   USBSTOR.SYS
   a5711000 a5746000   truecrypt.sys
   a5731000 a5746000   wudfrd.sys
   a5a19000 a5a23000   wpdusb.sys
   ...
   a5731000 a5746000   wudfrd.sys
   a57a9000 a57b3000   wpdusb.sys
   a571b000 a5746000   kmixer.sys
   babf0000 babf5000   Cdaudio.SYS
   ba489000 ba48c000   Sfloppy.SYS
   babe8000 babed000   Flpydisk.SYS
   babe0000 babe7000   Fdc.SYS 
   
----- Build started: Project: KernelTypes, Configuration: Debug Win32 ------
   Compiling...
   KernelTypes.cpp
   Linking...
   LINK : program database c:\Tests\KernelTypes\Debug\KernelTypes.pdb missing; performing full link
   Embedding manifest...
   Build log was saved at "file://c:\Tests\KernelTypes\Debug\BuildLog.htm"
   KernelTypes - 0 error(s), 0 warning(s)
   ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
   
   Microsoft Windows XP [versÎáÎ÷Îýo 5.1.2600]
   (C) Copyright 1985-2001 Microsoft Corp.
   
   C:\Tests\KernelTypes\Debug>ren KernelTypes.pdb usbstor.pdb
   
   lkd> .sympath C:\Tests\KernelTypes\Debug
   Symbol search path is: C:\Tests\KernelTypes\Debug
   lkd> .reload /i /f usbstor.sys
   lkd> lm m usb*
   start    end        module name
   bac60000 bac66700   USBSTOR  M (private pdb symbols)  C:\Tests\KernelTypes\Debug\usbstor.pdb

Depois que o símbolo foi carregado em nosso módulo de mentirinha, tudo que temos a fazer é alterar o contexto do processo atual (para que os endereços de user mode façam sentido) e moldar nossa memória com o comando dt, usando o tipo importado do símbolo carregado.

   
   lkd> .process 89068700
   Implicit process is now 89068700
   lkd> dt usbstor!_peb 7ffda000
      +0x000 InheritedAddressSpace : 0xdc ''
      +0x001 ReadImageFileExecOptions : 0xff ''
      +0x002 BeingDebugged    : 0x35 '5'
      +0x003 SpareBool        : 0x1 ''
      +0x004 Mutant           : 0x01360000
      +0x008 ImageBaseAddress : 0x0135e000
      +0x00c Ldr              : (null)
      +0x010 ProcessParameters : 0x00001e00 _RTL_USER_PROCESS_PARAMETERS
      +0x014 SubSystemData    : (null)
      +0x018 ProcessHeap      : 0x7ffda000
      +0x01c FastPebLock      : (null)
      +0x020 SparePtr1        : 0x00000efc
      +0x024 SparePtr2        : 0x000008b8
      +0x028 EnvironmentUpdateCount : 0
      +0x02c KernelCallbackTable : (null)
      +0x030 SystemReserved   : [1] 0x7ffde000
      +0x034 ExecuteOptions   : 0y00
      +0x034 SpareBits        : 0y000000000000000000000011111100 (0xfc)
      +0x038 FreeList         : (null)
      +0x03c TlsExpansionCounter : 0
   ...

Para que isso funcione, a estrutura definida tem que bater offset por offset com os dados na memória, o que envolve alinhamento (se lembre do pragma pack) e versionamento corretos. Se isso não ocorrer, logo aparecerá algum lixo nos membros da estrutura que não fará sentido. Se isso ocorrer, detecte onde o lixo começa e verifique se o membro existe nessa versão do sistema operacional, ou se o alinhamento está de acordo com o módulo analisado.

Acho que não é preciso dizer que isso não serve apenas para kernel mode =)

!peb traz inúmeras informações sobre essa estrutura. Mas talvez estivéssemos interessados em coisas não mostradas por esse comando, mas que existem na estrutura: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Process/PEB.html


# Passagem por valor e emails com anexo

2010-01-18 tag_coding ^

Mais uma analogia vencedora para ponteiros, chamadas por valor e chamadas por referência: e-mails.

Quando passamos um parâmetro por valor, estamos enviando um e-mail com um arquivo em anexo. Não importa o que o destinatário faça com o arquivo: nós não vamos saber o que foi mudado se ele não enviar uma outra cópia.

Por outro lado, ao passar um parâmetro por referência, estamos enviando um e-mail com um endereço de onde está o arquivo. Se o usuário alterar o arquivo diretamente do endereço que enviamos será possível ver essa alteração imediatamente, pois ambos estão olhando para o mesmo valor na memória.

A analogia pode ser levada mais longe, com ponteiros de ponteiros: enviamos um e-mail com o endereço de um arquivo; dentro desse arquivo existe um endereço para outro arquivo. Dessa forma é possível tanto alterar o arquivo final quanto o endereço de onde ele está; ou ainda "apontar" para outro arquivo, trocando o endereço de dentro do primeiro arquivo.

Assim é fácil de visualizar que os dados estão sempre em um arquivo que ocupa espaço na memória (do disco ou da RAM), mas endereços também podem ocupar espaço, se estiverem salvos em um arquivo.

Dessa forma, um e-mail que contenha um arquivo em anexo vai ser muito maior que um e-mail apenas com o endereço do arquivo, mas é porque todo o conteúdo do arquivo está dentro do e-mail no primeiro caso. No segundo caso, o endereço ocupa apenas alguns caracteres que identificam a localização do arquivo.


# House

2010-01-25 ^

Depois da analogia entre depuração e CSI, nada como fazer o mesmo com o seriado estilo House.

Quais as semelhanças com a profissão de programador-depurador?

Em primeiro lugar, a busca por pistas. Se algo está errado com o programa, vivemos criando teorias mirabolantes a respeito do porquê tal função estar retornando zero. No entanto, como não temos tanta capacidade adivinhatória assim, geralmente nossos palpites estão errados, e o fundo do poço irá nos mostrar uma outra função que nem estava ainda na história.

Mas existem alguns pontos-comuns de conhecimento que sempre desenvolvemos no decorrer da carreira:

 * Se a última instrução do código é zero (ou algo próximo disso), provavelmente a pilha foi corrompida por alguém que tentou zerar uma variável, e junto dela o ponto de retorno de alguma função chamadora.
 * Se um programa trava em um determinado momento, voltando após um período previsível de tempo (30 segundos), automaticamente sabemos que existe algum evento/mutex usado de forma errada que, dadas as circunstâncias, apresentou uma espera longa demais.
 * Se uma versão nova capota em um procedimento em que a versão antiga nunca capotou, podemos divagar rapidamente quais as características da nova versão que fizeram com que isso acontecesse, ainda sem olhar para o código.

Dessa forma é possível criar teorias a partir da análise mental do que o programa normal deveria estar fazendo, mas não está. É esse tipo de análise que é feita no seriado.

Porém, o lado bom: podemos testar todas nossas hipóteses. Na vida real! Se, por enquanto, matar pacientes para depois ressuscitá-los é coisa de ficção, matar sistemas e reiniciá-los não é. E, dependendo do problema, podemos sempre replicá-lo em "outro paciente".

Talvez isso faça a profissão tão realizadora e viciante: para resolver um problema, geralmente temos todas as cartas na mão, e se não temos, fazemos ter. Afinal de contas, somos nós que iremos ressuscitar o sistema perdido.


<< >>