sergio-echigo/malware_analysis_cp3

GitHub: sergio-echigo/malware_analysis_cp3

Stars: 0 | Forks: 0

# Funcionamento do programa geral Após uma longa análise do programa usando a aplicação `xdbg32`, pôde-se observar que o programa requere dois ou mais argumentos, a depender do segundo argumento que se é passado (o primeiro é o caminho de execução da aplicação). Caso o segundo argumento seja "/start", é necessário dar entrada a mais um argumento, "A", para que a aplicação funcione corretamente -- criando um arquivo no diretório "C:\Users\Public\", chamado "cp03.txt", contendo a data e hora da criação do arquivo, seguidos por uma quebra de linha, e por fim o caractere "A". No entanto, se o argumento for "/read", o conteúdo desse arquivo é mostrado (desde que ele exista), fora a mensagem "CheckPoint#03 Done! Congratz! Show me the result!", que a princípio, está codificada em base 64 nas referências textuais do programa. É assim que a aplicação funciona. Ademais, há uma checagem de ambiente verificando se a aplicação está rodando em modo "Debug", uma função própria do Windows, chamada `IsDebuggerPresent`. # Fluxo de execução Tudo começa com a obtenção dos argumentos da linha de comando, conforme a seguinte imagem com as instruções comentadas demonstra: ![Obtenção dos argumentos da linha de comando](/imgs/1.png) Logo após isso, uma determinada função é chamada; esta que é responsável, conforme se verá mais para frente, por diversas validações e verificações correspondentes aos argumentos passados e ao ambiente de execução em que a aplicação roda. A função está apelidada de `EnvArgsCheck`, e entender grande parte das condicionais sendo executadas é imprescindível para o funcionamento correto do programa. Além do mais, ela toma com si três outros parâmetros que também já foram descritos no código: o número de argumentos passados no programa, o primeiro endereço na memória do *array* de argumentos, e por fim, uma *string* referente a um determinado diretório, obtido da chamada da função `get_initial_narrow_environment`. A imagem anteriormente apresentada também indica esses valores sendo adicionados a pilha do programa, assim como a chamada dessa função. ## Checagem de ambiente de execução Apesar do foco ser na validação do ambiente, é observado, de antemão, se o número de argumentos é menor que dois; se isso for verdade, o programa é, então, finalizado. Caso contrário, a próxima validação ocorre por meio da função `IsDebuggerPresent`, que conforme as especificações da MSDN, retorna um valor booleano referente à execução do programa por meio de um *debugger*. Por exemplo, como a análise foi inteiramente feita pelo `xdbg`, a função retorna *true*, o que significa atribuir o valor binário **um** para o registrador `eax`. Tanto que logo após a chamada dessa função, uma instrução que manipula o valor de `eax`, `test`, é executada usando apenas esse valor. A próxima instrução é um salto condicional, o `jne`, que ocorre desde que a `ZF` seja zero, o que será verdade desde que `eax` também seja zero -- conforme a instrução `test` --, que vem da chamada da função de validação de *debugger*. Ou seja, **se há um debugger, o programa é finalizado novamente.** Assim é mais bonito. Mais uma vez, como a análise foi inteiramente feita por meio de um **debugger**, precisou-se alterar, em tempo de execução, o valor da *flag* `ZF`. A seguinte imagem demonstra todas as instruções referentes a tais validações, e mais comentários estão presentes ao lado delas. ![Checagem do ambiente de execução](/imgs/2.png) ## Validação de argumentos do programa As próximas checagens serão, de fato, relacionadas aos argumentos passados durante a execução do programa. Entre instruções aqui e outras ali, chamadas e etc., pôde-se análisar algumas funções que, por mais que estejam presentes, não são validações ou pontos que seriam responsáveis pela finalização do programa, como a `StrangeFunc1`, que, no geral, copia o valor do registrador `ecx` para o `eax`. De qualquer forma, a próxima validação que ocorre é sobre a quantidade de argumentos (de novo!), sendo comparada com 2. Entretanto, há um detalhe importante nessa verificação e no salto condicional que ocorre logo após ela: se a quantidade de argumentos for de fato igual a dois -- i.e., a não ocorrência do salto `jle` --, o próximo "bloco" de código é responsável por obter a quantidade de caracteres do segundo argumento, e comparar com seis. Detalhe: a não ocorrência do salto `jle`, isto é, o número de argumentos ser igual ou menor -- apesar de que nunca será menor, considerando que essa verificação já fora feita anteriormente -- apenas pula essa contagem de caracteres, e logo a execução do programa se encontra num caminho único novamente, "a comparação com seis". Não, o programa não é finalizado caso a quantidade de caracteres do segundo argumento seja diferente de seis. Os dois próximos subtópicos serão responsáveis pela explicação da lógica do programa, que agora, se divide em dois, de acordo com a última condição testada. Além disso, as seguintes imagens demonstram todo esse fluxo de execução. ![Fluxo de execução do código](/imgs/3.png) ### A quantidade de caracteres do segundo argumento é igual seis Isso até pode ser verdade, mas não há garantia de que o valor de tal argumento será o mesmo que "/start", condição correspondente a próxima principal verificação. A princípio, o argumento todo é comparado, mas isso não impede, pelo menos não de acordo com as instruções em assembly, de ocorrer a verificação caractere a caractere, que ocorre de qualquer jeito. Além do mais, diversas operações de soma e subtração ocorrem, provavelmente para garantir que as comparações estejam ocorrendo de forma correta, de acordo com o tamanho correto do valor do segundo argumento. Voltando a comparação, é necessário que o valor seja igual a "/start"; caso contrário, um salto condicional eventualmente ocorrerá, chegando num ponto de comparação em que "a quantidade de caracteres do segundo argumento é diferente de seis". É como se não houvesse chamada de retorno, como se ao invés de um *else* para as próximas validações, não houvesse nenhum, e só as validações por sí só. Como nesse eventual ponto a verificação se torna a quantidade de caracteres e cinco, o resultado será falso, e finalmente, o programa será finalizado. Essa comparação será explicada melhor no próximo subtópico, e fora isso, a imagem abaixo demonstra a comparação com a constante "/start" e a comparação "caractere a caractere": ![Comparação caractere a caractere](/imgs/4.png) O código, felizmente, não termina aqui. Ainda há muito mais a ser explorado. Afinal, há mais condições a serem verificadas. A próxima condição fundamental é a quantidade de caracteres do **terceiro** argumento passado pela linha de comando com 1. Terceiro argumento? Isso aí. Afinal, lembre-se que para chegar até aqui, a quantidade de argumentos deve ser maior que dois, afinal, a quantidade de caracteres do segundo argumento é igual a seis, e essa última afirmação pôde ser feita logo após a ocorrência daquele salto condicional `jle`. Detalhe: o comprimento do terceiro argumento foi alocado localmente durante a chamada da instrução `call cp03.ap1ec30fc4ke.C52740`, que pode ser vista um pouco mais acima nas imagens. Agora, pode-se então declarar o seguinte: que a quantidade de caracteres do terceiro argumento tem que ser igual a um. Isso porque, por mais que novas comparações (basicamente, a mesma) sejam feitas (de acordo com o código em assembly), o programa iria, novamente, cair na verificação da quantidade de caracteres do segundo argumento com cinco, e então, ser finalizado novamente. Além do mais, o valor desse único caractere sendo passado como argumento deve ser "A" -- caso contrário, mais uma vez, o código saíra do *if* gigante feito até aqui. Todas as instruções referentes a essas validações estão sendo demonstradas no único *print* abaixo: ![Validação do terceiro caractere](/imgs/5.png) Isso por si só é o suficiente. Logo após toda essa validação, é possível observar o código rodando, e ele é bem específico: um arquivo é criado no diretório `C:\Users\Public\cp03.fiap`, com um conteúdo que já fora explicado no começo de toda essa análise. E não, não importa se o arquivo já foi criado ou não; isto é, caso criado, o arquivo é modificado para conter os novos dados. Após isso tudo, o programa é finalizado. ![Arquivo sendo criado ou modificado](/imgs/6.png) ![Arquivo final](/imgs/7.png) ### A quantidade de caracteres do segundo argumento é diferente de seis (...) mas é obrigatório que seja cinco. É o tal do *else* que não existe, que faz, logo após a verificação, a finalização do programa, a depender de determinadas condições. E são essas condições que serão análisadas agora. E além de ser cinco, seu valor **deve** se igualar a "/read". É como aconteceu no "primeiro *if*", em que a quantidade de caracteres era seis: verificação do número de caracteres, valor do argumento, ação e tal. Novamente, pelo menos em assembly, uma comparação de caractere a caractere é feita. Pelo menos dessa vez não há validação de alguma espécie de terceiro argumento da linha de comando. Pelo fato de que toda a lógica executada é muito parecida com a já explicada acima, as seguintes imagens demonstram o resumo dessa lógica: ![Checagem do segundo argumento como /read](/imgs/8.png) ![Obtenção do diretório com itens e o arquivo previamente escrito](/imgs/9.png) ![Leitura do arquivo e decodificação do base64](/imgs/10.png) ![Resultado final](/imgs/11.png) As imagens acima buscam representar (é o mesmo que está escrito no alt): - Checagem do segundo argumento como /read; - Obtenção do diretório com itens e o arquivo previamente escrito; - Leitura do arquivo e decodificação do base64; - Resultado final.