В первой части было показан вариант серверной части простого кейлогера с передачей дампа по сети используя Socket. Сегодня мы доработаем серверную часть, немного расширив функционал и напишем клиента: переделаем функции для работы с сокетами и добавим несложный удаленный shell, использующий возможности Windows CMD.
Начнем с серверной части. Для начала вынесем функцию screenshot за пределы Main, оформим её отдельно. В дальнейшем мы будем обращаться к функции через команду Screen:
Кстати, если кто смотрел исходник, то мог заметить, что помимо нажатий клавиш, при запуске программы в лог записывается некоторая информация о системе и есть сохранение последнего содержимого буфера (text) через функцию GetBuff:
В дальнейшем можно также вынести функцию в отдельную команду, чтобы получать содержимое буфера по запросу. Очень часто в буфер попадают copy-pastу пароли.
Далее напишем пару функций-помощников. Для начала нам нужно научить пересылать данные между сервером и клиентом. Для начала напишем функцию, которая будет отправлять данные от сервера к клиенту:
Функция приминает два параметра: непосредственно данные и сокет, который будем использовать.
Использовать в дальнейшем будем таким образом:
Поскольку мы планируем сделать небольшой shell, необходимо каким-то образом заставить сервер запускать процессы на удаленном компьютере, а клиенту - пересылать данные.
Поможет нам в этом метод Process из пространства using System.Diagnostics.
Функция, написанная на основе этого метода, принимает два параметра: название команды, ее аргументы и сокет, через который будем работать.
Обращаем внимание на строку process.StartInfo.RedirectStandartOutput = true; она нам поможет формировать output для клиента. process.Start() запускает нашу команду, переданную через string s, string fl. Для формирования output используем StreamReader, читающий весь вывод до конца методом ReadToEnd(), и то, что получилось, отправляем через SSend(output,client).
process.WaitForExit() означает, что мы ждем завершения команды.
ВАЖНО! Например, если вы запустите Блокнот, он откроется на сервере и будет висеть до тех пор, пока кто-нибудь не закроет его. Пока на клиент не поступит сигнал о завершении, другую команду провести не выйдет. Вызов SSend как раз означает, что команда завершилась.
Теперь о сложном. Необходимо полностью переписать функцию ServerSocket():
Создаем точку подключения ipep, создаем сокет newsock, делаем bind и дальше слушаем на порту 9050. Все дальнейшие операции проводим в цикле while(true), внутри которого все будем обрабатывать через try-catch, чтобы иметь возможность отлавливать ошибки.
Далее ждем подключения клиента:
Готовим переменные под входящие данные, создаем просторный массив байтов.
Дальше начинается магия. Получать команды от клиента мы будем через client.Receive(bytes) и в переменную data направлять результат:
Дальше нам предстоит получить команду и ее аргументы. Считаем, что сначала идет команда, пробел, аргументы:
Отделяем команду от аргументов через data.Split(splitChars,2); - таким образом мы делим весь ввод на 2 части. Пробел - разделитель. На выходе получаем одномерный массив string a[].
Но что будет, если команда будет передана без аргументов, а в функцию мы NewProcess мы передаем аж три параметра? Ничего хорошего. Программа вылетит с ошибкой, т.к. не сможет найти a[1], поскольку его просто не существует. В сети есть куча примеров, как определить наличие индекса в массиве, от маленьких до больших. Но всегда есть способ проще:
Просто да? А то. Но ведь в NewProcess по-прежнему передается три параметра, скажете вы. Конечно, но теперь я покажу, как мы будем действовать. Пишем модуль для отправки команд.
Использовать будем switch(fileName). А fileName = a[0];
Полностью описывать не станем, всё есть в исходнике. Покажу основные моменты:
Таким образом вызовем ipconfig. В этом случае будет существовать только a[0].
Если наберем ipconfig /all - будет и a[0] и a[1] и они будут аргументами для NewProcess.
А вот если мы наберем cmd без параметров, то на сервер откроется cmd-сессия, но поскольку мы не передали аргументов, мы ничего не увидим, клиент останется в неведении и вполне возможно, мы просто потеряем контроль через Socket.
Чтобы этого не произошло, делаем так:
Таким простым образом защитили себя от невнимательности. Обратим внимание на вызов NewProcess: "cmd.exe" передается с ключем "/C" - это позволяет выполнить команду с последующим ее завершением. Без завершения нужен ключ "/K". Можете поэкспериментировать. Это позволяет проводить довольно большой список команд через CMD сервера, но не весь. Например переход в каталог через cd работать не будет. Кстати, работа cmd cmd не проверялась.
Для разнообразия я добавил еще функцию Delete. В ней также присутствует защита от использования без аргументов. А вот для удаления мы используем не cmd del, а метод FileInfo.Delete(). Но если вы дадите команду серверу cmd del path_to_file/file - это тоже сработает.
Как вы могли заметить, весь вывод идет через SSend. Функция в две строки оказалась чрезвычайно полезной.
Если вы наберете всякую белиберду, или вовсе пустую команду - есть case default:
Все что не нашлось в switch, будет распознано как мусор и выполняться не будет (почти, кое-что будет).Осталось совсем немного, дописать вызов функции отправляющей нам лог-файл и функцию, которая создает и отправляет по запросу скриншот удаленного компьютера.
За пределами switch пишем следующее:
Это вызов для функции скриншота. Заметили функцию SocketWorker? О ней чуть ниже.
Для лог-файла делаем аналогично:
В интернете есть много примеров передачи файлов через сокеты, что-то кривое, что-то нет.
Некоторые делают FTP-клиент(многие упираются в ошибку 500 при работе), некоторые пишут сервер.
Мы пойдем проще: останемся верными сокету. Функция принимает только имя файла, который нужен и текущий сокет:
Первые 5 строк должны быть понятны: берем входные параметры и переводим их в массивы байтов, с учетом смещений и выравнивания. А вот что за CopyTo?
Это весьма полезная вещь для работы с массивами. Метод позволяет копировать все элементы текущего одномерного массива в заданный одномерный массив начиная с указанного индекса в массиве назначения. Таким образом все содержимое нужного нам файла будет упаковано в массив byte[] и отправлено нам через... Socket.Send, т.е. тем же самым методом, которым мы отправляем и ответы на команды. Весьма удобно, можно протолкнуть слона в игольное ушко.
На этом моменте с серверной частью покончено. Базис задан, желающие могут пилить под себя. Это легче, чем кажется.
Приступим к клиенту. Задача клиента - соединяться с сервером, отправлять команды и получать ответы, а также принимать файлы по запросу.
Для клиента подключаем необходимые адресные пространства:
Больше для работы не требуется. В функции Main клиента пишем функционал для подключения к серверу:
В Main все просто. Главное в нем - функция SendMessageFromSocket(host). Функция принимает в качестве аргумента имя хоста или ip-адрес:
Первые три строки уже должны быть понятны: преобразовываем имя хоста, устанавливаем конечную точку подключения. Обратите внимание на AddressFamily. Метод InterNetwork означает использовать IP-адрес версии 4. Там же можно указать ATM, AppleTalk,Chaos и пр. Материал тут:
Сам метод AddressFamily возвращает семейство адресов IP-адреса.
Далее готовим буфер под принимаемые данные и соединяемся с удаленной точкой:
После того, как соединение установлено, нужно как-то послать команду серверу. Делаем это таким образом:
3 строка - проверка на пустую строку или Null. Наша команда это string message. Чтобы его отправить, его нужно преобразовать в байты - применим Encoding.
И дальше снова магия:
Отправляем команды, поддерживаемые сервером и получаем ответ.
Теперь оформим блок для приема двух файлов: скриншота и лог-файла с сервера.
Чуть позже напишем функцию SocketReceive, принимающая в качестве параметров Socket клиента и выходной файл. Желающие могу дописать функцию для сохранения в произвольное имя файла.
Чтобы клиент не закрывался после каждой введенной команды, используем переход в начало функции. Теоретически можно объявить метку и делать goto.
Обратите внимание, в серверной части нет функций для закрытия сокета, чтобы мы могли подключаться к серверу постоянно, если на клиенте вдруг произойдет сбой, либо мы прервем его выполнение (клиент можно прервать по CTRL+Z. В этом случае сервер напишет у себя в логе Bye!, а клиент завершиться с выводом строки "Передача завершена. Завершите соединение вручную")
Осталось малое - написать функцию SocketReceive, для приема файлов.
На вход подаем сокет и имя файла, создается буфер под принятые данные.
После подготовки к приему, сообщаем что мы готовы и открываем BinaryWriter для записи потока битов напрямую в файл на клиенте.
int receivedBytesLen = sender.Receive(clientData) - принимает данные от сервера.
Файлы сохраняются там же, где лежит клиент.
После приема сообщаем об этом и закрываем поток BinaryWriter.
На этом шаге написание клиента завершено. Осталось написать отдельный инструмент для работы с шифрованным логом, расшифровать его, проанализировать, восстановить все нажатия.
Но об этом - в следующей статье.
Начнем с серверной части. Для начала вынесем функцию screenshot за пределы Main, оформим её отдельно. В дальнейшем мы будем обращаться к функции через команду Screen:
Кстати, если кто смотрел исходник, то мог заметить, что помимо нажатий клавиш, при запуске программы в лог записывается некоторая информация о системе и есть сохранение последнего содержимого буфера (text) через функцию GetBuff:
В дальнейшем можно также вынести функцию в отдельную команду, чтобы получать содержимое буфера по запросу. Очень часто в буфер попадают copy-pastу пароли.
Далее напишем пару функций-помощников. Для начала нам нужно научить пересылать данные между сервером и клиентом. Для начала напишем функцию, которая будет отправлять данные от сервера к клиенту:
Функция приминает два параметра: непосредственно данные и сокет, который будем использовать.
Использовать в дальнейшем будем таким образом:
Поскольку мы планируем сделать небольшой shell, необходимо каким-то образом заставить сервер запускать процессы на удаленном компьютере, а клиенту - пересылать данные.
Поможет нам в этом метод Process из пространства using System.Diagnostics.
Функция, написанная на основе этого метода, принимает два параметра: название команды, ее аргументы и сокет, через который будем работать.
Обращаем внимание на строку process.StartInfo.RedirectStandartOutput = true; она нам поможет формировать output для клиента. process.Start() запускает нашу команду, переданную через string s, string fl. Для формирования output используем StreamReader, читающий весь вывод до конца методом ReadToEnd(), и то, что получилось, отправляем через SSend(output,client).
process.WaitForExit() означает, что мы ждем завершения команды.
ВАЖНО! Например, если вы запустите Блокнот, он откроется на сервере и будет висеть до тех пор, пока кто-нибудь не закроет его. Пока на клиент не поступит сигнал о завершении, другую команду провести не выйдет. Вызов SSend как раз означает, что команда завершилась.
Теперь о сложном. Необходимо полностью переписать функцию ServerSocket():
Создаем точку подключения ipep, создаем сокет newsock, делаем bind и дальше слушаем на порту 9050. Все дальнейшие операции проводим в цикле while(true), внутри которого все будем обрабатывать через try-catch, чтобы иметь возможность отлавливать ошибки.
Далее ждем подключения клиента:
Готовим переменные под входящие данные, создаем просторный массив байтов.
Дальше начинается магия. Получать команды от клиента мы будем через client.Receive(bytes) и в переменную data направлять результат:
Дальше нам предстоит получить команду и ее аргументы. Считаем, что сначала идет команда, пробел, аргументы:
Отделяем команду от аргументов через data.Split(splitChars,2); - таким образом мы делим весь ввод на 2 части. Пробел - разделитель. На выходе получаем одномерный массив string a[].
Но что будет, если команда будет передана без аргументов, а в функцию мы NewProcess мы передаем аж три параметра? Ничего хорошего. Программа вылетит с ошибкой, т.к. не сможет найти a[1], поскольку его просто не существует. В сети есть куча примеров, как определить наличие индекса в массиве, от маленьких до больших. Но всегда есть способ проще:
Просто да? А то. Но ведь в NewProcess по-прежнему передается три параметра, скажете вы. Конечно, но теперь я покажу, как мы будем действовать. Пишем модуль для отправки команд.
Использовать будем switch(fileName). А fileName = a[0];
Полностью описывать не станем, всё есть в исходнике. Покажу основные моменты:
Таким образом вызовем ipconfig. В этом случае будет существовать только a[0].
Если наберем ipconfig /all - будет и a[0] и a[1] и они будут аргументами для NewProcess.
А вот если мы наберем cmd без параметров, то на сервер откроется cmd-сессия, но поскольку мы не передали аргументов, мы ничего не увидим, клиент останется в неведении и вполне возможно, мы просто потеряем контроль через Socket.
Чтобы этого не произошло, делаем так:
Таким простым образом защитили себя от невнимательности. Обратим внимание на вызов NewProcess: "cmd.exe" передается с ключем "/C" - это позволяет выполнить команду с последующим ее завершением. Без завершения нужен ключ "/K". Можете поэкспериментировать. Это позволяет проводить довольно большой список команд через CMD сервера, но не весь. Например переход в каталог через cd работать не будет. Кстати, работа cmd cmd не проверялась.
Для разнообразия я добавил еще функцию Delete. В ней также присутствует защита от использования без аргументов. А вот для удаления мы используем не cmd del, а метод FileInfo.Delete(). Но если вы дадите команду серверу cmd del path_to_file/file - это тоже сработает.
Как вы могли заметить, весь вывод идет через SSend. Функция в две строки оказалась чрезвычайно полезной.
Если вы наберете всякую белиберду, или вовсе пустую команду - есть case default:
Все что не нашлось в switch, будет распознано как мусор и выполняться не будет (почти, кое-что будет).Осталось совсем немного, дописать вызов функции отправляющей нам лог-файл и функцию, которая создает и отправляет по запросу скриншот удаленного компьютера.
За пределами switch пишем следующее:
Это вызов для функции скриншота. Заметили функцию SocketWorker? О ней чуть ниже.
Для лог-файла делаем аналогично:
В интернете есть много примеров передачи файлов через сокеты, что-то кривое, что-то нет.
Некоторые делают FTP-клиент(многие упираются в ошибку 500 при работе), некоторые пишут сервер.
Мы пойдем проще: останемся верными сокету. Функция принимает только имя файла, который нужен и текущий сокет:
Первые 5 строк должны быть понятны: берем входные параметры и переводим их в массивы байтов, с учетом смещений и выравнивания. А вот что за CopyTo?
Это весьма полезная вещь для работы с массивами. Метод позволяет копировать все элементы текущего одномерного массива в заданный одномерный массив начиная с указанного индекса в массиве назначения. Таким образом все содержимое нужного нам файла будет упаковано в массив byte[] и отправлено нам через... Socket.Send, т.е. тем же самым методом, которым мы отправляем и ответы на команды. Весьма удобно, можно протолкнуть слона в игольное ушко.
На этом моменте с серверной частью покончено. Базис задан, желающие могут пилить под себя. Это легче, чем кажется.
Приступим к клиенту. Задача клиента - соединяться с сервером, отправлять команды и получать ответы, а также принимать файлы по запросу.
Для клиента подключаем необходимые адресные пространства:
Больше для работы не требуется. В функции Main клиента пишем функционал для подключения к серверу:
В Main все просто. Главное в нем - функция SendMessageFromSocket(host). Функция принимает в качестве аргумента имя хоста или ip-адрес:
Первые три строки уже должны быть понятны: преобразовываем имя хоста, устанавливаем конечную точку подключения. Обратите внимание на AddressFamily. Метод InterNetwork означает использовать IP-адрес версии 4. Там же можно указать ATM, AppleTalk,Chaos и пр. Материал тут:
Ссылка скрыта от гостей
Сам метод AddressFamily возвращает семейство адресов IP-адреса.
Далее готовим буфер под принимаемые данные и соединяемся с удаленной точкой:
После того, как соединение установлено, нужно как-то послать команду серверу. Делаем это таким образом:
3 строка - проверка на пустую строку или Null. Наша команда это string message. Чтобы его отправить, его нужно преобразовать в байты - применим Encoding.
И дальше снова магия:
Отправляем команды, поддерживаемые сервером и получаем ответ.
Теперь оформим блок для приема двух файлов: скриншота и лог-файла с сервера.
Чуть позже напишем функцию SocketReceive, принимающая в качестве параметров Socket клиента и выходной файл. Желающие могу дописать функцию для сохранения в произвольное имя файла.
Чтобы клиент не закрывался после каждой введенной команды, используем переход в начало функции. Теоретически можно объявить метку и делать goto.
Обратите внимание, в серверной части нет функций для закрытия сокета, чтобы мы могли подключаться к серверу постоянно, если на клиенте вдруг произойдет сбой, либо мы прервем его выполнение (клиент можно прервать по CTRL+Z. В этом случае сервер напишет у себя в логе Bye!, а клиент завершиться с выводом строки "Передача завершена. Завершите соединение вручную")
Осталось малое - написать функцию SocketReceive, для приема файлов.
На вход подаем сокет и имя файла, создается буфер под принятые данные.
После подготовки к приему, сообщаем что мы готовы и открываем BinaryWriter для записи потока битов напрямую в файл на клиенте.
int receivedBytesLen = sender.Receive(clientData) - принимает данные от сервера.
Файлы сохраняются там же, где лежит клиент.
После приема сообщаем об этом и закрываем поток BinaryWriter.
На этом шаге написание клиента завершено. Осталось написать отдельный инструмент для работы с шифрованным логом, расшифровать его, проанализировать, восстановить все нажатия.
Но об этом - в следующей статье.
Вложения
Последнее редактирование модератором: