- PVSM.RU - https://www.pvsm.ru -
TCP-стеганография [1] не является чем-то принципиально новым, например Джон Торакис [2] в 2016 году реализовывал на Питоне довольно [3] интересные [4] вещи [5], жаль не все они есть в открытом доступе. Не была она принципиально новой и на момент написания статей Торакисом. Вот пост на Хабре [6] 2009 года, описывающий идею и, например, программа Covert_TCP [7] далекого (а может и не очень) 1996 года, написанная на чистом Си и реализующая довольно тонкие настройки.
Если Covert TCP предлагает передавать по одному байту полезной нагрузки в TCP пакете, Торакис использовал от 2 до 6 байт на пакет и предложил идею создания протокола в протоколе. Но даже таким образом затруднительно передать большие массивы данных.
Тут на помощь может прийти система одноразовых сообщений [8]. Действительно, почему бы не объединить 2 подхода, раз они изобретены до нас?
Итак, действующие лица — машина 1 (отправитель) и машина 2 (получатель). Требуется автоматизировать следующие действия:
Начнем с середины — с внедрения полезной нагрузки.
Пакет, в который мы будем внедрять (секретные, но не зашифрованные) биты, состоит из IP-заголовка и TCP-загловка.
Заголовок IPv4.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Стандарт RFC 791 [10] содержит следующее:
Identification: 16 bits
An identifying value assigned by the sender to aid in assembling the
fragments of a datagram.
Поле ID нужно для того, чтобы собрать упорядоченные фрагменты данных. Т.е. в случае единственного пакета это поле не используется. Значит для наших целей уже можно использовать 2 байта. Экспериментально установлено, что нулевое поле ID автоматически заполняется случайными значениями, наверное это тоже где-то написано.
Рассмотрим TCP заголовок.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Поле Sequence Number первого пакета соединения не является фиксированным, потому что иначе его бы могли знать злоумышленники (RCF 793 [11]). А это еще 4 байта.
Disclaimer: да, глобальные переменные — некрасиво и айайай, но для небольших тестовых программ, как мне кажется, приемлемы.
Код в этом разделе будет на чистом Си и ориентирован на linux-системы, т.к. в них требуется меньше неочевидных шагов чтобы заставить программы работать (и никаких winpcap). Все программы, использующие "сырые" сокеты следует запускать из-под рута.
Спасибо сайту binarytides.com [12] за разобранные примеры с отправкой пакетов [13], а также готовый сниффер [14].
#include<stdio.h> //for printf
#include<string.h> //memset
#include<sys/socket.h> //for socket ofcourse
#include<stdlib.h> //for exit(0);
#include<errno.h> //For errno - the error number
#include<netinet/tcp.h> //Provides declarations for tcp header
#include<netinet/ip.h> //Provides declarations for ip header
#include <unistd.h>
// 96 бит (12 байт) псевдо-заголовок, нужный для вычисления хэш-суммы
struct pseudo_header {
u_int32_t source_address;
u_int32_t dest_address;
u_int8_t placeholder;
u_int8_t protocol;
u_int16_t tcp_length;
};
// функция вычисление хэш-суммы
unsigned short csum(unsigned short *ptr,int nbytes) {
register long sum;
unsigned short oddbyte;
register short answer;
sum=0;
while(nbytes>1) {
sum+=*ptr++;
nbytes-=2;
}
if(nbytes==1) {
oddbyte=0;
*((u_char*)&oddbyte)=*(u_char*)ptr;
sum+=oddbyte;
}
sum = (sum>>16)+(sum & 0xffff);
sum = sum + (sum>>16);
answer=(short)~sum;
return(answer);
}
int main (int argc, char* argv[]) {
srand(time(NULL));
if (argc < 3) {
puts("Enter source and destination ip");
return 1;
}
while (1) {
puts("Enter payload:");
char payload[1024];
// считывание строки
fgets(payload, 1024, stdin);
// получение длины строки
int length = strlen(payload);
// нуль-терминирование строки
if (length > 0 && payload[strlen (payload) - 1] == 'n')
payload[strlen (payload) - 1] = '';
// завершение при отсутствии полезной нагрузки
if (!length)
break;
// вычисление необходимого количества пакетов
int n = (length + 5)/6;
int i;
for (i = 0; i < n; ++i) {
// задержка между отправлением пакетов
usleep(10000);
// Создание сокета в RAW режиме
int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
if(s == -1) {
// Создание сокета закончилось ошибкой
// вероятно, из-за отсутствия привилегий
perror("Failed to create socket");
exit(1);
}
// побитовове представление пакета
char datagram[4096] , source_ip[32] , *pseudogram;
// инициализация нулями
memset (datagram, 0, 4096);
// IP заголовок
struct iphdr *iph = (struct iphdr *) datagram;
//TCP заголовок
struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
struct sockaddr_in sin;
struct pseudo_header psh;
//some address resolution
strcpy(source_ip , argv[1]);
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr.s_addr = inet_addr (argv[2]);
// Заполнение IP заголовка
// Минимальная корректная длина 5
iph->ihl = 5;
// IPv3
iph->version = 4;
// приоритет не важен
iph->tos = 0;
// вычисление общей длины пакета
iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr);
// первая часть полезной нагрузки
iph->id = (6*i < length ? payload[6*i] << 8 : 0)
+ (6*i + 1 < length ? payload[6*i + 1] : 0);
// Если id == 0, он заменяется на случайное значение, что нам не выгодно
// При присвоении ему единицы, первая половина останется нулевой,
// что соответствует концу строки
if (iph->id == 0)
iph->id = 1;
// Первый фрагмент => нулевое смещение
iph->frag_off = 0;
// Стандартный во многих случаях TTL
iph->ttl = 64;
// Протокол TCP
iph->protocol = IPPROTO_TCP;
// Выставление нуля перед вычислением хэш-суммы
iph->check = 0;
// Исходящий IP
iph->saddr = inet_addr ( source_ip );
// IP точки назначения
iph->daddr = sin.sin_addr.s_addr;
// Вычисление хэша IP заголовка
iph->check = csum ((unsigned short *) datagram, iph->tot_len);
// Заголовок TCP
// порт источника
tcph->source = htons (20);
// порт цели
tcph->dest = htons (rand() % 10000); // "сканируем" разные порты
tcph->ack_seq = 0;
// Вторая часть полезной нагрузки
tcph->seq = 0;
int j;
for (j = 0; j < 4; ++j)
tcph->seq += (6*i + 2 + j < length ? payload[6*i + 2 + j] : 0) << 8*j;
// сдвиг равен размеру заголовка
tcph->doff = 5;
// Из флагов интересует только SYN
tcph->fin=0;
tcph->syn=1;
tcph->rst=0;
tcph->psh=0;
tcph->ack=0;
tcph->urg=0;
// максимальный размер окна
tcph->window = htons (5840);
// хэш-сумма будет заполнена при помощи псевдо-заголовка
tcph->check = 0;
// нулевая "важность"
tcph->urg_ptr = 0;
// Подготовка к вычислению хэша
psh.source_address = inet_addr( source_ip );
psh.dest_address = sin.sin_addr.s_addr;
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = 0;
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr);
pseudogram = (char*)malloc(psize);
memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
memcpy(pseudogram + sizeof(struct pseudo_header) , tcph,
sizeof(struct tcphdr));
tcph->check = csum( (unsigned short*) pseudogram , psize);
free(pseudogram);
//IP_HDRINCL чтобы сказать ядру, что заголовки включены в пакет
int one = 1;
const int *val = &one;
if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
{
perror("Error setting IP_HDRINCL");
exit(0);
}
// Отправка пакета
if (sendto (s, datagram, iph->tot_len , 0, (struct sockaddr *) &sin, sizeof (sin)) < 0)
{
perror("sendto failed");
}
// Успех
else
{
// информация об отправленном пакете
printf ("Packet sent. "" );
for (j = 0; j < 6; ++j)
if (6*i + j < length)
printf("%c", payload[6*i + j]);
puts(""");
}
}
}
return 0;
}
Для удобства проверки будем передавать сообщения, введенные с клавиатуры.
// считывание строки
fgets(payload, 1024, stdin);
// получение длины строки
int length = strlen(payload);
Всего имеется 6 байт, поэтому количество требующихся пакетов равно длине, деленной на 6 и округленной в б_о_льшую сторону.
// вычисление необходимого количества пакетов
int n = (length + 5)/6;
Дальнейшие действия выполняются n раз.
Создание TCP пакета в "сыром" режиме.
int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
Создание необходимых переменных, заполнение полей.
// побитовове представление пакета
char datagram[4096] , source_ip[32] , *pseudogram;
// инициализация нулями
memset (datagram, 0, 4096);
// IP заголовок
struct iphdr *iph = (struct iphdr *) datagram;
//TCP заголовок
struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
Внедрение первой части полезной нагрузки (2 байта в поле ID).
iph->id = (6*i < length ? payload[6*i] << 8 : 0)
+ (6*i + 1 < length ? payload[6*i + 1] : 0);
Внедрение второй части полезной нагрузки (4 байта в поле Sequence Number).
tcph->seq = 0;
int j;
for (j = 0; j < 4; ++j)
tcph->seq += (6*i + 2 + j < length ? payload[6*i + 2 + j] : 0) << 8*j;
Отправка пакета и вывод его полезной нагрузки.
if (sendto (s, datagram, iph->tot_len , 0, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror("sendto failed");
}
// Успех
else {
// информация об отправленном пакете
printf ("Packet sent. "" );
for (j = 0; j < 6; ++j)
if (6*i + j < length)
printf("%c", payload[6*i + j]);
puts(""");
}
#include<stdio.h> //For standard things
#include<stdlib.h> //malloc
#include<string.h> //memset
#include<netinet/ip_icmp.h> //Provides declarations for icmp header
#include<netinet/udp.h> //Provides declarations for udp header
#include<netinet/tcp.h> //Provides declarations for tcp header
#include<netinet/ip.h> //Provides declarations for ip header
#include<sys/socket.h>
#include<arpa/inet.h>
void processPacket(unsigned char*);
void handleMessage(unsigned char*);
int sock_raw;
// буфер для хранения сообщения
char global_buffer[1024];
// текущий размер сообщения
int global_n = 0;
char * src_addr, *dst_addr;
int main(int argc, char* argv[]) {
if (argc < 3) {
puts("Enter source and destination ip");
return 1;
}
src_addr = argv[1];
dst_addr = argv[2];
int saddr_size , data_size;
struct sockaddr saddr;
unsigned char *buffer = (unsigned char *)malloc(65536); //Its Big!
puts("Starting...");
// Создание "сырого" сокета, который будет прослушивать
sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP);
if(sock_raw < 0) {
printf("Socket Errorn");
return 1;
}
while(1) {
saddr_size = sizeof saddr;
// Получение пакета
data_size = recvfrom(sock_raw , buffer , 65536 , 0 , &saddr , &saddr_size);
if(data_size <0 )
{
printf("Recvfrom error , failed to get packetsn");
return 1;
}
// Обработка пакета
processPacket(buffer);
}
close(sock_raw);
printf("Finished");
return 0;
}
void processPacket(unsigned char* buffer) {
// Получение указателя на IP заголовок пакета
struct iphdr *iph = (struct iphdr*)buffer;
// Если получен TCP пакет
if (iph->protocol == IPPROTO_TCP) {
// обработка сообщения
handleMessage(buffer);
}
}
void handleMessage(unsigned char *Buffer) {
int i;
struct iphdr *iph = (struct iphdr *)Buffer;
struct tcphdr *tcph = (struct tcphdr *) (Buffer + sizeof (struct ip));
struct sockaddr_in source,dest;
// получение адресов цели и источника
memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = iph->saddr;
memset(&dest, 0, sizeof(dest));
dest.sin_addr.s_addr = iph->daddr;
// если адрес источника совпадает с требуемым
if (source.sin_addr.s_addr == inet_addr(src_addr)
&& dest.sin_addr.s_addr == inet_addr(dst_addr)) {
// выбор потока вывода
setvbuf (stdout, NULL, _IONBF, 0);
// массив с полезной нагрузкой
char payload[6];
// извлечение первой часли полезной нагрузки
payload[0] = iph->id >> 8;
payload[1] = iph->id & ((1 << 8) - 1);
//извлечение второй части полезной нагрузки
for (i = 0; i < 4; ++i) {
payload[i + 2] = (tcph->seq >> i*8) & ((1 << 8 ) - 1);
}
// копирование глобальный буфер
for (i = 0; i < 6; ++i) {
global_buffer[global_n++] = payload[i];
}
// вывод полученной части сообщения
for (i = 0; i < 6; ++i) {
// проверка на окончание
if (payload[i])
printf("%c", payload[i]);
else {
// 0 => конец сообщения
// перенос строки
puts("");
// сброс текущей длины сообщения
global_n = 0;
// выход из цикла
break;
}
}
}
}
Чтобы не нагружать код лишними передачами переменных, сообщение и его длину будем контролировать в глобальных переменных. На текущем этапе записывать сообщение не обязательно, но почему бы не позаботиться о его хранении заранее.
// буфер для хранения сообщения
char global_buffer[1024];
// текущий размер сообщения
int global_n = 0;
Создаем "сырой" сокет для прослушивания и получаем пакеты в бесконечном цикле, с последующим вызовом функции обработки пакета.
sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP);
// ...
while(1) {
// ...
// получение пакета
recvfrom(sock_raw , buffer , 65536 , 0 , &saddr , &saddr_size);
//...
// Обработка пакета
processPacket(buffer);
}
После проверки на то, что был получен именно TCP пакет и именно с нужного адреса, извлекаем обе части полезной нагрузки.
// ...
char payload[6];
// извлечение первой часли полезной нагрузки
payload[0] = iph->id >> 8;
payload[1] = iph->id & ((1 << 8) - 1);
//извлечение второй части полезной нагрузки
for (i = 0; i < 4; ++i)
payload[i + 2] = (tcph->seq >> i*8) & ((1 << 8 ) - 1);
// ...
(слева Получатель, справа отправитель)
Нагрузка отлично видна при анализе пакета, например, в Wireshark. Так что было бы неплохо внести шифрование, но для Proof Of Consept мы этого делать не будем.
Итак, до сих пор мы повторили успех Торакиса из его первой статьи [3], но используя Си, а не Питон. Пришло время для расхождений.
Хотелось бы передавать большие массивы данных в TCP пакетах, но это неудобно, так как стеганографическая скорость передачи данных достаточно мала. Однако никто не мешает оставлять сообщение в оговоренном месте, а таким местом может быть либо созданный специально для сегодняшнего эксперимента сайт, либо один [15] из [9] существующих [16].
Тут дела обстоят несколько сложнее, чем в прошлый раз, потому что придется дополнить готовые приложения загрузкой страниц через https и их парсингом. Т.к. мы используем конкретный сайт, "парсинг" будет упрощен до нахождения нужной строчки в коде страницы и извлечения оттуда кода для ссылки по известным соседним символам.
Для начала понадобится библиотека curl. По умолчанию она отправляет полученную веб-страницу в консоль, поэтому необходимо сделать буфер, в который мы хотим записывать исходный код страницы и callback функцию, соответствующую API curl, производящую ту самую запись.
char html_buffer[65530];
size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
memcpy(html_buffer, contents, realsize);
return realsize;
}
#include<stdio.h> //for printf
#include<string.h> //memset
#include<sys/socket.h> //for socket ofcourse
#include<stdlib.h> //for exit(0);
#include<errno.h> //For errno - the error number
#include<netinet/tcp.h> //Provides declarations for tcp header
#include<netinet/ip.h> //Provides declarations for ip header
#include <unistd.h>
#include <curl/curl.h>
// 96 бит (12 байт) псевдо-заголовок, нужный для вычисления хэш-суммы
struct pseudo_header {
u_int32_t source_address;
u_int32_t dest_address;
u_int8_t placeholder;
u_int8_t protocol;
u_int16_t tcp_length;
};
unsigned short csum(unsigned short *ptr,int nbytes);
size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp);
char * findMessage();
const char post_parameter[] = "message%5Bbody%5D=";
char html_buffer[65530];
int main (int argc, char* argv[]) {
srand(time(NULL));
if (argc < 3) {
puts("Enter source and destination ip");
return 1;
}
while (1) {
puts("Enter payload:");
// массив, содержащий переменную POST-запроса с сообщением
char post[65530];
memset(post, 0, sizeof(post));
memcpy(post, post_parameter, sizeof(post_parameter)*sizeof(char));
// считывание строки со смещением от начала
fgets(post + sizeof(post_parameter) - 1, sizeof(post) - sizeof(post_parameter), stdin);
// нуль-терминирование строки
post[strlen (post) - 1] = '';
puts(post);
// переменные curl
CURL *curl;
CURLcode res;
// инициализация xurl
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
// сайт с одноразовыми сообщениями
curl_easy_setopt(curl, CURLOPT_URL, "https://tmwsd.ws/messages");
// передаем сообщение "test"
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);
// разрешаем перенаправление
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
// выбор нашей функции записи
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
// выполнение запроса
res = curl_easy_perform(curl);
// /* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %sn",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_global_cleanup();
char *link = findMessage();
puts("Link:");
puts(link);
// получение длины строки
int length = strlen(link);
// нуль-терминирование строки
// завершение при отсутствии полезной нагрузки
if (!length)
break;
// вычисление необходимого количества пакетов
int n = (length + 5)/6;
int i;
for (i = 0; i < n; ++i) {
// задержка между отправлением пакетов
usleep(10000);
// Создание сокета в RAW режиме
// AF_INTER == PF_INER - IP v4
int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
if(s == -1) {
// Создание сокета закончилось ошибкой
// вероятно, из-за отсутствия привилегий
perror("Failed to create socket");
exit(1);
}
// побитовове представление пакета
char datagram[4096] , source_ip[32] , *pseudogram;
// инициализация нулями
memset (datagram, 0, 4096);
// IP заголовок
struct iphdr *iph = (struct iphdr *) datagram;
//TCP заголовок
struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
struct sockaddr_in sin;
struct pseudo_header psh;
//some address resolution
strcpy(source_ip , argv[1]);
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr.s_addr = inet_addr (argv[2]);
// Заполнение IP заголовка
// Минимальная корректная длина 5
iph->ihl = 5;
// IPv3
iph->version = 4;
// приоритет не важен
iph->tos = 0;
// вычисление общей длины пакета
iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr);
// первая часть полезной нагрузки
iph->id = (6*i < length ? link[6*i] << 8 : 0)
+ (6*i + 1 < length ? link[6*i + 1] : 0);
// Если id == 0, он заменяеся на случайной значение
// При присвоении ему единицы, первая половина останется нулевой,
// что соответствует концу строки
if (iph->id == 0)
iph->id = 1;
// Первый фрагмент => нулевое смещение
iph->frag_off = 0;
// Стандартный во многих случаях TTL
iph->ttl = 64;
// Протокол TCP
iph->protocol = IPPROTO_TCP;
// Выставление нуля перед вычислением хэш-суммы
iph->check = 0;
// Исходящий IP
iph->saddr = inet_addr ( source_ip );
// IP точки назначения
iph->daddr = sin.sin_addr.s_addr;
// Вычисление хэша IP заголовка
iph->check = csum ((unsigned short *) datagram, iph->tot_len);
// Заголовок TCP
// порт источника
tcph->source = htons (20);
// порт цели
tcph->dest = htons (rand() % 10000); // "сканируем" разные порты
tcph->ack_seq = 0;
// Вторая часть полезной нагрузки
tcph->seq = 0;
int j;
for (j = 0; j < 4; ++j)
tcph->seq += (6*i + 2 + j < length ? link[6*i + 2 + j] : 0) << 8*j;
// сдвиг равен размеру заголовка
tcph->doff = 5;
// Из флагов интересует только SYN
tcph->fin=0;
tcph->syn=1;
tcph->rst=0;
tcph->psh=0;
tcph->ack=0;
tcph->urg=0;
// максимальный размер окна
tcph->window = htons (5840);
// хэш-сумма будет заполнена при помощи псевдо-заголовка
tcph->check = 0;
// нулевая "важность"
tcph->urg_ptr = 0;
// Подготовка к вычислению хэша
psh.source_address = inet_addr( source_ip );
psh.dest_address = sin.sin_addr.s_addr;
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = 0;
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr);
pseudogram = (char*)malloc(psize);
memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
memcpy(pseudogram + sizeof(struct pseudo_header) , tcph,
sizeof(struct tcphdr));
tcph->check = csum( (unsigned short*) pseudogram , psize);
free(pseudogram);
//IP_HDRINCL чтобы сказать ядру, что заголовки включены в пакет
int one = 1;
const int *val = &one;
if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0) {
perror("Error setting IP_HDRINCL");
exit(0);
}
// Отправка пакета
if (sendto (s, datagram, iph->tot_len , 0, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror("sendto failed");
}
// Успех
else {
// информация об отправленном пакете
printf ("Packet sent. "" );
for (j = 0; j < 6; ++j)
if (6*i + j < length)
printf("%c", link[6*i + j]);
puts(""");
}
}
free(link);
}
return 0;
}
// функция вычисление хэш-суммы
unsigned short csum(unsigned short *ptr,int nbytes) {
register long sum;
unsigned short oddbyte;
register short answer;
sum=0;
while(nbytes>1) {
sum+=*ptr++;
nbytes-=2;
}
if(nbytes==1) {
oddbyte=0;
*((u_char*)&oddbyte)=*(u_char*)ptr;
sum+=oddbyte;
}
sum = (sum>>16)+(sum & 0xffff);
sum = sum + (sum>>16);
answer=(short)~sum;
return(answer);
}
size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
memcpy(html_buffer, contents, realsize);
return realsize;
}
char * findMessage() {
// нужна 75-я строка
size_t i, j = 0;
for (i = 0; i < 74; ++i) {
while (html_buffer[j++] != 'n');
}
while (html_buffer[j++] != 'w');
int first, last;
first = j+2;
while (html_buffer[++j] != '<');
last = j;
char * link = malloc(sizeof(char)*(last - first + 1));
memset(link, 0, last - first + 1);
memcpy(link, html_buffer + first, last - first);
return link;
}
Чтобы создать сообщение, необходимо послать на https://tmwsd.ws/messages [17] (или любой другой из их доменных имен) POST-запрос с, как минимум, заданной переменной message[body]
, которая и будет являться нашим сообщением. В результате сайт перенаправит на страницу, содержащую одноразувую ссылку.
Пример страницы со ссылкой.
Видим оставленное сообщение "test" и предупреждение о том, что сообщение удалится.
Если снова попытать счастья и перейти по той же ссылке, сообщение увидеть не удастся.
Из всей страницы нас интересует только адрес, т.е. в данном случае https://⌫.ws/4Gn30zLh
, а т.к. доменное имя заранее оговорено, необходим только набор 4Gn30zLh
. В исходном коде страницы он лежит на 75 строке в таком виде:
<span id="message_url_366056">https://⌫.ws/4Gn30zLh</span>
При чем число 366056 не является фиксированным. Итак можно сначала перейти на 75 строку, затем дойти до символа 'w', и начиная со смещения от него на 2 вправо до следующей угловой скобки получить сообщение
char * findLink() {
// нужна 74 строка
size_t i, j = 0;
for (i = 0; i < 74; ++i) {
while (html_buffer[j++] != 'n');
}
while (html_buffer[j++] != 'w');
int first, last;
first = j+2;
while (html_buffer[++j] != '<');
last = j;
char * msg = malloc(sizeof(char)*(last-first));
for (i = first; i < last; ++i)
msg[i - first] = html_buffer[i];
return msg;
}
Автор не разбирается в вебе Что если попробоавать сломать алгоритм, отправив символ <
? А ничего не произойдет, в исходном тексте страницы он будет в виде html кода <
.
// переменные curl
CURL *curl;
CURLcode res;
// инициализация curl
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
// сайт с одноразовыми сообщениями
curl_easy_setopt(curl, CURLOPT_URL, "https://tmwsd.ws/messages");
// передаем сообщение "test"
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "message%5Bbody%5D=test");
// разрешаем перенаправление
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
// выбор нашей функции записи
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
// выполнение запроса
res = curl_easy_perform(curl);
После парсинга получаем рабочий адрес.
Именно его надо будет передавать в TCP пакетах.
Для начала отведем переменную под постоянную часть POST-запроса.
const char post_parameter[] = "message%5Bbody%5D=";
В массив, содержащий POST-запрос копируется постоянная часть, а затем со смещением считывается сообщение.
char post[65530];
memset(post, 0, sizeof(post));
memcpy(post, post_parameter, sizeof(post_parameter)*sizeof(char));
// считывание строки со смещением от начала
fgets(post + sizeof(post_parameter) - 1, sizeof(post) - sizeof(post_parameter), stdin);
Далее выполняются уже известные манипуляции с curl и TCP пакетами.
В результате можем передавать любое сообщение при помощи двух пакетов.
Проверяем сообщение по ссылке.
Осталось научиться принимать сообщения!
Нагрузку извлекать умеем, html подгружать умеем, а сообщение находится на 31 строке.
Все нюансы аналогичной реализации были разобраны. Дабы лишний раз не читать одно и то же, предлагаю желающим ознакомиться с кодом, а остальным перейти к "проверке связи".
#include<stdio.h> //for printf
#include<string.h> //memset
#include<sys/socket.h> //for socket ofcourse
#include<stdlib.h> //for exit(0);
#include<errno.h> //For errno - the error number
#include<netinet/tcp.h> //Provides declarations for tcp header
#include<netinet/ip.h> //Provides declarations for ip header
#include <unistd.h>
#include <curl/curl.h>
// 96 бит (12 байт) псевдо-заголовок, нужный для вычисления хэш-суммы
struct pseudo_header {
u_int32_t source_address;
u_int32_t dest_address;
u_int8_t placeholder;
u_int8_t protocol;
u_int16_t tcp_length;
};
unsigned short csum(unsigned short *ptr,int nbytes);
size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp);
char * findMessage();
const char post_parameter[] = "message%5Bbody%5D=";
char html_buffer[65530];
int main (int argc, char* argv[]) {
srand(time(NULL));
if (argc < 3) {
puts("Enter source and destination ip");
return 1;
}
while (1) {
puts("Enter payload:");
// массив, содержащий переменную POST-запроса с сообщением
char post[65530];
memset(post, 0, sizeof(post));
memcpy(post, post_parameter, sizeof(post_parameter)*sizeof(char));
// считывание строки со смещением от начала
fgets(post + sizeof(post_parameter) - 1, sizeof(post) - sizeof(post_parameter), stdin);
// нуль-терминирование строки
post[strlen (post) - 1] = '';
puts(post);
// переменные curl
CURL *curl;
CURLcode res;
// инициализация xurl
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
// сайт с одноразовыми сообщениями
curl_easy_setopt(curl, CURLOPT_URL, "https://tmwsd.ws/messages");
// передаем сообщение "test"
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);
// разрешаем перенаправление
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
// выбор нашей функции записи
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
// выполнение запроса
res = curl_easy_perform(curl);
// /* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %sn",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_global_cleanup();
char *link = findMessage();
puts("Link:");
puts(link);
// получение длины строки
int length = strlen(link);
// нуль-терминирование строки
// завершение при отсутствии полезной нагрузки
if (!length)
break;
// вычисление необходимого количества пакетов
int n = (length + 5)/6;
int i;
for (i = 0; i < n; ++i) {
// задержка между отправлением пакетов
usleep(10000);
// Создание сокета в RAW режиме
// AF_INTER == PF_INER - IP v4
int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
if(s == -1) {
// Создание сокета закончилось ошибкой
// вероятно, из-за отсутствия привилегий
perror("Failed to create socket");
exit(1);
}
// побитовове представление пакета
char datagram[4096] , source_ip[32] , *pseudogram;
// инициализация нулями
memset (datagram, 0, 4096);
// IP заголовок
struct iphdr *iph = (struct iphdr *) datagram;
//TCP заголовок
struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
struct sockaddr_in sin;
struct pseudo_header psh;
//some address resolution
strcpy(source_ip , argv[1]);
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr.s_addr = inet_addr (argv[2]);
// Заполнение IP заголовка
// Минимальная корректная длина 5
iph->ihl = 5;
// IPv3
iph->version = 4;
// приоритет не важен
iph->tos = 0;
// вычисление общей длины пакета
iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr);
// первая часть полезной нагрузки
iph->id = (6*i < length ? link[6*i] << 8 : 0)
+ (6*i + 1 < length ? link[6*i + 1] : 0);
// Если id == 0, он заменяеся на случайной значение
// При присвоении ему единицы, первая половина останется нулевой,
// что соответствует концу строки
if (iph->id == 0)
iph->id = 1;
// Первый фрагмент => нулевое смещение
iph->frag_off = 0;
// Стандартный во многих случаях TTL
iph->ttl = 64;
// Протокол TCP
iph->protocol = IPPROTO_TCP;
// Выставление нуля перед вычислением хэш-суммы
iph->check = 0;
// Исходящий IP
iph->saddr = inet_addr ( source_ip );
// IP точки назначения
iph->daddr = sin.sin_addr.s_addr;
// Вычисление хэша IP заголовка
iph->check = csum ((unsigned short *) datagram, iph->tot_len);
// Заголовок TCP
// порт источника
tcph->source = htons (20);
// порт цели
tcph->dest = htons (rand() % 10000); // "сканируем" разные порты
tcph->ack_seq = 0;
// Вторая часть полезной нагрузки
tcph->seq = 0;
int j;
for (j = 0; j < 4; ++j)
tcph->seq += (6*i + 2 + j < length ? link[6*i + 2 + j] : 0) << 8*j;
// сдвиг равен размеру заголовка
tcph->doff = 5;
// Из флагов интересует только SYN
tcph->fin=0;
tcph->syn=1;
tcph->rst=0;
tcph->psh=0;
tcph->ack=0;
tcph->urg=0;
// максимальный размер окна
tcph->window = htons (5840);
// хэш-сумма будет заполнена при помощи псевдо-заголовка
tcph->check = 0;
// нулевая "важность"
tcph->urg_ptr = 0;
// Подготовка к вычислению хэша
psh.source_address = inet_addr( source_ip );
psh.dest_address = sin.sin_addr.s_addr;
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = 0;
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr);
pseudogram = (char*)malloc(psize);
memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
memcpy(pseudogram + sizeof(struct pseudo_header) , tcph,
sizeof(struct tcphdr));
tcph->check = csum( (unsigned short*) pseudogram , psize);
free(pseudogram);
//IP_HDRINCL чтобы сказать ядру, что заголовки включены в пакет
int one = 1;
const int *val = &one;
if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0) {
perror("Error setting IP_HDRINCL");
exit(0);
}
// Отправка пакета
if (sendto (s, datagram, iph->tot_len , 0, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror("sendto failed");
}
// Успех
else {
// информация об отправленном пакете
printf ("Packet sent. "" );
for (j = 0; j < 6; ++j)
if (6*i + j < length)
printf("%c", link[6*i + j]);
puts(""");
}
}
free(link);
}
return 0;
}
// функция вычисление хэш-суммы
unsigned short csum(unsigned short *ptr,int nbytes) {
register long sum;
unsigned short oddbyte;
register short answer;
sum=0;
while(nbytes>1) {
sum+=*ptr++;
nbytes-=2;
}
if(nbytes==1) {
oddbyte=0;
*((u_char*)&oddbyte)=*(u_char*)ptr;
sum+=oddbyte;
}
sum = (sum>>16)+(sum & 0xffff);
sum = sum + (sum>>16);
answer=(short)~sum;
return(answer);
}
size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
memcpy(html_buffer, contents, realsize);
return realsize;
}
char * findMessage() {
// нужна 75-я строка
size_t i, j = 0;
for (i = 0; i < 74; ++i) {
while (html_buffer[j++] != 'n');
}
while (html_buffer[j++] != 'w');
int first, last;
first = j+2;
while (html_buffer[++j] != '<');
last = j;
char * link = malloc(sizeof(char)*(last - first + 1));
memset(link, 0, last - first + 1);
memcpy(link, html_buffer + first, last - first);
return link;
}
Итак, отправитель отправляет, получатель получает и загружает, все идет по плану, хотя на самом деле не были рассмотрены некоторые исключения.
Желающие потестировать, ничего не компилируя, могут скачать виртуалку (гугл драйв [18], ЯД [19]).
Благодарю хаброюзера PavelMSTU [20] за помощь в исслеловании.
Автор: scidev
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/260349
Ссылки в тексте:
[1] стеганография: https://habrahabr.ru/post/253045/
[2] Джон Торакис: https://github.com/operatorequals
[3] довольно: https://securosophy.com/2016/09/14/teaching-an-old-dog-not-that-new-tricks-stego-in-tcpip-made-easy-part-1/
[4] интересные: https://securosophy.com/2016/09/19/pozzo-lucky-stego-in-tcpip-part-2/
[5] вещи: https://securosophy.com/2016/09/28/pozzo-lucky-busted-the-tales-of-a-mathematician-soc-analyst/
[6] пост на Хабре: https://habrahabr.ru/post/60726/
[7] Covert_TCP: http://www-scf.usc.edu/~csci530l/downloads/covert_tcp.c
[8] система одноразовых сообщений: https://habrahabr.ru/post/132153/
[9] One-Time Secret: https://onetimesecret.com/
[10] RFC 791: https://tools.ietf.org/html/rfc791#page-13
[11] RCF 793: https://tools.ietf.org/html/rfc793#page-27
[12] binarytides.com: http://www.binarytides.com
[13] отправкой пакетов: http://www.binarytides.com/raw-sockets-c-code-linux/
[14] готовый сниффер: http://www.binarytides.com/packet-sniffer-code-c-linux/
[15] один: https://www.thismessagewillselfdestruct.com
[16] существующих: https://privnote.com/
[17] https://tmwsd.ws/messages: https://tmwsd.ws/messages
[18] гугл драйв: https://drive.google.com/drive/folders/0B8hGB0dDldqKbWpkdExJbmVfS2s?usp=sharing
[19] ЯД: https://yadi.sk/d/Hu2k_znl3L2JxS
[20] PavelMSTU: https://habrahabr.ru/users/pavelmstu/
[21] Источник: https://habrahabr.ru/post/332962/
Нажмите здесь для печати.