D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор: tenfield
Эксклюзивно для форума: xss.is
Введение
Привет любителям Golang. Привет любителям Python. Сегодня поговорим о строках в бинарнике Го.
Попробуем усложнить анализ бинарника. Напишем небольшой скрипт на питоне, который можно вызывать перед сборкой. Проведем повторную сборку и анализ.
Содержание
- Go. Демо проект
- Анализ бинарника
- Схема работы обфускатора
- Пишем обфускатор на python
- Запуск обфускатора
- Go. Сборка проекта
- Повторный анализ бинарника
- Выводы
Go. Демо проект
Создадим папку project. Далее работать будем только в этой директории. Создадим файл main.go в директории с демо проектом. Функция main состоит из нескольких блоков
- Вызов функции со строковым аргументом
- Переменная со строкой
- массив строк
Каждый блок служит примером ситуации, в котором может потребоваться использовать строки.
Код с оформлением (BB-коды): Скопировать в буфер обмена
И для сборки проекта необходимо создать файл go.mod. Это файл указывающий минимальную версию golang и зависимости проекта.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Собирать будем через "go build . -o main"
параметр "." означает создать билд из файлов текущей директории.
параметр "-o main" означает выход в бинарный файл main.
Запускаем: "./main"
Вывод:
Hello XSS
This is Text
[arr_1 arr_2 arr_3]
Работает точно как ожидалось. Перейдем к анализу бинарного файла
Анализ бинарника
В linux окружении есть программа strings, которая выводит обнаруженные строки из бинарного файла.
Запустим программу:
strings main
На тестовом примере обнаруживаем мусор, строки, пути к нашим .go, имена всех функций, ... Стоп, что?
Первым делом гугл. Находим две библиотеки:
- https://github. com/burrowers/garble
Написана на Go. Развивается, но требует версию исходников Go 22 и выше
- https://github. com/unixpickle/gobfuscate
Последний commit 3 года назад
По указанным причинам не подходит ни один. Неприятно. Но если скрыть только строки, это не сложно сделатьдома самостоятельно. И в результате размышлений появляется подобная схема работы:
Схема работы обфускатора
Если перед билдом скрыть строки, значит строк не будет после билда (c).
Исходник.go -> pre_build.py -> без_строк.go -> go build -> bin_без_строк
И желательно до обработки исходники оставались валидными. Удобно для теста.
Исходник.go -> go build -> bin_c_строками
Если хотим найти строки, сначала надо показать, где эти строки располагаются в текстовом файле. Поэтому, расставим маркеры: В исходниках Go обернем каждую строку в функцию-маркер hide_me.HideMe("Text").
Заглушка расшифровки на Go
Если вы пользуетесь IDE, то лучше сначала создать функцию-заглушку. Пока вы будете проводить замену, заглушка позволит IDE подсказывать вам, вместо подчеркивания ошибки несуществующего пакета.
И сразу вынесем код функции в отдельный пакет hide_me. Это позволит импортировать код функции из других пакетов и проект находится в чистоте.
В рабочем проекте создаем папку hide_me и внутри файл hide_me.go
Временно создаем функцию-маркер-заглушку HideMe() которая просто вернет текст.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Расшифровка строк на Go
Теперь нужно написать настоящую функцию, вместо функции-заглушки из предыдущей главы. Этот файл будет вставлен в файл hide_me/hide_me.go перед сборкой.
Функция собирает обратно строку из байтов. Эту функцию необходимо написать совместимо с ответной Python частью (функцией hide_arg())
Алгоритм может быть любой. Пример DES, chacha20, salsa, fernet. Для примера XOR с фиксированным ключом.
И для примера получаем следующую функцию:
Выполняем операцию XOR для каждого байта из входного массива. Результат приводим к строке и возвращаем
Код с оформлением (BB-коды): Скопировать в буфер обмена
Маркеры
Далее предстоит весьма длительная однообразная часть:
В каждом файле проекта заменяем строковые аргументы на результат вызова функции hide_me.HideMe()
Пример: myFunc("Test") заменяем на myFunc(hide_me.HideMe("Test"))
На примере файла main.go
Код с оформлением (BB-коды): Скопировать в буфер обмена
Теперь код проходит проверки IDE, типы сходятся, и появились маркеры для строк (функция HideMe). Можем продолжить писать на Go, но теперь появилась замечательная возможность скрыть строки перед сборкой. Сделайте build, все должно работать как до изменений. Поздравляем, все позади.
Но, строки еще обнаруживаются через поиск в бинарном файле.
Пишем обфускатор на python
Вспоминаем о схеме работы. Этот скрипт скроет все строки в исходнике.
Модуль ast помогает Python приложениям обрабатывать Python деревья абстрактных синтаксических грамматик.
Проще: Позволяет из python кода редактировать python/Golang/JS/C++/... код.
Попробуем использовать готовый AST. AST обрабатывает сложные конструкции с учетом переносов, скобочек, запятых.
Нашел 2 библиотеки AST Go для Python:
- https://github.com/up9inc/gopygo 56 звезд. Последний commit 3 года назад
- https://github.com/itayg25/goastpy 3 звезды. Последний commit год назад
- goastpy прокси между python и нативным golang AST через C вызовы. Прозвучало страшно, но на деле все просто.
Но это не заработало. Возможно виновата кроссплатформенность, или ошибка в коде, или неправильный билд, или ...
- gopygo написан на чистом python. Работает на SLY (Sly Lex-Yacc), но распарсил только первый элемент из блока команд.
Снова обе не прошли отбор. Будем писать поиск строк сами.
Напишем скрипт pre_build.py. Пусть рекурсивно обходит указанную папку. В каждом файле с расширением '.go', заменяет аргумент у функции hide_me.HideMe.
Пример hide_me.HideMe("any_text") на hide_me.HideMe(<байты>). После этого шага читать исходники станет трудно, но исходники остаются синтаксически корректными.
Python: Скопировать в буфер обмена
Функция hide_arg
Напомню код функции:
Python: Скопировать в буфер обмена
Важно! Эта функция должна быть совместима с hide_me.go
Функция состоит из двух частей. Преобразование очередного символа из строки с помощью XOR и ключом xor_key. Вторая часть собирает строку из result (массива строк)
Станет понятно на примере. input и output - строки,
input: test
output: []byte{1,2,3,4,5,6}
Функция get_arg
Python: Скопировать в буфер обмена
Для получения аргумента функции (строки), предлагается обойтись split вместо AST. Работает, если писать код и помнить как будет работать парсер.
Функция hide_line
Python: Скопировать в буфер обмена
Функция принимающая одну строковую переменную line. line это строка из файла-исходника. Производим замену в строке (переменная line) исходной строки 'HideMe("qwe")' на результат функции hide_arg (описанной
ранее), пример 'HideMe([]byte{1,2,3,....})'
Пример:
input: greeting(hide_me.HideMe("XSS"))
output: greeting(hide_me.HideMe([]byte{ 19, 24, 24 }))
Остальные функции
- hide_file
запускает hide_line для каждой строки в файле. перезаписывает файл
- hide_project
запускает hide_file для каждого файла из папки (рекурсивно) И записывает файл hide_me/hide_me.go с заполненным xor_key и настоящей функцией HideMe
Запуск обфускатора
Внимание! Перед запуском pre_build.py следует копировать проект во временную папку. Иначе потеряете исходники!
Запустим скрипт: python3 main.py
Секунду работает и завершается без ошибки. Посмотрим на новое содержимое main.go файла после обфускации строк.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Замечательно! В исходнике нет строк. Синтаксис правильный, код валидный. IDE не видит проблем. Не думаю что кто-либо захочет редактировать исходники после этого преобразования. Остался последний шаг: запустить сборку проекта.
Go. Сборка проекта
Собираем как раньше: "go build -o main ."
Запускаем: "./main"
И получаем вывод как до модификации проекта:
Hello XSS
This is Text
[arr_1 arr_2 arr_3]
Работает точно как в первый раз, с небольшим отличием: golang в runtime расшифровывает строки. Убедимся в этом в следующей части.
Повторный анализ бинарника
Запускаем поиск через strings как в первый раз. Но в этот раз поиск строк по файлу дает пустой результат.
Кроме строк, в бинарнике находятся имена пакетов, файлов. Этим возможно займемся в следующих статьях.
Выводы
В билдах на Go нужно прятать строки, имена файлов, имена пакетов. Обфускацию можно проводить перед сборкой проекта.
Современные системы сборки позволяют организовывать многоступенчатые сборки. Воспользуйтесь этим. Или запускайте команды руками каждый раз.
Эксклюзивно для форума: xss.is
Введение
Привет любителям Golang. Привет любителям Python. Сегодня поговорим о строках в бинарнике Го.
Попробуем усложнить анализ бинарника. Напишем небольшой скрипт на питоне, который можно вызывать перед сборкой. Проведем повторную сборку и анализ.
Содержание
- Go. Демо проект
- Анализ бинарника
- Схема работы обфускатора
- Пишем обфускатор на python
- Запуск обфускатора
- Go. Сборка проекта
- Повторный анализ бинарника
- Выводы
Go. Демо проект
Создадим папку project. Далее работать будем только в этой директории. Создадим файл main.go в директории с демо проектом. Функция main состоит из нескольких блоков
- Вызов функции со строковым аргументом
- Переменная со строкой
- массив строк
Каждый блок служит примером ситуации, в котором может потребоваться использовать строки.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
// main.go
package test
import (
"fmt"
)
func greeting(name string) {
fmt.Println("Hello", name)
}
func main() {
greeting("XSS")
t := "This is Text"
fmt.Println(t)
arr := []string{
"arr_1",
"arr_2",
"arr_3",
}
fmt.Println(arr)
}
И для сборки проекта необходимо создать файл go.mod. Это файл указывающий минимальную версию golang и зависимости проекта.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
// go.mod
module test
go 1.23
Собирать будем через "go build . -o main"
параметр "." означает создать билд из файлов текущей директории.
параметр "-o main" означает выход в бинарный файл main.
Запускаем: "./main"
Вывод:
Hello XSS
This is Text
[arr_1 arr_2 arr_3]
Работает точно как ожидалось. Перейдем к анализу бинарного файла
Анализ бинарника
В linux окружении есть программа strings, которая выводит обнаруженные строки из бинарного файла.
Запустим программу:
strings main
На тестовом примере обнаруживаем мусор, строки, пути к нашим .go, имена всех функций, ... Стоп, что?
Первым делом гугл. Находим две библиотеки:
- https://github. com/burrowers/garble
Написана на Go. Развивается, но требует версию исходников Go 22 и выше
- https://github. com/unixpickle/gobfuscate
Последний commit 3 года назад
По указанным причинам не подходит ни один. Неприятно. Но если скрыть только строки, это не сложно сделать
Схема работы обфускатора
Если перед билдом скрыть строки, значит строк не будет после билда (c).
Исходник.go -> pre_build.py -> без_строк.go -> go build -> bin_без_строк
И желательно до обработки исходники оставались валидными. Удобно для теста.
Исходник.go -> go build -> bin_c_строками
Если хотим найти строки, сначала надо показать, где эти строки располагаются в текстовом файле. Поэтому, расставим маркеры: В исходниках Go обернем каждую строку в функцию-маркер hide_me.HideMe("Text").
Заглушка расшифровки на Go
Если вы пользуетесь IDE, то лучше сначала создать функцию-заглушку. Пока вы будете проводить замену, заглушка позволит IDE подсказывать вам, вместо подчеркивания ошибки несуществующего пакета.
И сразу вынесем код функции в отдельный пакет hide_me. Это позволит импортировать код функции из других пакетов и проект находится в чистоте.
В рабочем проекте создаем папку hide_me и внутри файл hide_me.go
Временно создаем функцию-маркер-заглушку HideMe() которая просто вернет текст.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
// hide_me/hide_me.go
package hide_me
func HideMe(text string) string {
return text
}
Расшифровка строк на Go
Теперь нужно написать настоящую функцию, вместо функции-заглушки из предыдущей главы. Этот файл будет вставлен в файл hide_me/hide_me.go перед сборкой.
Функция собирает обратно строку из байтов. Эту функцию необходимо написать совместимо с ответной Python частью (функцией hide_arg())
Алгоритм может быть любой. Пример DES, chacha20, salsa, fernet. Для примера XOR с фиксированным ключом.
И для примера получаем следующую функцию:
Выполняем операцию XOR для каждого байта из входного массива. Результат приводим к строке и возвращаем
Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
// hide_me/hide_me.go // v 1.prod
package hide_me
const HideMe_key int = 15
func HideMe(data []byte]) string {
result := ""
for i := 0; i < len(data); i++ {
result += string(data ^ HideMe_key)
}
return result
}
Маркеры
Далее предстоит весьма длительная однообразная часть:
В каждом файле проекта заменяем строковые аргументы на результат вызова функции hide_me.HideMe()
Пример: myFunc("Test") заменяем на myFunc(hide_me.HideMe("Test"))
На примере файла main.go
Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
// main.go
package main
import (
"fmt"
"main/hide_me"
)
func greeting(name string) {
fmt.Println(hide_me.HideMe("Hello"), name)
}
func main() {
t := hide_me.HideMe("This is Text")
fmt.Println(t)
arr := []string{
hide_me.HideMe("arr_1"),
hide_me.HideMe("arr_2"),
hide_me.HideMe("arr_3"),
}
fmt.Println(arr)
greeting(hide_me.HideMe("XSS"))
}
Теперь код проходит проверки IDE, типы сходятся, и появились маркеры для строк (функция HideMe). Можем продолжить писать на Go, но теперь появилась замечательная возможность скрыть строки перед сборкой. Сделайте build, все должно работать как до изменений. Поздравляем, все позади.
Но, строки еще обнаруживаются через поиск в бинарном файле.
Пишем обфускатор на python
Вспоминаем о схеме работы. Этот скрипт скроет все строки в исходнике.
Модуль ast помогает Python приложениям обрабатывать Python деревья абстрактных синтаксических грамматик.
Проще: Позволяет из python кода редактировать python/Golang/JS/C++/... код.
Попробуем использовать готовый AST. AST обрабатывает сложные конструкции с учетом переносов, скобочек, запятых.
Нашел 2 библиотеки AST Go для Python:
- https://github.com/up9inc/gopygo 56 звезд. Последний commit 3 года назад
- https://github.com/itayg25/goastpy 3 звезды. Последний commit год назад
- goastpy прокси между python и нативным golang AST через C вызовы. Прозвучало страшно, но на деле все просто.
Но это не заработало. Возможно виновата кроссплатформенность, или ошибка в коде, или неправильный билд, или ...
- gopygo написан на чистом python. Работает на SLY (Sly Lex-Yacc), но распарсил только первый элемент из блока команд.
Снова обе не прошли отбор. Будем писать поиск строк сами.
Напишем скрипт pre_build.py. Пусть рекурсивно обходит указанную папку. В каждом файле с расширением '.go', заменяет аргумент у функции hide_me.HideMe.
Пример hide_me.HideMe("any_text") на hide_me.HideMe(<байты>). После этого шага читать исходники станет трудно, но исходники остаются синтаксически корректными.
Python: Скопировать в буфер обмена
Код:
import random
from pathlib import Path
def get_arg(line: str) -> str:
# yourFunc("test") -> test
obj = line.split('"')
assert len(obj) == 3, line
return obj[1]
def hide_arg(arg: str, xor_key: int) -> str:
# test -> []byte{1,2,3,4,5,6}
result = []
for i in arg:
result.append(str(ord(i) ^ xor_key))
return f'[]byte{{ {", ".join(result)} }}'
def hide_line(line: str, xor_key: int) -> str:
# greeting(hide_me.HideMe("XSS")) -> greeting(hide_me.HideMe([]byte{ 19, 24, 24 }))
if "hide_me.HideMe(" not in line:
return line
arg = get_arg(line)
return line.replace(
f'hide_me.HideMe("{arg}")',
f'hide_me.HideMe({hide_arg(arg, xor_key)})',
)
def hide_file(file: Path, xor_key: int):
# hide_line для всего файла
new_data = ""
with open(file, 'r') as f:
for line in f:
new_data += hide_line(line, xor_key)
with open(file, "w") as f:
f.write(new_data)
def hide_project(root: Path):
xor_key = random.randint(0, 254)
for file in root.glob('**/*.go'):
hide_file(file, xor_key)
# запишем инструкции по дешифровке строк
(root / "hide_me").mkdir(exist_ok=True, parents=True)
with open(root / "hide_me" / "hide_me.go", "w") as f:
f.write(f"""
package hide_me
const HideMe_key byte = {xor_key}
func HideMe(data []byte) string {{
result := ""
for i := 0; i < len(data); i++ {{
result += string(data[i] ^ HideMe_key)
}}
return result
}}
""")
if __name__ == "__main__":
hide_project(Path("."))
Функция hide_arg
Напомню код функции:
Python: Скопировать в буфер обмена
Код:
def hide_arg(arg: str, xor_key: int) -> str:
result = []
for i in arg:
result.append(str(ord(i) ^ xor_key))
return f'[]byte{{ {", ".join(result)} }}'
Важно! Эта функция должна быть совместима с hide_me.go
Функция состоит из двух частей. Преобразование очередного символа из строки с помощью XOR и ключом xor_key. Вторая часть собирает строку из result (массива строк)
Станет понятно на примере. input и output - строки,
input: test
output: []byte{1,2,3,4,5,6}
Функция get_arg
Python: Скопировать в буфер обмена
Код:
def get_arg(line: str) -> str:
# yourFunc("test") -> test
obj = line.split('"')
assert len(obj) == 3, line
return obj[1]
Функция hide_line
Python: Скопировать в буфер обмена
Код:
def hide_line(line: str, xor_key: int) -> str:
if "hide_me.HideMe(" not in line:
return line
arg = get_arg(line)
return line.replace(
f'hide_me.HideMe("{arg}")',
f'hide_me.HideMe({hide_arg(arg, xor_key)})',
)
Функция принимающая одну строковую переменную line. line это строка из файла-исходника. Производим замену в строке (переменная line) исходной строки 'HideMe("qwe")' на результат функции hide_arg (описанной
ранее), пример 'HideMe([]byte{1,2,3,....})'
Пример:
input: greeting(hide_me.HideMe("XSS"))
output: greeting(hide_me.HideMe([]byte{ 19, 24, 24 }))
Остальные функции
- hide_file
запускает hide_line для каждой строки в файле. перезаписывает файл
- hide_project
запускает hide_file для каждого файла из папки (рекурсивно) И записывает файл hide_me/hide_me.go с заполненным xor_key и настоящей функцией HideMe
Запуск обфускатора
Внимание! Перед запуском pre_build.py следует копировать проект во временную папку. Иначе потеряете исходники!
Запустим скрипт: python3 main.py
Секунду работает и завершается без ошибки. Посмотрим на новое содержимое main.go файла после обфускации строк.
Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
package main
# main.go
import (
"fmt"
"main/hide_me"
)
func greeting(name string) {
fmt.Println(hide_me.HideMe([]byte{ 214, 251, 242, 242, 241 }), name)
}
func main() {
t := hide_me.HideMe([]byte{ 202, 246, 247, 237, 190, 247, 237, 190, 202, 251, 230, 234 })
fmt.Println(t)
arr := []string{
hide_me.HideMe([]byte{ 255, 236, 236, 193, 175 }),
hide_me.HideMe([]byte{ 255, 236, 236, 193, 172 }),
hide_me.HideMe([]byte{ 255, 236, 236, 193, 173 }),
}
fmt.Println(arr)
greeting(hide_me.HideMe([]byte{ 198, 205, 205 }))
}
Замечательно! В исходнике нет строк. Синтаксис правильный, код валидный. IDE не видит проблем. Не думаю что кто-либо захочет редактировать исходники после этого преобразования. Остался последний шаг: запустить сборку проекта.
Go. Сборка проекта
Собираем как раньше: "go build -o main ."
Запускаем: "./main"
И получаем вывод как до модификации проекта:
Hello XSS
This is Text
[arr_1 arr_2 arr_3]
Работает точно как в первый раз, с небольшим отличием: golang в runtime расшифровывает строки. Убедимся в этом в следующей части.
Повторный анализ бинарника
Запускаем поиск через strings как в первый раз. Но в этот раз поиск строк по файлу дает пустой результат.
Кроме строк, в бинарнике находятся имена пакетов, файлов. Этим возможно займемся в следующих статьях.
Выводы
В билдах на Go нужно прятать строки, имена файлов, имена пакетов. Обфускацию можно проводить перед сборкой проекта.
Современные системы сборки позволяют организовывать многоступенчатые сборки. Воспользуйтесь этим. Или запускайте команды руками каждый раз.