Статья Пишем simple-кейлогер с передачей дампа по TCP на C#. Часть II

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

Начнем с серверной части. Для начала вынесем функцию screenshot за пределы Main, оформим её отдельно. В дальнейшем мы будем обращаться к функции через команду Screen:
upload_2016-11-28_9-57-23.png


Кстати, если кто смотрел исходник, то мог заметить, что помимо нажатий клавиш, при запуске программы в лог записывается некоторая информация о системе и есть сохранение последнего содержимого буфера (text) через функцию GetBuff:
upload_2016-11-28_9-57-52.png

В дальнейшем можно также вынести функцию в отдельную команду, чтобы получать содержимое буфера по запросу. Очень часто в буфер попадают copy-pastу пароли.

Далее напишем пару функций-помощников. Для начала нам нужно научить пересылать данные между сервером и клиентом. Для начала напишем функцию, которая будет отправлять данные от сервера к клиенту:
upload_2016-11-28_9-59-53.png


Функция приминает два параметра: непосредственно данные и сокет, который будем использовать.
Использовать в дальнейшем будем таким образом:
upload_2016-11-28_10-1-3.png


Поскольку мы планируем сделать небольшой shell, необходимо каким-то образом заставить сервер запускать процессы на удаленном компьютере, а клиенту - пересылать данные.
Поможет нам в этом метод Process из пространства using System.Diagnostics.
Функция, написанная на основе этого метода, принимает два параметра: название команды, ее аргументы и сокет, через который будем работать.

upload_2016-11-28_10-7-27.png


Обращаем внимание на строку process.StartInfo.RedirectStandartOutput = true; она нам поможет формировать output для клиента. process.Start() запускает нашу команду, переданную через string s, string fl. Для формирования output используем StreamReader, читающий весь вывод до конца методом ReadToEnd(), и то, что получилось, отправляем через SSend(output,client).
process.WaitForExit() означает, что мы ждем завершения команды.
ВАЖНО! Например, если вы запустите Блокнот, он откроется на сервере и будет висеть до тех пор, пока кто-нибудь не закроет его. Пока на клиент не поступит сигнал о завершении, другую команду провести не выйдет. Вызов SSend как раз означает, что команда завершилась.

Теперь о сложном. Необходимо полностью переписать функцию ServerSocket():
upload_2016-11-28_10-13-55.png


Создаем точку подключения ipep, создаем сокет newsock, делаем bind и дальше слушаем на порту 9050. Все дальнейшие операции проводим в цикле while(true), внутри которого все будем обрабатывать через try-catch, чтобы иметь возможность отлавливать ошибки.
Далее ждем подключения клиента:
upload_2016-11-28_10-16-9.png

Готовим переменные под входящие данные, создаем просторный массив байтов.
Дальше начинается магия. Получать команды от клиента мы будем через client.Receive(bytes) и в переменную data направлять результат:
upload_2016-11-28_10-17-0.png


Дальше нам предстоит получить команду и ее аргументы. Считаем, что сначала идет команда, пробел, аргументы:
upload_2016-11-28_10-18-58.png


Отделяем команду от аргументов через data.Split(splitChars,2); - таким образом мы делим весь ввод на 2 части. Пробел - разделитель. На выходе получаем одномерный массив string a[].

Но что будет, если команда будет передана без аргументов, а в функцию мы NewProcess мы передаем аж три параметра? Ничего хорошего. Программа вылетит с ошибкой, т.к. не сможет найти a[1], поскольку его просто не существует. В сети есть куча примеров, как определить наличие индекса в массиве, от маленьких до больших. Но всегда есть способ проще:
upload_2016-11-28_10-32-14.png

Просто да? А то. Но ведь в NewProcess по-прежнему передается три параметра, скажете вы. Конечно, но теперь я покажу, как мы будем действовать. Пишем модуль для отправки команд.
Использовать будем switch(fileName). А fileName = a[0];
Полностью описывать не станем, всё есть в исходнике. Покажу основные моменты:
upload_2016-11-28_10-41-44.png


Таким образом вызовем ipconfig. В этом случае будет существовать только a[0].
Если наберем ipconfig /all - будет и a[0] и a[1] и они будут аргументами для NewProcess.
А вот если мы наберем cmd без параметров, то на сервер откроется cmd-сессия, но поскольку мы не передали аргументов, мы ничего не увидим, клиент останется в неведении и вполне возможно, мы просто потеряем контроль через Socket.
Чтобы этого не произошло, делаем так:
upload_2016-11-28_10-45-21.png


Таким простым образом защитили себя от невнимательности. Обратим внимание на вызов NewProcess: "cmd.exe" передается с ключем "/C" - это позволяет выполнить команду с последующим ее завершением. Без завершения нужен ключ "/K". Можете поэкспериментировать. Это позволяет проводить довольно большой список команд через CMD сервера, но не весь. Например переход в каталог через cd работать не будет. Кстати, работа cmd cmd не проверялась.

Для разнообразия я добавил еще функцию Delete. В ней также присутствует защита от использования без аргументов. А вот для удаления мы используем не cmd del, а метод FileInfo.Delete(). Но если вы дадите команду серверу cmd del path_to_file/file - это тоже сработает.
upload_2016-11-28_10-48-38.png


Как вы могли заметить, весь вывод идет через SSend. Функция в две строки оказалась чрезвычайно полезной.
Если вы наберете всякую белиберду, или вовсе пустую команду - есть case default:
upload_2016-11-28_10-51-52.png


Все что не нашлось в switch, будет распознано как мусор и выполняться не будет (почти, кое-что будет).Осталось совсем немного, дописать вызов функции отправляющей нам лог-файл и функцию, которая создает и отправляет по запросу скриншот удаленного компьютера.
За пределами switch пишем следующее:

upload_2016-11-28_10-54-39.png


Это вызов для функции скриншота. Заметили функцию SocketWorker? О ней чуть ниже.
Для лог-файла делаем аналогично:
upload_2016-11-28_10-55-33.png


В интернете есть много примеров передачи файлов через сокеты, что-то кривое, что-то нет.
Некоторые делают FTP-клиент(многие упираются в ошибку 500 при работе), некоторые пишут сервер.
Мы пойдем проще: останемся верными сокету. Функция принимает только имя файла, который нужен и текущий сокет:
upload_2016-11-28_10-57-6.png


Первые 5 строк должны быть понятны: берем входные параметры и переводим их в массивы байтов, с учетом смещений и выравнивания. А вот что за CopyTo?
Это весьма полезная вещь для работы с массивами. Метод позволяет копировать все элементы текущего одномерного массива в заданный одномерный массив начиная с указанного индекса в массиве назначения. Таким образом все содержимое нужного нам файла будет упаковано в массив byte[] и отправлено нам через... Socket.Send, т.е. тем же самым методом, которым мы отправляем и ответы на команды. Весьма удобно, можно протолкнуть слона в игольное ушко.

На этом моменте с серверной частью покончено. Базис задан, желающие могут пилить под себя. Это легче, чем кажется.

Приступим к клиенту. Задача клиента - соединяться с сервером, отправлять команды и получать ответы, а также принимать файлы по запросу.
Для клиента подключаем необходимые адресные пространства:
upload_2016-11-28_13-26-30.png

Больше для работы не требуется. В функции Main клиента пишем функционал для подключения к серверу:
upload_2016-11-28_13-26-55.png

В Main все просто. Главное в нем - функция SendMessageFromSocket(host). Функция принимает в качестве аргумента имя хоста или ip-адрес:
upload_2016-11-28_13-28-48.png

Первые три строки уже должны быть понятны: преобразовываем имя хоста, устанавливаем конечную точку подключения. Обратите внимание на AddressFamily. Метод InterNetwork означает использовать IP-адрес версии 4. Там же можно указать ATM, AppleTalk,Chaos и пр. Материал тут:

Сам метод AddressFamily возвращает семейство адресов IP-адреса.

Далее готовим буфер под принимаемые данные и соединяемся с удаленной точкой:
upload_2016-11-28_13-35-19.png


После того, как соединение установлено, нужно как-то послать команду серверу. Делаем это таким образом:
upload_2016-11-28_13-36-18.png


3 строка - проверка на пустую строку или Null. Наша команда это string message. Чтобы его отправить, его нужно преобразовать в байты - применим Encoding.
И дальше снова магия:
upload_2016-11-28_13-38-34.png

Отправляем команды, поддерживаемые сервером и получаем ответ.
Теперь оформим блок для приема двух файлов: скриншота и лог-файла с сервера.
upload_2016-11-28_13-39-34.png


Чуть позже напишем функцию SocketReceive, принимающая в качестве параметров Socket клиента и выходной файл. Желающие могу дописать функцию для сохранения в произвольное имя файла.

upload_2016-11-28_13-41-44.png


Чтобы клиент не закрывался после каждой введенной команды, используем переход в начало функции. Теоретически можно объявить метку и делать goto.

Обратите внимание, в серверной части нет функций для закрытия сокета, чтобы мы могли подключаться к серверу постоянно, если на клиенте вдруг произойдет сбой, либо мы прервем его выполнение (клиент можно прервать по CTRL+Z. В этом случае сервер напишет у себя в логе Bye!, а клиент завершиться с выводом строки "Передача завершена. Завершите соединение вручную")

Осталось малое - написать функцию SocketReceive, для приема файлов.
upload_2016-11-28_13-45-49.png

На вход подаем сокет и имя файла, создается буфер под принятые данные.
После подготовки к приему, сообщаем что мы готовы и открываем BinaryWriter для записи потока битов напрямую в файл на клиенте.
int receivedBytesLen = sender.Receive(clientData) - принимает данные от сервера.
Файлы сохраняются там же, где лежит клиент.
upload_2016-11-28_13-47-55.png

После приема сообщаем об этом и закрываем поток BinaryWriter.

На этом шаге написание клиента завершено. Осталось написать отдельный инструмент для работы с шифрованным логом, расшифровать его, проанализировать, восстановить все нажатия.
Но об этом - в следующей статье.
 

Вложения

  • upload_2016-11-28_13-53-18.png
    upload_2016-11-28_13-53-18.png
    8,4 КБ · Просмотры: 540
  • upload_2016-11-28_13-53-52.png
    upload_2016-11-28_13-53-52.png
    7,8 КБ · Просмотры: 506
  • upload_2016-11-28_13-54-47.png
    upload_2016-11-28_13-54-47.png
    1,1 КБ · Просмотры: 707
Последнее редактирование модератором:

viktorcruce

Green Team
19.12.2016
28
5
BIT
0
Кстати, если кто смотрел исходник, то мог заметить, что помимо нажатий клавиш, при запуске программы в лог записывается некоторая информация о системе и есть сохранение последнего содержимого буфера (text) через функцию GetBuff:
Посмотреть вложение 7635
Хай бро. Как лучше мониторить буфер c минимальным ресурсо-потреблением. Через бесконечный while и сравнить oldbuf c newbuf ?
 
I

Ishikawa

Хай бро. Как лучше мониторить буфер c минимальным ресурсо-потреблением. Через бесконечный while и сравнить oldbuf c newbuf ?
Бесконечный while не лучший вариант, как и вообще бесконечный цикл. Лучше отлавливать события буфера (класс Clipboard)
upload_2017-1-12_11-29-57.png


p.s. спасибо, рассмотрю вопрос детально на досуге :)
 
L

LifeStream

В след статье напиши для всеобщего развития: Как открыть порт если у пользователя ( модем ) - ( IP - который всегда меняется)
Я замечал что большую часть людей именно с этим возятся).
 
M

molexuse

Привет, бро. Да, будет еще дешифратор. Он самая сложная часть, т.к.нужно парсить строки, используя всякие Replace и Regex. В дальнейшем можно будет объединить клиент и дешифратор в одну программу.
На текущий момент принцип такой: сервер работает на жертве постоянно, мы подсоединяемся клиентом с целью быстро вытащить логи или скрин и смыться. А награбленное скормить дешифратору.
Время от времени я дописываю еще и серверную часть, сегодня написал как поставить прогу в автозапуск.
Тема очень интересная, особенно для начинающих, все доступно расписано, прочитал не отрываясь.
Жаль нет продолжения.
 
I

Ishikawa

Продолжение будет. Тема на своём месте (уже обсуждалось)
[doublepost=1495683259,1495673215][/doublepost]Часть 3
 
  • Нравится
Реакции: dingobongoп
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!