Hakuoo Posted January 20, 2021 at 05:33 PM Share Posted January 20, 2021 at 05:33 PM Olá Pessoal, tudo certo ? Bom eu criei esse desafio para ajuda nos estudos do pessoal. As regras são as seguintes: Seu Unpack tem que ser funcional(OEP válido) Seu Algoritmo revertido tem que bater com o esperado ele pode ser similar(não igual), porem funcional Aqui tem uma simples explicação de como funciona esse tipo de desafio: UnpackMe - é um CrackMe Diferente, ou seja você vai receber um arquivo que está compactado ou protegido, pode ser um protector/packer comercial ou privado ou até mesmo um obfuscador e seu objetivo é limpar o binário de forma que seja possível recuperar o algoritmo de maneira clara e que ainda continue funcional, de maneira simplificada é recuperar ao estado antes do packer/protector, para isso basta atingir o "OEP". ReverseMe - é um CrackMe mais gourmet, seu objetivo não é só reescrever o algoritmo de validação implementado pelo desenvolvedor mas também reconstruir todo o funcionamento original do software ou então explicar como ele funciona de maneira clara para as outras pessoas. Sobre nosso desafio: "Protegido" usando o NsPack 3 MinGW, original C IA-32 Como mencionado seu objetivo é fazer o Unpack Corrigir a IAT e encontrar o OEP, e reverter ou explicar como funciona o algoritmo original, isso não é uma competição, então tenha calma e compartilhe seu progresso vamos treinar juntos, caso necessário postarei a solução original como resposta. Não esqueçam de descrever corretamente os passos feitos. Boa Sorte e Saudações. UnpMe+RevMe_MENTEBINARIA.zip 2 Quote Link to comment Share on other sites More sharing options...
Supporter - Nibble anderson_leite Posted January 24, 2021 at 05:31 PM Supporter - Nibble Share Posted January 24, 2021 at 05:31 PM Spoiler Unpack: As instruções no inicio do entrypoint do nspack são: pushfd -> empilha a eflag (zero flag, carry flag e etc) pushad -> empilha todos os registradores call $ Onde $ é um calculado para a instrução seguinte, Ocorre algumas instruções, o importante é ver que a VirtualAlloc é chamada para salvar o conteúdo que será usado para o unpack: Indo mais a fundo, é possivel acompanhar todo o unpack, porém o importante é: Quando é feito as primeiras instruções, é enviado como "parâmetro" para a rotina de descompressão onde deve ser o inicio do código descomprimido, o valor em si é o primeiro da pilha e pode ser achado no valor do ESP (0x4E6201) antes da call para a descompressão. Por isso, coloca-se um hardware breakpoint de acesso que será chamado ao final do unpack. É possível também acompanhar toda a descompressão, basta acompanhar a saida de EAX do VirtualAlloc, lá irá o endereço da nova memória alocada, porém ao final da execução o conteúdo é movido para o valor acima, não chamarei de OEP por que ainda não é o OEP mas pode ser interpretado como um "segundo" entrypoint. , Pode-se continuar a execução, Após o breakpoint chegar iremos estar de cara com o famoso jump, esse jump está presente em quase todos os packers e ele irá finalizar o trabalho do unpack: Esse jump irá nos levar a uma outra função que fará o papel de reconstruir a IAT do binário que foi desconstruida no pack: Note que após essa call, tem mais um jump, esse jump vai para o nosso OEP, localizado em 0x401180, assim podemos usar o Scylla para fazer o dump do nosso PE em memória e realizar o fix da IAT: Desse jeito, fizemos o manual unpack e reconstruimos a IAT ? Visão do DIE: Algoritmo: O programa pede uma chave de acesso numérica e então checa com uma chave interna após realizar um calculo sobre a chave: Onde a chave é movida para a pilha em loc_401636: O algoritmo realizar um xor com 0x32, realiza um right shift e multiplica o resultado por 100: key = 0x11D29450 if (100 * (( val ^ 0x32) >> 5)) == key: print("You win with {}".format(val)) else: print("Not yet!") Para descobrir o input valido, é possível realizar a operação inversa e com o valor aproximado da chave realizar um brute force, que seria: key_dec = 299013200 almost_the_key = int(((key_dec ^ 0x32) << 5) / 100) # 95684229 for i in range(almost_the_key, key_dec): if (100 * ((i ^ 0x32) >> 5)) == key_dec: print("Correct input -> {}".format(i)) break Correct input -> 95684256 É isso ai, acredito que deva ter uma maneira mais rápida e direta de resolver em vez de brute force, mas talvez eu tenha tido alguns problemas com a precisão ? 3 Quote Link to comment Share on other sites More sharing options...
Hakuoo Posted January 24, 2021 at 07:45 PM Author Share Posted January 24, 2021 at 07:45 PM (edited) Olá Anderson, Como combinado vamos a solução, eu separei por blocos e ocultei para que não prejudique quem ainda for fazer para estudos.Fazendo o Unpack e Reconstruindo: Spoiler Visualizando o original com um Detector: Vamos visualizar a IAT(Import Address Table): Não é possível encontrar nada muito relevante, além de funções de alocação de memória, porem isso pode ser mais útil do que se imagina. Vamos carregar o Binário no IDA: Após ignorarmos os avisos do IDA sobre binário compactado, podemos visualizar a rotina de descompressão na memória. Vamos nos atentar ao EntryPoint do NsPack: pushfd ; Manda as Eflags para stack pushad ; Manda os registradores de uso geral para stack call $+5 ; Call para próxima instrução, ou inicio da rotina de descompressão Uma maneira fácil de fazer o Unpack é filtrar por instruções que "Desempilham Eflags e Registradores", se procurarmos na rotina é possível encontrar uma única referência ao final da rotina: popad ; Faz um pop em todos os registradores de uso geral popfd ; Faz um pop em todos os registradores EFLAGS jmp near ptr _mainCRTStartup ; Salto para o entrypoint OEP Ok, vamos avançar ao debugger: Estamos parados justamente no EntryPoint do NSPack. Temos informações de onde suspeitamos ser o final da sua rotina, agora voltando ao IDA, vamos copiar esse endereço para procurar no debugger: Vamos obter um endereço: 0x4E6471 Agora apertando os Atalhos: CTRL+G colamos o endereço e procuramos Vamos nos deparar justamente ao final da rotina: Faremos duas coisas, definir um breakpoint normal com F2, e executar com F9 Vamos nos deparar com a seguinte situação: 1º Quando a execução parar no Breakpoint vamos avançar com F8 2º Quando chegarmos ao Salto, vamos apertar F7 e entrar Agora nos depararemos com a seguinte situação: Vamos abrir o Scylla: Agora basta reconstruir a IAT e Dumpar: Pronto o software está funcionando perfeitamente e não possui mais a proteção, Somente resquícios(Sim, o unpack é para analisar, mas ainda vamos nos deparar com funções usadas para esconder Strings e coisas do tipo, será explicado mais em breve). Curiosidade: O NSPack assim como o UPX é muito utilizado pelos autores de Malware, é bom saber fazer o unpack desses packers mais comuns e a maneira mais divertida é resolvendo UnpackMes e CrackMes ? Entendendo as Traps e Analisando o algoritmo Spoiler A primeira coisa que veremos quando abrirmos no IDA será: Assembly mov dword ptr [esp+4], offset Locale ; "Portuguese" ; <- IDIOMA mov dword ptr [esp], 0 ; Category ; <- LC_ALL call setlocale ;<- SetLocale(LC_ALL, "Portuguese") Equivalente em C SetLocale(LC_ALL, "Portuguese") Assembly mov eax, ds:IsDebuggerPresent ; call eax ; IsDebuggerPresent ; test eax, eax ; setnz al test al, al jz short loc_40159A ROTINA 2 - caso DBG mov dword ptr [esp], 1 ; uExitCode mov eax, ds:ExitProcess call eax ; ExitProcess Equivalente em C if(IsDebuggerPresent()){ ExitProcess(1); } Com essas informações identificamos que temos diversas Traps Simples de IsDebuggerPresent, ao decorrer do programa. Vamos entender a rotina que escondia as strings: Assembly 1 mov dword ptr [esp], offset asc_48C00C ; "***************************************"... call ESCONDER_STRING mov eax, ds:IsDebuggerPresent call eax ; IsDebuggerPresent Assembly 2 mov dword ptr [esp], offset aMentebinariaUn ; " MENTEBINARIA | UNPACKME & REVERSE ME"... call ESCONDER_STRING mov eax, ds:IsDebuggerPresent call eax ; IsDebuggerPresent Equivalente em C ESCONDER_STRING(const char* STR); Descendo mais um pouco encontraremos algo muito interessante e que nos ajudará a obter a senha correta: loc_401636: mov [ebp+var_C], 11D29450h ; Valor hexadecimal sendo armazenado em um local da memória mov eax, ds:IsDebuggerPresent call eax ; IsDebuggerPresent test eax, eax setnz al test al, al jz short loc_40165B vamos guardar esse valor e construir um pseudocódigo: 11D29450h -> decimal -> 299013200 e vamos nomear essa posição com algo mais legal: mov [x], 11D29450h ; 299013200 vamos procurar abaixo para encontrar algo mais interessante. Bingo! Encontrei: xor eax, 32h ; Valor de EAX = Lido pelo usuário XOR 0x32 -> Decimal -> 50 sar eax, 5 ; Deslocamento >> 5 imul eax, 64h ; 'd' ; multiplicado por 64h -> Decimal -> 100 mov [ebp+var_10], eax ; Armazena o valor do registrador em uma posição da memória e vamos nos divertir no nosso amado Pseudo C ((valor_lido ^ 0x50) >> 5)*100 Assim como anteriormente vamos dar um nome para esse local de memória: mov [y], valor_lido Avançando mais um pouquinho encontraremos a seguinte rotina: mov eax, [y] cmp eax, [x] ; Subtrai de EAX o valor de X e seta zero flag jnz short loc_40173C ; salto para o final e exibe o erro Arrumando para deixar entendivel para quem está iniciando: mov c, [y] cmp c, [x] ; Subtrai de c o valor de X e seta zero flag jnz short loc_40173C ; salto para o final e exibe o erro pseudoC if(y == x){ EBA! }else{ HÁ, ADEUS! } Acredito que já ficou bem grande e quero organizar por tópicos elegantes, então vamos ao próximo que é reconstruir o algoritmo e encontrar sua chave. Reconstruindo o algoritmo e encontrando a chave Spoiler Com as informações obtidas na nossa analise temos o seguinte pseudocódigo C int main(){ int x = 299013200; int y = 0; printf("Digite sua chave de acesso: \n"); scanf("%d", &y); y= ((y^50)>>5)*100; if(y == x){ // EBA! }else{ //Ops.. } } Pessoa: Então vamos digitar logo o valor de X no console e ser feliz Eu: NÃO ! Vamos entender porque: X é o valor esperado, então temos que ter uma chave para calcular seu valor final Pessoa: Há, eu tiro de letra, isso está muito fácil, basta fazer o processo inverso. Eu: Blz Vamos lá: - O Processo inverso de uma multiplicação é a divisão - O Processo inverso de um SHL(>>) é o SHR(<<) - O XOR não tem processo diferença, basta fazer um XOR Ok, vamos obter a seguinte solução: y= ((y^50)<<5)/100; <----- 299013200 / 100 << 5 ^ 50 = 95684274 Quando usamos essa chave: "Parabéns !! agora reescreva esse algoritmo, faça o unpack do packer, de maneira funcional e poste sua solução." Sucesso conseguimos a chave de forma reversa e o desafio foi resolvido! Dúvidas frequentes: Qual era a chave original que você usou: 95684262 Pessoa: Minha chave é diferente, mas eu cheguei no objetivo, estou errado ? Eu: De maneira alguma, como expliquei no desafio todas as formas de pensar são corretas se você chegou no objetivo, compartilhe porque você conseguiu atingir por mérito próprio, e parabéns ? Fonte original: Spoiler #include <iostream> #include <locale.h> #include <stdio.h> #include <windows.h> /* chave 95684262 */ int main(int argc, char** argv) { setlocale(LC_ALL, "Portuguese"); if(IsDebuggerPresent()){ ExitProcess(1); } printf("************************************************\n"); if(IsDebuggerPresent()){ ExitProcess(1); } printf(" MENTEBINARIA | UNPACKME & REVERSE ME \n"); if(IsDebuggerPresent()){ ExitProcess(1); } printf("************************************************\n"); if(IsDebuggerPresent()){ ExitProcess(1); } if(IsDebuggerPresent()){ ExitProcess(1); } int senha = 299013200; if(IsDebuggerPresent()){ ExitProcess(1); } int leia = 0; if(IsDebuggerPresent()){ ExitProcess(1); } printf("Digite sua chave de acesso: \n"); if(IsDebuggerPresent()){ ExitProcess(1); } scanf("%d", &leia); if(IsDebuggerPresent()){ ExitProcess(1); } leia = ((leia^50)>>5)*100; if(IsDebuggerPresent()){ ExitProcess(1); } if(leia == senha){ if(IsDebuggerPresent()){ ExitProcess(1); } printf("Parabéns !!\n agora reescreva esse algoritimo, faça o unpack do packer, de maneira funcional e poste sua solução.\n"); }else{ if(IsDebuggerPresent()){ ExitProcess(1); } printf("Não foi dessa vez, vai pedir para sair ? VAMOS VOCÊ CONSEGUE !\n"); } return 0; } Espero que tenham gostado do desafio, e até o próximo e quem resolveu, parabéns você pensou fora da caixa e isso que é realmente importante ! Saudações, Edited January 24, 2021 at 08:14 PM by Hakuoo 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.