Hakuoo Postado Janeiro 20, 2021 em 17:33 Compartilhar Postado Janeiro 20, 2021 em 17:33 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 Link para o comentário Compartilhar em outros sites More sharing options...
Apoiador Nibble anderson_leite Postado Janeiro 24, 2021 em 17:31 Apoiador Nibble Compartilhar Postado Janeiro 24, 2021 em 17:31 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 ? Link para o comentário Compartilhar em outros sites More sharing options...
Hakuoo Postado Janeiro 24, 2021 em 19:45 Autor Compartilhar Postado Janeiro 24, 2021 em 19:45 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, Link para o comentário Compartilhar em outros sites More sharing options...
Posts Recomendados
Arquivado
Este tópico foi arquivado e está fechado para novas respostas.