mirror of
https://github.com/f1zm0/acheron
synced 2026-06-08 14:09:51 +00:00
feat: add zw exports parsing logic to resolver
This commit is contained in:
+29
-11
@@ -24,8 +24,8 @@ TEXT ·getNtdllBaseAddr(SB),NOSPLIT,$0
|
|||||||
RET
|
RET
|
||||||
|
|
||||||
|
|
||||||
// func getModuleEATAddr (moduleBase uintptr) uintptr
|
// func getModuleExportsDirAddr (moduleBase uintptr) uintptr
|
||||||
TEXT ·getModuleEATAddr(SB),NOSPLIT,$0-8
|
TEXT ·getModuleExportsDirAddr(SB),NOSPLIT,$0-8
|
||||||
MOVQ moduleBase+0(FP), AX
|
MOVQ moduleBase+0(FP), AX
|
||||||
|
|
||||||
XORQ R15, R15
|
XORQ R15, R15
|
||||||
@@ -41,28 +41,28 @@ TEXT ·getModuleEATAddr(SB),NOSPLIT,$0-8
|
|||||||
ADDQ $0x88, R15
|
ADDQ $0x88, R15
|
||||||
|
|
||||||
// AX = ntdll base + IMAGE_DATA_DIRECTORY.VirtualAddress
|
// AX = ntdll base + IMAGE_DATA_DIRECTORY.VirtualAddress
|
||||||
ADDL 0x0(R15), R14
|
ADDL (R15), R14
|
||||||
ADDQ R14, AX
|
ADDQ R14, AX
|
||||||
|
|
||||||
MOVQ AX, ret+8(FP)
|
MOVQ AX, ret+8(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
|
||||||
// func getEATNumberOfFunctions(exportsBase uintptr) uint32
|
// func getExportsNumberOfNames(exportsBase uintptr) uint32
|
||||||
TEXT ·getEATNumberOfFunctions(SB),NOSPLIT,$0-8
|
TEXT ·getExportsNumberOfNames(SB),NOSPLIT,$0-8
|
||||||
MOVQ exportsBase+0(FP), AX
|
MOVQ exportsBase+0(FP), AX
|
||||||
|
|
||||||
XORQ R15, R15
|
XORQ R15, R15
|
||||||
|
|
||||||
// R15 = exportsBase + IMAGE_EXPORT_DIRECTORY.NumberOfFunctions
|
// R15 = exportsBase + IMAGE_EXPORT_DIRECTORY.NumberOfNames
|
||||||
MOVL 0x14(AX), R15
|
MOVL 0x18(AX), R15
|
||||||
|
|
||||||
MOVL R15, ret+8(FP)
|
MOVL R15, ret+8(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
|
||||||
// func getEATAddressOfFunctions(moduleBase,exportsBase uintptr) uintptr
|
// func getExportsAddressOfFunctions(moduleBase,exportsBase uintptr) uintptr
|
||||||
TEXT ·getEATAddressOfFunctions(SB),NOSPLIT,$0-16
|
TEXT ·getExportsAddressOfFunctions(SB),NOSPLIT,$0-16
|
||||||
MOVQ moduleBase+0(FP), AX
|
MOVQ moduleBase+0(FP), AX
|
||||||
MOVQ exportsBase+8(FP), R8
|
MOVQ exportsBase+8(FP), R8
|
||||||
|
|
||||||
@@ -78,8 +78,8 @@ TEXT ·getEATAddressOfFunctions(SB),NOSPLIT,$0-16
|
|||||||
RET
|
RET
|
||||||
|
|
||||||
|
|
||||||
// func getEATAddressOfNames(moduleBase,exportsBase uintptr) uintptr
|
// func getExportsAddressOfNames(moduleBase,exportsBase uintptr) uintptr
|
||||||
TEXT ·getEATAddressOfNames(SB),NOSPLIT,$0-16
|
TEXT ·getExportsAddressOfNames(SB),NOSPLIT,$0-16
|
||||||
MOVQ moduleBase+0(FP), AX
|
MOVQ moduleBase+0(FP), AX
|
||||||
MOVQ exportsBase+8(FP), R8
|
MOVQ exportsBase+8(FP), R8
|
||||||
|
|
||||||
@@ -93,3 +93,21 @@ TEXT ·getEATAddressOfNames(SB),NOSPLIT,$0-16
|
|||||||
|
|
||||||
MOVQ AX, ret+16(FP)
|
MOVQ AX, ret+16(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
|
||||||
|
// func getExportsAddressOfNameOrdinals(moduleBase, exportsBase uintptr) uintptr
|
||||||
|
TEXT ·getExportsAddressOfNameOrdinals(SB),NOSPLIT,$0-16
|
||||||
|
MOVQ moduleBase+0(FP), AX
|
||||||
|
MOVQ exportsBase+8(FP), R8
|
||||||
|
|
||||||
|
XORQ SI, SI
|
||||||
|
|
||||||
|
// SI = exportsBase + IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals
|
||||||
|
MOVL 0x24(R8), SI
|
||||||
|
|
||||||
|
// AX = exportsBase + AddressOfNames offset
|
||||||
|
ADDQ SI, AX
|
||||||
|
|
||||||
|
MOVQ AX, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
|
||||||
wt "github.com/f1zm0/acheron/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const SYSCALL_STUB_SIZE = 0x20
|
|
||||||
|
|
||||||
// FindSyscallRetGadgets finds syscall;ret gadgets in ntdll.dll
|
// FindSyscallRetGadgets finds syscall;ret gadgets in ntdll.dll
|
||||||
// that can be "recycled" to ensure syscalls goes through ntdll.
|
// that can be "recycled" to ensure syscalls goes through ntdll.
|
||||||
func FindSyscallRetGadgets(hNtdll *wt.PEModule) []uintptr {
|
func FindSyscallRetGadgets(stubs map[int64]*ZwStub) []uintptr {
|
||||||
// TODO: do implementation
|
// TODO: do implementation
|
||||||
return []uintptr{}
|
return []uintptr{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
wt "github.com/f1zm0/acheron/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetNtdlloduleHandle returns a PEModule struct with information about in-memory
|
|
||||||
// ntdll.dll's module, or an error if for some reason an error occur while reading it.
|
|
||||||
func GetNtdllModuleHandle() (*wt.PEModule, error) {
|
|
||||||
moduleBase := getNtdllBaseAddr()
|
|
||||||
fmt.Printf("ntdll base: 0x%x\r\n\r\n", moduleBase)
|
|
||||||
|
|
||||||
exportsBase := getModuleEATAddr(moduleBase)
|
|
||||||
fmt.Printf("EAT base: 0x%x\r\n\r\n", exportsBase)
|
|
||||||
|
|
||||||
numberOfFunctions := getEATNumberOfFunctions(exportsBase)
|
|
||||||
fmt.Printf("Number of functions: %d\r\n\r\n", numberOfFunctions)
|
|
||||||
|
|
||||||
addressOfFunctions := getEATAddressOfFunctions(moduleBase, exportsBase)
|
|
||||||
fmt.Printf("Address of functions: 0x%x\r\n\r\n", addressOfFunctions)
|
|
||||||
|
|
||||||
addressOfNames := getEATAddressOfNames(moduleBase, exportsBase)
|
|
||||||
fmt.Printf("Address of names: 0x%x\r\n\r\n", addressOfNames)
|
|
||||||
|
|
||||||
// rr := rrd.NewRawReader(modBaseAddr, modSize)
|
|
||||||
|
|
||||||
// p, err := pe.NewFileFromMemory(rr)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, errors.New("error reading module from memory")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return &wt.PEModule{
|
|
||||||
// BaseAddr: modBaseAddr,
|
|
||||||
// File: p,
|
|
||||||
// }, nil
|
|
||||||
|
|
||||||
return &wt.PEModule{}, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/f1zm0/acheron/pkg/hashing"
|
||||||
|
"github.com/f1zm0/acheron/pkg/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NtModule struct {
|
||||||
|
BaseAddr uintptr
|
||||||
|
ExportsBaseAddr uintptr
|
||||||
|
NumberOfNames uint32
|
||||||
|
AddressOfFunctions uintptr
|
||||||
|
AddressOfNames uintptr
|
||||||
|
AddressOfNameOrdinals uintptr
|
||||||
|
ZwStubs map[int64]*ZwStub
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZwStub struct {
|
||||||
|
RVA uint32
|
||||||
|
VA uintptr
|
||||||
|
SSN uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseNtdllModule returns a NtModule struct with the relevant information
|
||||||
|
// about the in-memory ntdll.dll module.
|
||||||
|
func ParseNtdllModule(hashFn hashing.Hasher) *NtModule {
|
||||||
|
var m NtModule
|
||||||
|
|
||||||
|
m.BaseAddr = getNtdllBaseAddr()
|
||||||
|
m.ExportsBaseAddr = getModuleExportsDirAddr(m.BaseAddr)
|
||||||
|
m.NumberOfNames = getExportsNumberOfNames(m.ExportsBaseAddr)
|
||||||
|
m.AddressOfFunctions = getExportsAddressOfFunctions(m.BaseAddr, m.ExportsBaseAddr)
|
||||||
|
m.AddressOfNames = getExportsAddressOfNames(m.BaseAddr, m.ExportsBaseAddr)
|
||||||
|
m.AddressOfNameOrdinals = getExportsAddressOfNameOrdinals(m.BaseAddr, m.ExportsBaseAddr)
|
||||||
|
|
||||||
|
m.ZwStubs = make(
|
||||||
|
map[int64]*ZwStub,
|
||||||
|
m.NumberOfNames/4, // Zw* functions are less than ~25% of the total so we can save some memory
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := uint32(0); i < m.NumberOfNames; i++ {
|
||||||
|
fn := memory.ReadCStringAt(m.BaseAddr, memory.ReadDwordAt(m.AddressOfNames, i*4))
|
||||||
|
if fn[0] != 'Z' || fn[1] != 'w' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fnHash := hashFn.HashByteString(fn)
|
||||||
|
|
||||||
|
nameOrd := memory.ReadWordAt(m.AddressOfNameOrdinals, i*2)
|
||||||
|
rva := memory.ReadDwordAt(m.AddressOfFunctions, uint32(nameOrd*4))
|
||||||
|
|
||||||
|
m.ZwStubs[fnHash] = &ZwStub{
|
||||||
|
RVA: rva,
|
||||||
|
VA: memory.RVA2VA(m.BaseAddr, uint32(rva)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/f1zm0/acheron/internal/resolver"
|
"github.com/f1zm0/acheron/internal/resolver"
|
||||||
wt "github.com/f1zm0/acheron/internal/types"
|
|
||||||
"github.com/f1zm0/acheron/pkg/hashing"
|
"github.com/f1zm0/acheron/pkg/hashing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ type ssnSortResolver struct {
|
|||||||
hasher hashing.Hasher
|
hasher hashing.Hasher
|
||||||
|
|
||||||
// map of Zw* InMemProc structs indexed by their name's hash
|
// map of Zw* InMemProc structs indexed by their name's hash
|
||||||
zwStubs map[int64]wt.InMemProc
|
zwStubs map[int64]*resolver.ZwStub
|
||||||
|
|
||||||
// slice with addresses of clean gadgets
|
// slice with addresses of clean gadgets
|
||||||
safeGates []uintptr
|
safeGates []uintptr
|
||||||
@@ -22,7 +21,9 @@ type ssnSortResolver struct {
|
|||||||
var _ resolver.Resolver = (*ssnSortResolver)(nil)
|
var _ resolver.Resolver = (*ssnSortResolver)(nil)
|
||||||
|
|
||||||
func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
|
func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
|
||||||
r := &ssnSortResolver{}
|
r := &ssnSortResolver{
|
||||||
|
hasher: h,
|
||||||
|
}
|
||||||
if err := r.init(); err != nil {
|
if err := r.init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -30,17 +31,9 @@ func NewResolver(h hashing.Hasher) (resolver.Resolver, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ssnSortResolver) init() error {
|
func (r *ssnSortResolver) init() error {
|
||||||
// var zwStubs []wt.InMemProc
|
ntMod := resolver.ParseNtdllModule(r.hasher)
|
||||||
|
_ = ntMod
|
||||||
|
|
||||||
_, err := resolver.GetNtdllModuleHandle()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// exports, err := hNtdll.File.Exports()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// for _, exp := range exports {
|
// for _, exp := range exports {
|
||||||
// memAddr := int64(hNtdll.BaseAddr) + int64(exp.VirtualAddress)
|
// memAddr := int64(hNtdll.BaseAddr) + int64(exp.VirtualAddress)
|
||||||
// r.safeGates = resolver.FindSyscallRetGadgets(hNtdll)
|
// r.safeGates = resolver.FindSyscallRetGadgets(hNtdll)
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
// getNtdllBase signature.
|
|
||||||
func getNtdllBaseAddr() uintptr
|
func getNtdllBaseAddr() uintptr
|
||||||
|
|
||||||
// getModuleEATAddr signature.
|
func getModuleExportsDirAddr(modBaseAddr uintptr) uintptr
|
||||||
func getModuleEATAddr(modBaseAddr uintptr) uintptr
|
|
||||||
|
|
||||||
// getEATNumberOfFunctions signature.
|
func getExportsNumberOfNames(exportsBase uintptr) uint32
|
||||||
func getEATNumberOfFunctions(exportsBase uintptr) uint32
|
|
||||||
|
|
||||||
// getEATAddressOfFunctions signature.
|
func getExportsAddressOfFunctions(moduleBase, exportsBase uintptr) uintptr
|
||||||
func getEATAddressOfFunctions(moduleBase, exportsBase uintptr) uintptr
|
|
||||||
|
|
||||||
// getEATAddressOfNames signature.
|
func getExportsAddressOfNames(moduleBase, exportsBase uintptr) uintptr
|
||||||
func getEATAddressOfNames(moduleBase, exportsBase uintptr) uintptr
|
|
||||||
|
func getExportsAddressOfNameOrdinals(moduleBase, exportsBase uintptr) uintptr
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Binject/debug/pe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PEModule is a struct that contains the base address of a PE module and a pointer to the PE file.
|
|
||||||
type PEModule struct {
|
|
||||||
BaseAddr uintptr
|
|
||||||
File *pe.File
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
rrd "github.com/f1zm0/acheron/pkg/rawreader"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InMemProc is a struct that contains the name, base address and SSN of a function.
|
|
||||||
type InMemProc struct {
|
|
||||||
Name string
|
|
||||||
BaseAddr uintptr
|
|
||||||
GateAddr uintptr
|
|
||||||
SSN int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InMemProc) IsHooked() bool {
|
|
||||||
safeBytes := []byte{0x4c, 0x8b, 0xd1, 0xb8}
|
|
||||||
stub := make([]byte, len(safeBytes))
|
|
||||||
|
|
||||||
rr := rrd.NewRawReader(p.BaseAddr, len(safeBytes))
|
|
||||||
|
|
||||||
sr := io.NewSectionReader(rr, 0, 1<<63-1)
|
|
||||||
binary.Read(sr, binary.LittleEndian, &stub)
|
|
||||||
|
|
||||||
if bytes.Compare(stub, safeBytes) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import "golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
// UnicodeString is a struct that represents a Windows Unicode string.
|
|
||||||
type UnicodeString struct {
|
|
||||||
Length uint16
|
|
||||||
MaximumLength uint16
|
|
||||||
Buffer *uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the string representation of the UnicodeString.
|
|
||||||
func (s UnicodeString) String() string {
|
|
||||||
return windows.UTF16PtrToString(s.Buffer)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// func RVA2VA(moduleBase uintptr, rva uint32) uintptr
|
||||||
|
TEXT ·RVA2VA(SB),NOSPLIT,$0-16
|
||||||
|
MOVQ moduleBase+0(FP), AX
|
||||||
|
XORQ DI, DI
|
||||||
|
|
||||||
|
MOVL rva+8(FP), DI
|
||||||
|
ADDQ DI, AX
|
||||||
|
|
||||||
|
MOVQ AX, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
|
||||||
|
// func ReadDwordAt(start uintptr, offset uint32) uint32
|
||||||
|
TEXT ·ReadDwordAt(SB),NOSPLIT,$0-16
|
||||||
|
MOVQ start+0(FP), AX
|
||||||
|
MOVL offset+8(FP), R8
|
||||||
|
|
||||||
|
XORQ DI, DI
|
||||||
|
ADDQ R8, AX
|
||||||
|
MOVL (AX), DI
|
||||||
|
|
||||||
|
MOVL DI, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
|
||||||
|
// func ReadWordAt(start uintptr, offset uint32) uint16
|
||||||
|
TEXT ·ReadWordAt(SB),NOSPLIT,$0-16
|
||||||
|
MOVQ start+0(FP), AX
|
||||||
|
MOVL offset+8(FP), R8
|
||||||
|
|
||||||
|
XORQ DI, DI
|
||||||
|
ADDQ R8, AX
|
||||||
|
MOVW (AX), DI
|
||||||
|
|
||||||
|
MOVW DI, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
|
||||||
|
// func ReadByteAt(start uintptr, offset uint32) uint8
|
||||||
|
TEXT ·ReadByteAt(SB),NOSPLIT,$0-16
|
||||||
|
MOVQ start+0(FP), AX
|
||||||
|
MOVL offset+8(FP), R8
|
||||||
|
|
||||||
|
XORQ DI, DI
|
||||||
|
ADDQ R8, AX
|
||||||
|
MOVB (AX), DI
|
||||||
|
|
||||||
|
MOVB DI, ret+16(FP)
|
||||||
|
RET
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
// ReadCStringAt reads a null-terminated ANSI string from memory.
|
||||||
|
func ReadCStringAt(start uintptr, offset uint32) []byte {
|
||||||
|
var buf []byte
|
||||||
|
for {
|
||||||
|
ch := ReadByteAt(start, offset)
|
||||||
|
if ch == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf = append(buf, ch)
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
func RVA2VA(moduleBase uintptr, rva uint32) uintptr
|
||||||
|
|
||||||
|
func ReadDwordAt(start uintptr, offset uint32) uint32
|
||||||
|
|
||||||
|
func ReadWordAt(start uintptr, offset uint32) uint16
|
||||||
|
|
||||||
|
func ReadByteAt(start uintptr, offset uint32) byte
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package rawreader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RawReader struct and functions below are taken from:
|
|
||||||
// https://github.com/awgh/rawreader/blob/master/rawreader.go
|
|
||||||
|
|
||||||
// RawReader struct uses reflect to read data from underlying memory
|
|
||||||
type RawReader struct {
|
|
||||||
sliceHeader *reflect.SliceHeader
|
|
||||||
rawPtr uintptr
|
|
||||||
Data []byte
|
|
||||||
Length int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRawReader returns a reference to a new populated RawReader
|
|
||||||
func NewRawReader(start uintptr, length int) *RawReader {
|
|
||||||
sh := &reflect.SliceHeader{
|
|
||||||
Data: start,
|
|
||||||
Len: length,
|
|
||||||
Cap: length,
|
|
||||||
}
|
|
||||||
data := *(*[]byte)(unsafe.Pointer(sh))
|
|
||||||
return &RawReader{sliceHeader: sh, rawPtr: start, Data: data, Length: length}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadAt func reads a file with a seek offset
|
|
||||||
func (f *RawReader) ReadAt(p []byte, off int64) (n int, err error) {
|
|
||||||
if off < 0 {
|
|
||||||
return 0, errors.New("RawReader.ReadAt: negative offset")
|
|
||||||
}
|
|
||||||
reqLen := len(p)
|
|
||||||
buffLen := int64(f.Length)
|
|
||||||
if off >= buffLen {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
n = copy(p, f.Data[off:])
|
|
||||||
if n < reqLen {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user