{
	"id": "68da034f-66af-46d8-bd80-efaa48a2424f",
	"created_at": "2026-04-06T02:12:34.302565Z",
	"updated_at": "2026-04-10T03:20:26.650439Z",
	"deleted_at": null,
	"sha1_hash": "cc1eb7569b8512f7d8cc1b98f57eb68045bd34d7",
	"title": "Потомок «нецензурного» трояна или как воруют пароли на FTP.",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 52004,
	"plain_text": "Потомок «нецензурного» трояна или как воруют пароли на\r\nFTP.\r\nBy Владимир Мартьянов\r\nPublished: 2008-06-08 · Archived: 2026-04-06 01:31:41 UTC\r\n5 мин\r\n3.2K\r\nВчера я разбирал «нецензурный» троян (http://vilgeforce.habrahabr.ru/blog/44130.html), а сегодня\r\nразделываю его потомка — ftp34.dll. Эта тваринка, кстати, куда как интереснее подавляющего\r\nбольшинства троянов. Хотя бы тем, что ворует информацию не с диска, а прямо из сетевого траффика.\r\nКак? Смотрите под кат.\r\nВ первой серии был почти до конца разобран один из компонентов комплекса «Троян Залупко». Он дропал\r\nна диск файл ftp34.dll и загружал его при помощи LoadLibrary. Причем это происходило при каждой\r\nактивации трояна.\r\nftp34.dll — файл размером 4608 байт, упакован UPX, поэтому в Linux (я дома в линухе) распаковывается за\r\n5 секунд. Распакованный файл весит 7608 байт. DLL, как и ее дроппер, использует тот же принципи\r\nшифрования строк — XOR одним байтом, код функции тот же. Скрипт для IDA пишется за минуту.\r\nIDA заботливо свернула код точки входа, переместив курсор на DllMain. В DllMain проверяется причина\r\nвызова: если производится загрузка библиотеки выполняются одни действия, если выгрузка — другие. Так\r\nкак call'ов для выгрузки было меньше, начал с этого куска. В продуре Detach (назовем ее так) — странный\r\nкод:\r\n.text:10001D2F push eax; lpNumberOfBytesWritten\r\n.text:10001D30 push 6; nSize\r\n.text:10001D32 push offset unk_1001213C; lpBuffer\r\n.text:10001D37 push lpBaseAddress; lpBaseAddress\r\n.text:10001D3D push 0FFFFFFFFh; hProcess\r\n.text:10001D3F call ds:WriteProcessMemory\r\nи повторяется он 4 раза. Если я ничего не путаю, то hProcess равный -1 означает запись в свое же\r\nсобственное адресное пространство. Беглый анализ ссылок на адреса буфера для чтения и для записи\r\nпоказал, что DLL довольно активно читает/пишет в них при помощи Read/WriteProcessMemory. Открытый\r\nвопрос «Зачем?» оставлю на потом.\r\nДействия при загрузке\r\nКак и свой «родитель», эта библиотека подготавливает строки, содержащие пути к нужным файлам:\r\nhttps://habr.com/ru/post/27053/\r\nPage 1 of 4\n\n%TEMP%\\r43q34.tmp и %TEMP%\\mpz.tmp. Присутствие в системе других экземпляров библиотеки\r\nопределяется при помощь мьютекса, и если его нет, создается поток Thread1. Имена мьютексов я не\r\nпривожу, ибо сомневаюсь, что кто-то будет проверять систему на их наличие :-) Теперь — самое\r\nинтересное! Библиотека ПАТЧИТ функции Windows Sockets в памяти. Как это происходит? Вот код:\r\n.text:10001B72 push 4; int\r\n.text:10001B74 push offset aSw676Hh; «ws2_32.dll»\r\n.text:10001B79 call decryptXor\r\n.text:10001B7E pop ecx\r\n.text:10001B7F pop ecx\r\n.text:10001B80 push eax; lpModuleName\r\n.text:10001B81 call ds:GetModuleHandleA; Получаем хэнд ws2_32.dll\r\n.text:10001B87 mov [ebp+ws2_32handle], eax\r\n.text:10001B8A push 5; int\r\n.text:10001B8C push offset aWFs; «recv»\r\n.text:10001B91 call decryptXor\r\n.text:10001B96 pop ecx\r\n.text:10001B97 pop ecx\r\n.text:10001B98 push eax; lpProcName\r\n.text:10001B99 push [ebp+ws2_32handle]; hModule\r\n.text:10001B9C call ds:GetProcAddress; Получаем адрес функции recv\r\n.text:10001BA2 mov recvAddr, eax\r\n.text:10001BA7 lea eax, [ebp+NumberOfBytesWritten]\r\n.text:10001BAA push eax; lpNumberOfBytesRead\r\n.text:10001BAB push 6; nSize\r\n.text:10001BAD push offset originalCode; lpBuffer\r\n.text:10001BB2 push recvAddr; lpBaseAddress\r\n.text:10001BB8 push 0FFFFFFFFh; hProcess\r\n.text:10001BBA call ds:ReadProcessMemory; Читаем в буфер originalCode первые 6 байт функции recv\r\n.text:10001BC0 mov HookCode, 68h; В буфер, записываемый в начало recv() помещаем опкод команды push\r\n.text:10001BC7 mov dword ptr HookCode+1, offset newRecv; Следом за push — адрес нашего нового\r\nобработчика\r\n.text:10001BD1 mov HookCode+5, 0C3h; И теперь RET\r\n.text:10001BD8 lea eax, [ebp+NumberOfBytesWritten]\r\n.text:10001BDB push eax; lpNumberOfBytesWritten\r\n.text:10001BDC push 6; nSize\r\n.text:10001BDE push offset HookCode; lpBuffer\r\n.text:10001BE3 push recvAddr; lpBaseAddress\r\n.text:10001BE9 push 0FFFFFFFFh; hProcess\r\n.text:10001BEB call ds:WriteProcessMemory; Пишем нашу вставку на начало recv(). Дело сделано!\r\nУвы, получился он куда как менее читаемым, нежели в IDA :-( Вкратце: получили адрес нужной функции.\r\nСчитали с этого адреса 6 байт, подготовили буфер с кодом\r\nhttps://habr.com/ru/post/27053/\r\nPage 2 of 4\n\npush offset myRecv\r\nret\r\nи записали его в начало перехватываемой процедуры. Комбинация push-ret — переход на нужный нам\r\nадрес не совсем очевидным способом. Перехватываются следующие функции: recv(), WSARecv(),\r\nWSASend(), send(). Теперь стало ясно, что такое пишется в память при выгрузке DLLки: это\r\nвосстанавливается оригинальный код перехватываемых функций. Остался главный вопрос — как\r\nпередается управление на оригинальные функции? И что же с Thread1? Она устанавливает свой\r\nобработчик (который, кстати, ничего криминального не далет) при помощи SetWindowsHookEx. Зачем?\r\nТочно не знаю… Но да это, думаю, не важно.\r\nФункции перехватчиков\r\nУ всех перехватчиков много общего: это короткие процедуры, содержащие, грубо говоря, только 2 вызова.\r\nПервый вызов одинаков для перехватчиков send() и WSASend(), а второй — для recv() и WSARecv(), то\r\nесть разделение по функционалу. Назову эти две функции HookSend и HookRecv соответственно. Второй\r\nвызов в перехватчиках разный, это вызов функции, которая патчит перехватываемые функции до\r\nисходного состояния, вызывает их, а потом патчит в вариант с перехватчиком.\r\nФункции HookSend() и HookRecv() получают три параметра — сокет, буфер и длину. Начальный код тоже\r\nсовпадает: получаем адрес, к которому подключен сокет, преобразовываем этот адрес в строку, а также\r\nпереводит адрес из сетевого порядка следования байт в хостовый. Тут возникает не совсем понятный мне\r\nмомент:\r\n.text:100015CA push [ebp+s]; s\r\n.text:100015CD call ds:getpeername\r\n.text:100015D3 push dword ptr [ebp+name.sa_data+2]; in\r\n.text:100015D6 call ds:inet_ntoa\r\n.text:100015DC push eax; Source\r\n.text:100015DD push offset byte_10011C10; Dest\r\n.text:100015E2 call strcpy\r\n.text:100015E7 pop ecx\r\n.text:100015E8 pop ecx\r\n.text:100015E9 push dword ptr [ebp-12h]; netshort\r\n.text:100015EC call ds:ntohs\r\n.text:100015F2 movzx eax, ax\r\n.text:100015F5 cmp eax, 25\r\n.text:100015F8 jnz short loc_10001607\r\ns — сокет. Не понимаю, как мы после вызова ntohs в ax получаем порт? Или там действительно порт будет\r\nи я плохо доки читал? В общем, интуиция и знакомые числа (25, 80, 110 :-) подсказали, что идет проверка\r\nпорта, к которому осуществлен коннект. Для приема перехватывается траффик по следующим портам: 25,\r\n80, 110. Для передачи: 25, 80, 21. Причем траффик 21-го порта обрабатывается как-то хитро. Передача по\r\n80-му порту, похоже, вносит некоторые изменения в траффик: если в передаваемых данных встречается\r\nстрока «gzip,», то она будет заменена на 5 байт с кодом 0x6E («n»). Зачем? Не знаю… На этот момент не\r\nразобранными остались только процедуры поиска в передаваемых данных паролей на FTP и почтовых\r\nhttps://habr.com/ru/post/27053/\r\nPage 3 of 4\n\nадресов, а также записи этого добра в файлы. Строк для воровства почтовых паролей не видно, равно как и\r\nкода, отправлящего собранную информацию по сети. Для этого, наверное, есть свои компоненты.\r\nЕсли такая зараза получит распространение, то никакие рекомендации от Пинча типа «Не хранить пароли\r\nна дисках» не помогут. Остается только переход на шифрованные каналы связи. Но учитывая перехват\r\nвсего траффика это, думаю, слабо поможет :-(\r\nЧто мне во всем этом не ясно и странно:\r\n1) WriteProcessMemory использует в качестве хэндла -1. Почему сомневаюсь, что перехвачены будут\r\nвызовы для всех приложений.\r\n2) Зачем применяется SetWindowsHookEx?\r\n3) Обнаруживается ли активность трояна поведенческими анализаторами? И вообще хоть каким-нибудь\r\nсофтом (кроме сигнатурного поиска).\r\n4) Откуда столько людей узнают про мой пост? Меня читает, насколько я знаю, меньше 10 человек :-D\r\nВремя на анализ — около 2 часов (попутно отвечая на комменты). Инструменты — UPX + IDA Pro +\r\nOllyDbg (можно было и без него), голова с мозгами.\r\nSource: https://habr.com/ru/post/27053/\r\nhttps://habr.com/ru/post/27053/\r\nPage 4 of 4",
	"extraction_quality": 1,
	"language": "RU",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://habr.com/ru/post/27053/"
	],
	"report_names": [
		"27053"
	],
	"threat_actors": [],
	"ts_created_at": 1775441554,
	"ts_updated_at": 1775791226,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/cc1eb7569b8512f7d8cc1b98f57eb68045bd34d7.pdf",
		"text": "https://archive.orkl.eu/cc1eb7569b8512f7d8cc1b98f57eb68045bd34d7.txt",
		"img": "https://archive.orkl.eu/cc1eb7569b8512f7d8cc1b98f57eb68045bd34d7.jpg"
	}
}