Ao analisar o malware destinado à mineração de criptomoedas, é mais interessante estudar os carregadores (carregadores ou conta-gotas) dos mineradores do que os próprios mineradores, pois é nos carregadores que as técnicas destinadas a contornar as proteções e neutralizar a detecção são implementadas no primeiro lugar .
Então, ao analisar uma amostra envolvida na campanha de distribuição do minerador Monero, me deparei com um mecanismo de malware bem conhecido, mas ainda assim interessante.
Este mecanismo é baseado em uma chamada direta para as funções do sistema operacional Windows, ignorando o uso do caminho padrão através da chamada para as funções das bibliotecas kernel32.dll e ntdll.dll .. Essa abordagem permite que os autores de malware contornem os seqüestros de funções do sistema no espaço do usuário por agentes EDR, reduzindo assim o risco de detecção de malware.
Um dos problemas com a implementação dessa abordagem é que, para cada versão e compilação do sistema operacional, o número de chamada do sistema pode ser diferente. Um bom recurso para ver essas alterações é a tabela de j00ru , que mostra como os números de recursos específicos mudam de versão para versão. O número syscol é determinado por sua localização em ntdll.dll , ou seja, o número syscol 0 estará localizado no endereço mais baixo na seção .text em comparação com outras funções semelhantes.
Ao mesmo tempo, todas as funções do sistema exportadas em ntdll.dll são um wrapper sobre a instrução syscall (ou interrupções int 2Eh), que inicia a execução de uma função do sistema no nível do kernel:
Um exemplo do conteúdo de uma função exportada em ntdll.dll
Como os números syscoll podem diferir em cada versão do sistema operacional, o malware deve ter uma lista completa de chamadas de sistema para todas as versões de interesse ou outro mecanismo para ajudar a entender quais números syscoll são relevantes para uma determinada versão.
Depois de atualizarmos nossos conhecimentos sobre a técnica de chamada direta de syscols, passaremos à análise da própria amostra, ou melhor, do mecanismo que nos interessa.
análise
No processo de análise de uma amostra maliciosa nos estágios iniciais, encontramos a seguinte função
Uma função que usa syscall Que contém uma instrução syscall
que é atípica para software comum , mas como estamos analisando malware, algo atípico pode, pelo contrário, ser bastante apropriado nesse contexto de funcionalidade maliciosa. syscall pega o número syscall do registro rax/eax , como não há entradas em rax no código anterior , parece que o número syscall é retornado pela função sub_4018f1 (vamos chamá-la de ResolveSysCall , já que existem várias refexs apontando para ela , e com certeza vamos encontrá-lo mais). Dentro do ResolveSysCall
pode-se observar que o valor do argumento no loop é comparado com os valores de algum array , e em caso de match, é retornado o índice do elemento do array, ou seja, o número da chamada do sistema .
Conteúdo do ResolveSysCall
Loop em ResolveSysCall
Assim, torna-se inequivocamente claro que este módulo malicioso usa a técnica de usar chamadas de sistema diretamente .
SysWhispers2
No ResolveSysCall, toda a mágica acontece na função sub_4014b6, vamos renomeá-la para ComputeSysCallsList e passar para sua análise.
Nesta função, encontramos o seguinte conteúdo, ainda não muito claro:
O conteúdo da função ComputeSyscallsList
que se tornará mais acessível após reconhecer as estruturas utilizadas e renomear as variáveis correspondentes. Nomeadamente
Fragmento 2
Em alguns lugares, devido à visão do descompilador, não fica totalmente claro o que exatamente está acontecendo nesta seção do código, mas se você mergulhar na visão desmontada em paralelo, um mecanismo como mover-se pelas estruturas do formato PE64 fica mais acessível:
Seção de análise de cabeçalho PE em representações desmontadas e descompiladas
Pessoalmente, não consegui entender que a linha de código destacada estava procurando o endereço da tabela de exportação até que verifiquei exatamente quais deslocamentos foram usados no código nativo. Aqui, mais uma vez, é confirmada a velha verdade de que a representação descompilada ajuda quando você precisa analisar algo rapidamente, mas ao estudar algumas seções em detalhes, é melhor olhar para a janela do desmontador, não contando apenas com o descompilador.
Pelo que vi, está claro que esses são componentes da ferramenta SysWhispers2 . Por exemplo, o padrãopara procurar funções com base em uma comparação com os caracteres Zw, que corresponde à mesma seção de comparação no loop com esta substring na representação descompilada:
Seções de código semelhantes nas fontes e no descompilador
SysWhipsers2 resolvem o problema com diferenças de números em cada versão, analisando a versão do ntdll.dll carregada na memória e compilando uma tabela de correspondência entre hashes de nomes de funções e seus números, que é apresentada em o código acima.
Além do SysWhispers2, os autores de malware podem usar outras versões ou técnicas semelhantes (por exemplo, Hell's Gate ou Halo's Gate ).
Agora, tendo encontrado o projeto original, você pode visualizá-lo para comparar os resultados de sua pesquisa das partes relevantes do módulo malicioso com o código original.
Um pouco de automação
Como o SysWhispers2 pode ser encontrado de tempos em tempos durante a análise de vários módulos, você pode escrever um script para simplificar a análise desse malware, que também calculará os hashes dos nomes de todas as funções do sistema e indicará os nomes das funções em as seções apropriadas. Você também pode adicionar a compilação de uma tabela semelhante de correspondência de números siskol aos seus nomes, o que de repente será útil no futuro (especialmente porque já temos o código-fonte C para essa lógica).
O script final usando IDAPy assume a seguinte forma (é claro, se alguém vir falhas de código que não consegue tolerar, peço desculpas):
import idautils
import idaapi
import pefile
#
def hash(name):
position = 0
# seed-
seed = 0x7d895397
while name[position]:
seed ^= (((seed << 24) | (seed >> 8)) + (int.from_bytes(name[position:position+2], "little"))) & 0xffffffff
position += 1
return(seed)
#
def get_func_name(hashes, syscalls, hash_value):
syscall = list(hashes.keys())[list(hashes.values()).index(hash_value)]
return(syscalls[syscall])
# : ,
# ,
#
SysCallsTableTmp = {}
SysCallsTable = {}
SysCallsTable_hashes = {}
# ntdll ,
# ,
#
pe = pefile.PE("C:\\Windows\\System32\\ntdll.dll")
for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
try:
if b"Zw" in entry.name:
SysCallsTableTmp[entry.name] = hex(pe.OPTIONAL_HEADER.ImageBase + entry.address)
except:
continue
#
# ( 0 ),
# ,
# .
# , ,
#
SysCallsTable_sorted = sorted(SysCallsTableTmp.items(), key = lambda syscall: syscall[1])
# ,
for i in range(len(SysCallsTableTmp)):
SysCallsTable[hex(i)] = SysCallsTable_sorted[i][0]
SysCallsTable_hashes[hex(i)] = hash(SysCallsTable[hex(i)] + b"\x00")
# ResolveSyscalls
# 0x4018F1 ,
#
xrefs = XrefsTo(0x4018F1)
for i in xrefs:
ea = prev_head(i.frm)
if "ecx" in generate_disasm_line(ea, 0):
name = get_func_name(SysCallsTable_hashes, SysCallsTable, get_operand_value(ea, 1) & 0xffffffff)
set_cmt(ea, name.decode("utf-8"), 1)
Após executar o script, teremos a seguinte visualização das seções onde são utilizados os siscols (com o nome da função que será chamada):
Exemplo 1
Exemplo 2
Assim, obtivemos uma pequena ferramenta que permite usar o SysWhispers2 para determinar pelo hash do nome syscol que tipo de função do sistema será chamada. Agora, se encontrarmos novamente uma amostra que usa SysWhispers2 , poderemos entender a funcionalidade do módulo malicioso analisado muito mais rapidamente.