Ir para conteúdo

Unpack Me Custom Packer + Reverse Me | Nível 3


Hakuoo

Posts Recomendados

Olá, confrades, tudo certo?

Espero que todo mundo esteja bem!

Eu criei mais um desafio divertido para nossa comunidade, dessa vez com algumas características únicas.


Bom antes de prosseguir tenho algumas advertências para quem for tentar:

  • Use uma VM(Não use seu computador físico de maneira alguma)!
  • Você pode se assustar com algumas técnicas empregadas(mas garanto não tem nada de malicioso).

Como regras do nosso desafio:

  1. Se o unpack for feito(Criar um passo a passo de como você fez).
  2. Se a chave for encontrada(Postar como você chegou a essa conclusão)
  3. Você é livre para "bypassar" minhas técnicas empregadas, e se fizer, também faça um tutorial de como conseguiu.

Não está familiarizado com os termos de challenges? Clique aqui para ler a explicação.


Sobre o nosso desafio:

  • "Protegido" com Rika's Packer 1.0(Hakuoo Packer)
  • Visual C++ 2019
  • IA-32
     

Dica:

  • Não tente fazer bruteforce, você pode conseguir se prestar bem atenção nas instruções.
  • Senha para o arquivo: desafiomb
  • Uso na linha de comando: CrackmeV3_challenge.exe senha

Como mencionado o objetivo é para estudos aperfeiçoamento e aprendizado, todo mundo vai sair ganhando, o desafio é dado como ganho para a melhor resposta ou para quem fazer um unpack funcional, encontrar o OEP, corrigir a IAT, e encontrar a senha.


Boa Sorte, diversão e Saudações !

CrackmeV3_challenge.zip

Link para o comentário
Compartilhar em outros sites

  • Apoiador Nibble

Excelente desafio, parabéns pelo packer! por mais simples que seja rende um bom aprendizado.

Segue minha resolução abaixo, se as imagens ficarem pequenas pode clicar que expande.

 

Spoiler

1 - Overview

Sabendo que estamos trabalhando diretamente com um packer,  podemos primeiro dar uma olhadas nas seções na esperança de já encontrar aonde vai estar o código "packeado" e unpacked:

overview seções

A visão acima, no Cutter , revela algumas informações interessantes:

  1. 2 seções com nomes incomums: .wohami e .hakuoo
  2.  O tamanho em disco da .wohami sendo 0 e seu tamanho virtual sendo 0x2000
  3. Entropia presente na seção .hakuoo sendo bem elevado, o que indica algum tipo de compressão ou criptografia
  4. Permissões de escrita, leitura e execução em ambas

Todos os fatos indicam que de fato é um packer, e que provavlmente o código unpacked será copiado para a seção .wohami, também podemos inferir que modificações serão feitas na seção .hakuoo,  por conta da permissão de escrita.

2 - Rika packer - Manual unpack e reconstrução da IAT

A maioria dos packers  seguem a seguinte ordem de execução:

  1. Carregamento de informações basicas como: ImageBase, OEP, IAT address
  2. Descompressão/decriptografia do código packeado
  3. Reconstrução da IAT
  4. jump para OEP

Com o Rika não é diferente, logo no inicio já vemos uma chamada a função que vai realizar o unpack  que recebe um argumento que aponta para o inicio da seção .hakuoo, onde será extraido os dados necessários para iniciar o unpack:

entrypoint

 

Note também, que logo após a call para o unpack, é feio um jump para o valor retornado, dword ptr [ebp-4] que recebe o valor de eax (retorno da função), este é o nosso OEP, mas irei explicar o motivo disso porém essa informação de antemão (pela estrutura) é importante para que seja possível reconstruir, mesmo que parcialmente, a header desse packer.

 

Primeiro acessos a header

 

No inicio da função, podemos notar que existe acessos ao endereço que foi passado como argumento (eu renomeiei para rikaHdr), note também que existem varios outros acessos a offsets especificos como ecx+8, ecx+2C, ecx+18 e etc. Isso nada mais é que o acesso a um elemento de uma struct,  já que structs são apenas dados alinhados em memória e não existem de fato quando o código é compilado, para facilitar podemos reconstruir as structs com o IDA, porém antes de realizar isso, abrindo o desafio no x32dbg e colocando um breakpoint nos endereços que realizam acesso ao offset 0x18 da header, 0x407BA4F, é possível notar que está sendo carregado os endereços das funções importadas:

importsLoading.thumb.png.b2094d366714df92c1a5a8896d3ec1be.png

 

Então podemos deduzir que no offset 18 é guardado o endereço da LoadLibraryA, porém nos próximos valores também são guardados os endereços das funções VirtualAlloc, VirtualFree e GetProcAddress, em sequência na memória o que é possível deduzir que estão em um vetor de ponteiros.

Bom, o processo inteiro de reconstruir structs é bem complicado e requer bastante atenção então para não perder muito tempo, irei colocar a struct na qual eu reconstrui e que foi usado para obtenção do OEP e da rotina que realiza a reconstrução da IAT:

 

struct rikaHdr {
    DWORD* imageBase;
    DWORD  OEPRVA;
    DWORD  numChunks; // Primeiro imaginei que fosse o numero de imports, porém casa melhor o número de "partes" que foram comprimidas
    DWORD  IATRVA;
    DWORD  packedSize;
    DWORD  unpackedSize;
    DWORD  unknown_1;
    DWORD** importAddrs;
    DWORD  unknown_2;
    DWORD  dstSecRVA; // RVA para a seção wohami
};

Após construir essa struct no IDA, fruto de uma analise mais minuciosa e muito passível de erro, o código tanto do  decompiler quanto do disassembler fica mais simples de se ler:

Struct da header parcialmente reconstruida

Claro, existe outras structs aqui como visto na variavel packedHdr que ocorre logo após a rikaHdr, porém não tomei muito esforço para reconstruir visto que só com a rikaHdr é possível encontrar o OEP e reconstruir a IAT.

 

A estrutura do unpack adotado no rika tem os seguintes passos:

  • É criado um primeiro buffer que irá guardar valores parciais da descompressão
  • São criado então mais 2 buffers que irão ser usados de maneiras complementares ao primeiro para descompressão total
  •  É então criado um buffer para salvar todo o código unpacked
  •  Logo em seguido é copiado byte a byte para seção .wohami
  • A IAT é então reconstruida
  • Retorna então a soma da imageBase com o RVA do OEP

Os passos podem ser vistos na seguinte visão do decompiler, para fácil entendimento:

unpackedData.thumb.png.8648448a542b977d9065cac7ae1556b9.png

Então é copiado byte a byte até a seção que irá salvar o código descomprimido, o mesmo pode ser visto também no x32dbg:

 

whoamicopy.thumb.png.17808ef53466609b4c1e3cbdc18e5e35.png

 

É possível também confirmar que este endereço 0x401000 é o endereço que inicia a seção olhando no memory map:

image.thumb.png.fd596e446eaea3bb762704193824a808.png

Após isto, a IAT é reconstruida usando o RVA da iat, salvo na rikaHdr para encontrar cada endereço, carregar a biblioteca necessario e então preencher a com o endereço de cada função importada:

IATRebuild.thumb.png.0758d74d0bd0d7e8859dd94d3e4a8080.png

Após esse processo ser terminado, a função então termina retornando a soma da imageBase com  o RVA do OEP, que também são elementos da rikaHdr

 

returnOEP.thumb.png.16261a47082eca13e170f1cc70333386.png

 

Agora sabendo disso tudo, podemos proceder para o nosso própio manual unpack dentro do x32dbg, podemos deixar toda a rótina de descompressão ocorrer até o jump para o OEP e então realiza o dump do PE e reconstruir a IAT usando o Scylla.

Também visto na imagem acima, é possivel fazer o calculo manual do valor do OEPRVA e da imageBase para encontrar o OEP, que fica em 0x4019FF, então basta por um breakpoint nesse endereço e esperar chegar nele ?

unpackAndIATRebuild.thumb.png.5a9f0a8ab1466c149df30a0bf3a72dbd.png

Excelente, manual unpack feito e IAT reconstruida, agora sim podemos passar para o reverse me!

3 - Reverse me

O desafio em si é simples, apenas encontre a chave/entrada correta que levará até a mensagem "Successo :)", porém o nosso amigo @Hakuooadicionou algumas técnicas de Anti-VM e Anti-Debugging para dificultar nossa vida, então vamos resolver tudo isso.

 

ba900cbf08abfa2e4be892275b425619.thumb.png.1594e1ed9c847807581f8ba91b436490.png

 

Logo no inicio já temos uma técnica de Anti-Debugging usando a instruçã  rdtsc, Read Time-Stamp Counter, que é usada para contar quantos ciclos da CPU ocorreu desde da ultima instrução, esses valores são checados varias vezes, o que em si atrapalha a gente quando estamos realizando o debugging por step-by-step, mas não tem muita influência caso a gente só passe por cima delas, então por um breakpoint logo após a segunda chamada é uma maneira de burlar isso, mas vale lembrar que em algumas situações o nosso "tick" pode ser maior que o checado mesmo dando um "continue" no debugger então é bom ficar atento a isso também.

Não só isso é usado, como também, caso o rdtsc falhe, será chamada uma função na qual renomeiei para cpuid_trick:

 

cpu_trick.thumb.png.405fd415a7322dc203c3a7eb27b3de85.png

 

Onde principalmente é chamado a instrução cpuid com o valor de EAX igual a 1, para obter as features implementadas na CPU e com isso caso o valor diferente de 0 é possível detectar se estamos dentro de um ambiente virtualizado, visto que este valor seria preenchido por 1 dentro de um hypervisor, nossa VM. Sendo somente uma função, é possível apenas fazer um patch inline para sempre retornar 0 em eax, existe uma maneira tbm de editar os arquivos de configuração da nossa VM para fazer com que sempre retorne 0, porém para manter a simplicidade um patch para debugging funciona bem, e quando for executar apenas definir para nossa VM retornar 0.

patcheax.thumb.png.a51e0abfcfa422f7dde73a2ea37d52ba.png

 

Ainda é feito uma chamada para a função IsDebuggerPresent, porém por default o x32dbg salva como falso o valor usado IsDebuggerPresent na PEB. Assim com o patch na função que checa as informações da CPU e pulando as calls para a instrução rdtsc, evitamos todas as detecções de que estamos realizando debugging ou rodamos dentro de uma VM!

Bom, eventualmente, nosso input é jogado para ser validado numa função em 0x401050, onde é checado se a nossa entrada multiplicada por 0xd93f5 é igual a 0x15682B45C343B

checagemInput.thumb.png.cc96022ea9bc2fdff8ac684a925b8f35.png

 

Vale notar dois pontos aqui:

  • Nosso registrador de 32 bits não consegue guardar um valor desse tamanho, então na instrução real é feito a checagem em 2 partes enquanto no decompiler temos uma visão high level
  • Para realizar a multiplição é usado a rotina allmul que é  preenchida pelo compilador para realizar calculos de maiores magnitudes

 

É facil então encontrar a entrada correta, basta dividir  0x15682B45C343B por 0xd93f5  que é igual a 0x1939b96f, ou 423213423.

No meu caso, no binario feito o unpack, eu precisei também fazer um patch na função de multiplicação e divisão para que fosse enviado os valores corretos, visto que após o unpack alguns valores na stack ficaram provavelmente desalinhados e o meu PE estava realizando a multiplicação pelo endereço da LoadLibraryA, porém no binario original isso não ocorre.

Logo após a primeira checagem, é aplicado novamente as técnicas de anti-vm e anti-debugging:

antischeck.thumb.png.e59f3ff1548326c62e6f1eb2228caea0.png

Mas, facilmente "bypassavel" pelo patch que foi feito e por passar direto na contagem de ciclos.

Nossa entrada novamente é checada pela divisão pelo valor 0x2E9 (745) e enviado para a função alldiv que tem o mesmo proposito da allmul, porém para realizar a divisão, é feito as mesmas checagens de anti-vm e anti-debugging, então é verificado se o resultado da divisão é igual a 568071:

alldiv.thumb.png.df440d18f28eb311949a51871446d4e5.png

O valor 423213423 casa certinho para essa divisão também, considerando apenas a parte inteira do resultado da divisão. Logo após isso nossa entrada é somada com o valor 4444 e então, retornada. O valor é validado algumas instruções seguintes para que seja possível dar continuidade a execução:

keyCheck.thumb.png.d89a35c5fab3f96b3fd0651996139310.png

 

Lembrando que nossa chave é dividida e somada com 4444 o que resulta em 0x8bc63, com isso é então chamada a ultima função que realiza o print na tela de "Sucesso :)"!

sucesso.thumb.png.8490d2ef7622c915046ca64e3dca9c33.png

 

É isso, se tiver qualquer equivoco ou erro pode avisar, e o quão perto eu cheguei na reconstrução da struct ? hahaha

 

 

Também, meu feedback sobre o packer abaixo:

 

Spoiler

Não sou nenhum especialista em desenvolvimento de packers, porém esse tem uma estrutura bem comum com os demais que tem apenas o papel de comprimir/diminuir o tamanho do executável, então minhas dicas para deixar mais difícil de fazer o manual unpack ou eng reversa seria:

  • Esconder o OEP junto com os dados comprimidos e não deixar ele na header, do jeito que tava ficava fácil sempre achar o OEP
  • Quebrar o unpack em partes, uma função para fazer apenas o unpack e seguido de um jump/call para reconstruir a IAT
  • O carregamento das funções pode também ser feito usando o PEB_LDR_DATA  dentro da PEB
  • Não copiar o valor para outra seção, tenta reescrever na seção do .hakuoo mesmo

Bom, contando que é apenas um packer e não um protector ou crypter, acho que essas mudanças são boas para deixar ele mais difícil de ser reconhecido e de ser feito o unpack de maneira comum.

 

 

 

Editado por anderson_leite
Feedback packer
  • Curtir 1
  • l33t 1
Link para o comentário
Compartilhar em outros sites

Participe da conversa

Você pode postar agora e se cadastrar mais tarde. Se você tem uma conta, faça o login para postar com sua conta.

Visitante
Responder

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emojis são permitidos.

×   Seu link foi automaticamente incorporado.   Mostrar como link

×   Seu conteúdo anterior foi restaurado.   Limpar o editor

×   Não é possível colar imagens diretamente. Carregar ou inserir imagens do URL.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...