Como funciona o PsExec
Caloni, 2008-10-29 computer blogSemana passada precisei reproduzir o comportamento da ferramenta PsExec em um projeto, o que me fez sentir alguma nostalgia dos tempos em que eu fazia engenharia reversa todo dia. Este breve relato (espero) reproduz os passos que segui para descobrir o que esse programa tão útil quanto perigoso faz.
Sabe-se que o PsExec consegue executar um programa remotamente, ou seja, de uma máquina para outra, outra essa que chamaremos de máquina-alvo. O programa a ser executado geralmente deve estar disponível na própria máquina-alvo (condição ideal). Além da simples execução, para aplicativos console ele permite ainda a interação como se estivéssemos executando o programa remoto em nossa própria máquina local. Ele consegue isso redirecionando sua entrada e saída, o que o torna, como nos descreve o próprio autor, um "_telnet light_":
psexec \\maquina-alvo [-u admin-na-maquina-alvo] cmd.exe
Além desse comportamento já muito útil ainda existe um bônus que se trata de especificar um executável local que será copiado remotamente para a máquina-alvo e executado. Esse é o comportamento que espero imitar:
psexec \\maquina-alvo [-c c:\tests\myprogram.exe] [-u admin-na-maquina-alvo] PsExec v1.72 - Execute processes remotely Copyright (C) 2001-2006 Mark Russinovich Sysinternals - www.sysinternals.com Microsoft Windows XP [versão 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp.
No teste acima o myprogram.exe é somente o cmd.exe renomeado. Um teste básico =)
Já fizemos isso logo acima. Se trata apenas de observar o programa funcionando. Ao mesmo tempo em que entendemos seu _modus operandi_ coletamos pistas sobre suas entranhas. No caso do PsExec, que faz coisas além-mar, como redirecionar os _pipes_ de entrada/saída de um programa console, iremos checar a existência de algum serviço novo na máquina-alvo e arquivos novos que foram copiados, além de opcionalmente dar uma olhada no registro. Ferramentas da própria SysInternals como Process Explorer e Process Monitor também são úteis nessa análise inicial.
Como podemos ver, um serviço com o nome de PsExec foi criado na máquina-alvo. Se procurarmos saber o caminho do arquivo que corresponde a esse serviço, tanto pelo Process Explorer ou o Service Manager, descobriremos que se trata de um arquivo no diretório do windows chamado psexecsvc.exe.
Se o arquivo existe nessa pasta, então é óbvio que alguém o copiou. Resta saber como.
Nessa segunda fase, podemos refazer o comportamento esperado inúmeras vezes, coletando dados e pensando a partir dos dados obtidos. Para esse caso, como quase todos que analiso, vou usar o nosso amigo [WinDbg]. Para isso, como tenho sempre minhas ferramentas disponíveis no ambiente onde trabalho, basta digitar "windbg" antes do comando anterior e dar uma olhada em algumas APIs-chave, como a criação/abertura de arquivos e a criação de serviços. Note que é importante fazer isso em um escopo limitado para não perdermos horas de análise. Descobrir coisas como, por exemplo, que as ações do PsExec só começam a ser executadas após a digitação da senha do usuário, pode ajudar, pois daí só começo minha análise a partir desse ponto.
windbg psexec \\maquina-alvo -u admin cmd.exe PsExec v1.72 - Execute processes remotely Copyright (C) 2001-2006 Mark Russinovich Sysinternals - www.sysinternals.com Password: ntdll!DbgBreakPoint: 7c90120e cc int 3 0:001> bp kernel32!CreateFileW 0:001> bp advapi32!CreateServiceW 0:001> g Connecting to maquina-alvo... Breakpoint 0 hit eax=40150000 ebx=0011ee90 ecx=00000000 edx=00005078 esi=7c80932e edi=0011eeb0 eip=7c8107f0 esp=0011ee70 ebp=0011eed0 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 kernel32!CreateFileW: 7c8107f0 8bff mov edi,edi 0:000> du poi(@esp+4) 0011ee90 "\\.\PIPE\wkssvc" 0:000> k 50 // pilha muito grande! ChildEBP RetAddr 0011ee6c 77dc4b92 kernel32!CreateFileW 0011eed0 77dc4369 RPCRT4!NMP_Open+0x17a 0011ef1c 77dc48d3 RPCRT4!OSF_CCONNECTION::TransOpen+0x5e 0011ef70 77dc4a5d RPCRT4!OSF_CCONNECTION::OpenConnectionAndBind+0xbc 0011efb4 77dc49ac RPCRT4!OSF_CCALL::BindToServer+0x104 0011f018 77dbfdbc RPCRT4!OSF_BINDING_HANDLE::AllocateCCall+0x2b6 0011f048 77db8a01 RPCRT4!OSF_BINDING_HANDLE::NegotiateTransferSyntax+0x28 0011f060 77db8a38 RPCRT4!I_RpcGetBufferWithObject+0x5b 0011f070 77db906d RPCRT4!I_RpcGetBuffer+0xf 0011f080 77e3460b RPCRT4!NdrGetBuffer+0x28 0011f460 5bcb9d7c RPCRT4!NdrClientCall2+0x195 0011f474 5bcb9d1e NETAPI32!NetrWkstaGetInfo+0x1b 0011f4bc 71c78cc2 NETAPI32!NetWkstaGetInfo+0x38 0011f4e0 71c78a75 NETUI1!WKSTA_10::I_GetInfo+0x21 0011f4e8 71be1843 NETUI1!NEW_LM_OBJ::GetInfo+0x1c 0011fadc 71be31aa ntlanman!CheckLMService+0x3d 00120464 71be3058 ntlanman!AddConnection3Help+0x3e 0012118c 71be2e29 ntlanman!AddConnectionWorker+0x37c 001211b0 71ae461a ntlanman!NPAddConnection3+0x1f 001211d8 71ae45ac MPR!CUseConnection::TestProviderWorker+0x47 00121aa8 71ae2445 MPR!CUseConnection::TestProvider+0x62 00121b08 71ae431d MPR!CRoutedOperation::GetResult+0x10f 00121da4 71ae2348 MPR!CUseConnection::GetResult+0x180 00121ddc 71ae22fd MPR!CMprOperation::Perform+0x4d 00121de8 71ae4505 MPR!CRoutedOperation::Perform+0x22 00121e98 71ae50f0 MPR!WNetUseConnectionW+0x58 *** WARNING: Unable to verify checksum for image00400000 *** ERROR: Module load completed but symbols could not be loaded for image00400000 00121ec0 00401796 MPR!WNetAddConnection2W+0x1c WARNING: Stack unwind information not available. Following frames may be wrong. 001225d0 0032002e image00400000+0x1796 001225d4 0030002e 0x32002e 001225d8 0034002e 0x30002e 001225dc 00000000 0x34002e
Uma rápida busca no Google nos informa que o _pipe _querendo ser aberto pertence à lista de _pipes _que estão sempre disponíveis nas máquinas para responder às requisições do sistema. São importantes para a comunicação entre processos (IPC, Inter Process Communication). No entanto, quem usa esse pipe é o sistema, e ele foi chamado, como pudemos ver, pela função WNetAddConnection2W.
Se analisarmos mais a fundo a pilha de chamadas conseguiremos dar um olhada nos parâmetros passados. Para isso existe a opção de mostrar os argumentos passados para as funções ao exibir a pilha:
0:000> kv 50 ChildEBP RetAddr Args to Child 0011ee6c 77dc4b92 0011ee90 c0000000 00000003 kernel32!CreateFileW (FPO: 0011eed0 77dc4369 00147140 001463f8 005b4b46 RPCRT4!NMP_Open+0x17a (FPO: ... 00121e98 71ae50f0 00000000 00121ee4 004182a0 MPR!WNetUseConnectionW+0x58 (FPO: [Non-Fpo]) *** WARNING: Unable to verify checksum for image00400000 *** ERROR: Module load completed but symbols could not be loaded for image00400000 00121ec0 00401796 00121ee4 004182a0 004139e0 MPR!WNetAddConnection2W+0x1c (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 001225d0 0032002e 0030002e 0034002e 00000030 image00400000+0x1796 001225d4 0030002e 0034002e 00000030 00000000 0x32002e 001225d8 0034002e 00000030 00000000 00000000 0x30002e 001225dc 00000000 00000000 00000000 00000000 0x34002e 0:000> db 00121ee4 00121ee4 00 00 00 00 00 00 00 00-00 00 00 00 03 00 00 00 ................ 00121ef4 e2 1e 12 00 04 1f 12 00-00 00 00 00 00 00 00 00 ................ 00121f04 5c 00 5c 00 31 00 30 00-2e 00 32 00 2e 00 30 00 \.\.1.0...2...0. 00121f14 2e 00 34 00 30 00 5c 00-49 00 50 00 43 00 24 00 ..4.0.\.I.P.C.$. 00121f24 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00121f34 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00121f44 cc da 90 7c c8 2d 91 7c-ec 07 00 00 88 1f 12 00 ...|.-.|........ 00121f54 88 1f 12 00 00 00 00 00-f2 20 12 00 e0 24 12 00 ......... ...$..
Ele tenta abrir uma conexão com a máquina-alvo em seu compartilhamento de IPC, que como já vimos serve para comunicação entre processos, até entre máquinas distintas. Dessa forma, descobrimos um dos pontos importantes no funcionamento do PsExec: ele usa o nome e senha fornecidos para abrir uma comunicação remota no compartilhamento IPC$.
Depois sugem várias paradas ao CreateFile, de maneira que a melhor forma de acompanhar isso é colocando um "_dumpezinho_" de memória na sua parada:
0:000> bp kernel32!CreateFileW "du poi(@esp+4)" breakpoint 0 redefined 0:000> g 0011ee90 "\\.\PIPE\wkssvc" eax=40150000 ebx=0011ee90 ecx=00000000 edx=00005d6e esi=7c80932e edi=0011eeb0 eip=7c8107f0 esp=0011ee70 ebp=0011eed0 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 kernel32!CreateFileW: 7c8107f0 8bff mov edi,edi 0:000> g 0011f474 "\\.\PIPE\wkssvc" eax=40160000 ebx=0011f474 ecx=00000000 edx=00005d77 esi=7c80932e edi=0011f494 eip=7c8107f0 esp=0011f454 ebp=0011f4b4 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 kernel32!CreateFileW: 7c8107f0 8bff mov edi,edi 0:000> g 0011f210 "\\.\PIPE\wkssvc" eax=40160000 ebx=0011f210 ecx=00000000 edx=00005d7a esi=7c80932e edi=0011f230 eip=7c8107f0 esp=0011f1f0 ebp=0011f250 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 kernel32!CreateFileW: 7c8107f0 8bff mov edi,edi 0:000> g 0012213c "\\10.2.0.40\ADMIN$\PSEXESVC.EXE" eax=00122090 ebx=00000003 ecx=00000000 edx=009300d0 esi=00000080 edi=ffffffff eip=7c8107f0 esp=00122064 ebp=001220ac iopl=0 nv up ei pl nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213 kernel32!CreateFileW: 7c8107f0 8bff mov edi,edi
Muito bem! Chegamos a mais um ponto importante de nossa análise: o psexecsvc.exe é copiado através do compartilhamento ADMIN$ remotamente (diretório c:\windows). Esse compartilhamento se torna acessível, uma vez que uma conexão autenticada já foi aberta. Se listarmos as conexões existentes, veremos o compartilhamento IPC$ aberto:
>net use Novas conexões serão lembradas. Status Local Remoto Rede ------------------------------------------------------------------------------- OK \\10.2.0.40\IPC$ Rede Microsoft Windows Comando concluído com êxito. >
Também podemos notar que, enquanto estamos parados depurando o processo psexec.exe, temos acesso ao compartilhamento admin$:
A análise desses fatos demonstra como é importante fazer as coisas, pelo menos na fase "iniciante", bem lentamente, e entender a mudança de estado durante o processo. Nem sempre isso é possível, é verdade, ainda mais quando estamos falando de análise de kernel. Mas, quando as condições permitem, vale a pena pensar antes de fazer.
Voltando à análise: temos direitos remotos nessa máquina. Dessa forma, fica fácil criar um serviço remotamente, que é o que faz o nosso amigo PsExec:
0:000> g Breakpoint 1 hit eax=00410870 ebx=00410870 ecx=00147a68 edx=00410844 esi=00410844 edi=00147a68 eip=77fb7381 esp=001224e4 ebp=001228dc iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ADVAPI32!CreateServiceW: 77fb7381 6a20 push 20h 0:000> du poi(@esp+8) 00410870 "PSEXESVC" 0:000> du poi(@esp+8*4) 001228dc "%SystemRoot%\PSEXESVC.EXE"
Pronto. Isso era tudo que precisava para conseguir reproduzir seu comportamento. Agora posso fazer isso programando ou até manualmente:
C:\Tests>net use \\10.2.0.40\ipc$ /user:admin A senha ou o nome de usuário é inválido para \\10.2.0.40\ipc$. Digite a senha para que 'admin' se conecte a '10.2.0.40': Comando concluído com êxito. C:\Tests>net use Novas conexões serão lembradas. Status Local Remoto Rede ------------------------------------------------------------------------------- OK \\10.2.0.40\ipc$ Rede Microsoft Windows Comando concluído com êxito. C:\Tests>copy myprogram.exe \\10.2.0.40\admin$ 1 arquivo(s) copiado(s). C:\Tests>sc \\10.2.0.40 create MyProgram binPath= %systemroot%\myprogram.exe [SC] CreateService SUCCESS
O resto do comportamento, como o redirecionamento de entrada e saída e execução do processo na conta especificada, embora muito interessante, não me interessa de imediato. Quem sabe interesse a você, e não tenhamos uma continuação dessa análise em um outro blogue de "desmontagem" por aí =)