Вопрос или проблема
Я пытаюсь сделать программу на Go, чтобы выполнить файл .exe
, но без того, чтобы он на самом деле был файлом .exe
. Идея заключается в том, чтобы прочитать исполняемый файл из текстового файла .txt
и декодировать его (он в base64). Как только я сохраню этот код в переменной, я хочу выполнить его напрямую, без записи на диск.
В этой строке почему вы декодируете и сохраняете код в переменной decodedDataBytes:
decodedDataBytes, err := base64.StdEncoding.DecodeString(decodedData)
Затем я записываю его во временный файл и выполняю так:
tempFilePath := "temp.exe"
err = ioutil.WriteFile(tempFilePath, decodedDataBytes, 0644)
if err != nil {
log.Fatal(err)
}
cmd := exec.Command("D:\temp.exe")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Это работает прекрасно, но мне нужно сделать это без хранения кода в файле temp.exe
.
Как я могу это сделать??
Я использую Go, но если это можно сделать с другим языком, я тоже приму идеи.
Одно возможное решение — использовать API Windows под названием VirtualAlloc, чтобы выделить исполняемую память, записать декодированные байты в эту память, а затем выполнить его. Вот пример кода, который я написал и который поможет вам это сделать:
package main
import (
"encoding/base64"
"log"
"syscall"
"unsafe"
)
func main() {
// Читаем строку base64 из .txt файла
base64Data := "BASE64_ENCODED_STRING" // Замените на вашу фактическую строку base64
// Декодируем строку base64
decodedData, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
log.Fatal(err)
}
// Выделяем исполняемую память
executableMemory, err := syscall.VirtualAlloc(0, uintptr(len(decodedData)), syscall.MEM_COMMIT, syscall.PAGE_EXECUTE_READWRITE)
if err != nil {
log.Fatal(err)
}
// Копируем декодированные байты в исполняемую память
executableMemorySlice := *(*[]byte)(unsafe.Pointer(&syscall.Slice{Data: executableMemory, Len: len(decodedData), Cap: len(decodedData)}))
copy(executableMemorySlice, decodedData)
// Выполняем код в исполняемой памяти
syscall.Syscall(executableMemory, 0, 0, 0, 0)
// Освобождаем выделенную память
syscall.VirtualFree(executableMemory, 0, syscall.MEM_RELEASE)
}
В этом коде я использую функцию syscall.VirtualAlloc для выделения исполняемой памяти, syscall.VirtualFree для освобождения выделенной памяти и syscall.Syscall для выполнения кода в выделенной памяти. Пакет unsafe используется для преобразования выделенной памяти в срез байтов для копирования декодированных байтов.
Если ваша цель просто “не файл .EXE”, есть легкий вариант: используйте буквально любое другое расширение (или вообще не используйте). Единственное, что делает “.EXE” особенным, это то, что это одно из небольшого числа расширений файлов, которые оболочка Windows (через семейство API ShellExecute
) по умолчанию попытается выполнить напрямую, а не пытаться открыть обработчик для. Тем не менее, нет необходимости использовать ShellExecute
для чего-либо, особенно если вы делаете это программно. Вы можете вызывать семейство API CreateProcess
на любом файле системы, и если у него есть разрешение на выполнение для соответствующего субъекта безопасности (пользователя) и он читаем как PE исполняемый файл, это сработает. Расширение может быть любым, что вы хотите, или вообще отсутствовать.
На самом деле, CMD всегда пытался CreateProcess
файл прежде всего, если вы передаете файл как обычную команду. В результате следующее работает прекрасно (в cmd.exe
, а не в Powershell или любой другой оболочке, кроме, возможно, command.com
, если по какой-то причине вы находитесь на 32-битной системе), чтобы запустить калькулятор Windows:
> copy %windir%\System32\calc.exe .\bogus.txt
> bogus.txt
Обычно передача файла “.txt” в CMD откроет ваш текстовый редактор по умолчанию (Блокнот, если вы не изменили значение по умолчанию), но только потому, что оболочка использует ShellExecute
, когда CreateProcess
не срабатывает. Поскольку вы пишете свою программу, здесь вы можете просто вызвать CreateProcess
и вообще не рассматривать ShellExecute
.
Если вы хотите создать процесс непосредственно из буфера памяти… это возможно, но это определенно сложнее. Один из вариантов — создать процесс в приостановленном состоянии, указывая на какой-то произвольный исполняемый файл (даже ваш собственный текущий исполняемый файл), а затем заменить исполняемое изображение, прежде чем вызвать ResumeThread
, чтобы запустить процесс. Этот вопрос имеет ответ, обсуждающий этот подход. В качестве альтернативы, по крайней мере один из системных вызовов NT NtCreateProcessEx
или NtCreateUserProcess
должен позволить создать процесс из участка памяти, хотя я никогда этого не делал и у меня нет удобного руководства по этому (учтите, что от стороннего кода не ожидается вызов этих системных вызовов напрямую; они плохо документированы и подвержены изменениям в поведении). Вы также можете попробовать использовать объект, который технически не является файлом, например, именованное совместное использование памяти и передать путь (учтите, что это не путь к файлу) к стандартным API CreateProcess
, хотя я не знаю, сработает ли это тоже.
Ответ или решение
Для выполнения команды в Go без хранения команд в виде .exe файла, вы можете использовать низкоуровневые системные вызовы Windows, такие как VirtualAlloc
, VirtualFree
и Syscall
. Эти вызовы позволяют выделить память для исполняемого кода, загрузить в нее байты и выполнить их непосредственно из памяти. Это более сложный, но эффективный способ, который поможет вам избежать записи на диск.
Шаги по выполнению команд в Go из памяти
1. Чтение закодированных данных
Сначала вам необходимо получить закодированные данные из txt файла и декодировать их из формата base64:
package main
import (
"encoding/base64"
"io/ioutil"
"log"
)
func main() {
// Чтение base64 строки из текстового файла
data, err := ioutil.ReadFile("data.txt")
if err != nil {
log.Fatal(err)
}
base64Data := string(data)
// Декодирование base64 строки
decodedData, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
log.Fatal(err)
}
// Продолжайте выполнение...
}
2. Выделение исполняемой памяти
Теперь вы можете выделить память и скопировать в нее декодированные байты:
package main
import (
"encoding/base64"
"io/ioutil"
"log"
"syscall"
"unsafe"
)
func main() {
// Чтение base64 строки из текстового файла
data, err := ioutil.ReadFile("data.txt")
if err != nil {
log.Fatal(err)
}
base64Data := string(data)
// Декодирование base64 строки
decodedData, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
log.Fatal(err)
}
// Выделение исполняемой памяти
executableMemory, err := syscall.VirtualAlloc(0, uintptr(len(decodedData)), syscall.MEM_COMMIT, syscall.PAGE_EXECUTE_READWRITE)
if err != nil {
log.Fatal(err)
}
// Копирование декодированных байтов в выделенную память
copy((*[1<<30]byte)(unsafe.Pointer(executableMemory))[:len(decodedData):len(decodedData)], decodedData)
// Выполнение кода из выделенной памяти
syscall.Syscall(executableMemory, 0, 0, 0, 0)
// Освобождение выделенной памяти
syscall.VirtualFree(executableMemory, 0, syscall.MEM_RELEASE)
}
3. Описание кода
-
Чтение и Декодирование:
- Код читает base64-строку из текстового файла и декодирует ее, чтобы получить бинарные данные.
-
Выделение Памяти:
- Функция
syscall.VirtualAlloc
выделяет память в адресном пространстве вашего процесса с правами на исполнение.
- Функция
-
Копирование и Выполнение:
- Декодированные байты копируются в выделенную память.
syscall.Syscall
выполняет код, передавая адрес памяти, где находится исполняемый код.
-
Освобождение Памяти:
- После завершения выполнения кода, память освобождается с помощью
syscall.VirtualFree
.
- После завершения выполнения кода, память освобождается с помощью
Заключение
Этот подход позволяет запускать исполняемые байты без необходимости сохранять их в виде .exe файла. Он может быть полезен в ситуациях, когда требуется скрыть исполняемый код или избежать записи на диск по соображениям безопасности. Тем не менее, он требует глубокого понимания низкоуровневого программирования и системных вызовов, поэтому его следует использовать с осторожностью.