mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-30 13:34:53 +02:00
Add in write support, and change symbols to be both structures and constants.
This commit is contained in:
+27
-7
@@ -18,14 +18,26 @@ def test_symbols():
|
||||
|
||||
ctx = framework.Context(nativelst)
|
||||
# ctx.symbol_space.append(nativelst)
|
||||
ctx.symbol_space.append(ntkrnlmp)
|
||||
print("Symbols,", nativelst.symbols)
|
||||
ctx = utils_load_as()
|
||||
print("Symbols,", nativelst.structures)
|
||||
|
||||
for i in list(ntkrnlmp.symbols):
|
||||
symbol = ctx.symbol_space.resolve('ntkrnlmp!' + i)
|
||||
for i in list(ntkrnlmp.structures):
|
||||
symbol = ctx.symbol_space.get_structure('ntkrnlmp!' + i)
|
||||
print(symbol.symbol_name, symbol, symbol.size)
|
||||
_objthing = symbol(ctx, layer_name = '', offset = 0)
|
||||
symbol = ctx.symbol_space.resolve('ntkrnlmp!_EPROCESS')
|
||||
symbol = ctx.symbol_space.get_structure('ntkrnlmp!_EPROCESS')
|
||||
|
||||
def utils_load_as():
|
||||
nativelst = native.x86NativeTable
|
||||
|
||||
virtual_types = xp_sp2_x86_vtypes.ntkrnlmp_types
|
||||
|
||||
ntkrnlmp = vtypes.VTypeSymbolTable('ntkrnlmp', virtual_types, nativelst)
|
||||
|
||||
ctx = framework.Context(nativelst)
|
||||
# ctx.symbol_space.append(nativelst)
|
||||
ctx.symbol_space.append(ntkrnlmp)
|
||||
return ctx
|
||||
|
||||
def test_memory():
|
||||
nativelst = native.x86NativeTable
|
||||
@@ -47,6 +59,14 @@ def test_memory():
|
||||
val = ctx.object('ntkrnlmp!TEST_POINTER', 'data', 0)
|
||||
print(hex(val.point1.test1), val.point1.test2)
|
||||
|
||||
def test_kdbgfind(ctx):
|
||||
ctx = utils_load_as()
|
||||
base = layers.physical.FileLayer(ctx, 'data', filename = '/home/mike/memory/xp-laptop-2005-06-25.img')
|
||||
ctx.memory.add_layer(base)
|
||||
intel = layers.intel.Intel(ctx, 'intel', 'data', page_map_offset = 0x39000)
|
||||
ctx.memory.add_layer(intel)
|
||||
|
||||
|
||||
def intel32(ctx):
|
||||
base = layers.physical.FileLayer(ctx, 'data', filename = '/home/mike/memory/xp-laptop-2005-06-25.img')
|
||||
ctx.memory.add_layer(base)
|
||||
@@ -94,9 +114,9 @@ if __name__ == '__main__':
|
||||
# import timeit
|
||||
# print(timeit.Timer(main).timeit(10))
|
||||
try:
|
||||
# test_symbols()
|
||||
test_symbols()
|
||||
# test_memory()
|
||||
test_translation()
|
||||
# test_translation()
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pdb.post_mortem()
|
||||
|
||||
@@ -64,7 +64,7 @@ class Context(interfaces.context.ContextInterface):
|
||||
|
||||
Returns a fully constructed object
|
||||
"""
|
||||
object_template = self._symbol_space.resolve(symbol)
|
||||
object_template = self._symbol_space.get_structure(symbol)
|
||||
return object_template(self, layer_name = layer_name, offset = offset)
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Created on 1 Dec 2012
|
||||
class VolatilityException(Exception):
|
||||
"""Class to allow filtering of all VolatilityExceptions"""
|
||||
|
||||
class SymbolNotFoundException(VolatilityException):
|
||||
class SymbolError(VolatilityException):
|
||||
"""Thrown when a symbol lookup has failed"""
|
||||
|
||||
class InvalidAddressException(VolatilityException):
|
||||
|
||||
@@ -21,7 +21,11 @@ class ObjectInterface(validity.ValidityRoutines):
|
||||
self._symbol_name = symbol_name
|
||||
self._size = size
|
||||
|
||||
def write(self, value):
|
||||
"""Writes the new value into the format at the offset the object currently resides at"""
|
||||
|
||||
def cast(self, new_symbol_name):
|
||||
"""Returns a new object at the offset and from the layer that the current object inhabits"""
|
||||
object_template = self._context.symbol_space.resolve(new_symbol_name)
|
||||
return object_template(context = self._context, layer_name = self._layer_name, offset = self._offset)
|
||||
|
||||
|
||||
@@ -4,70 +4,87 @@ Created on 4 May 2013
|
||||
@author: mike
|
||||
'''
|
||||
|
||||
from volatility.framework import validity
|
||||
from volatility.framework import validity, exceptions
|
||||
|
||||
class SymbolTableInterface(validity.ValidityRoutines):
|
||||
"""Handles a table of symbols"""
|
||||
|
||||
def __init__(self, name, native_symbols = None):
|
||||
def __init__(self, name, native_structures = None):
|
||||
self.name = self.type_check(name or None, str)
|
||||
self._native_symbols = self.type_check(native_symbols, NativeTableInterface)
|
||||
self._native_structures = self.type_check(native_structures, NativeTableInterface)
|
||||
|
||||
#TODO: Add in support for constants
|
||||
### Required Constant symbol functions
|
||||
|
||||
### Required Symbol List functions
|
||||
def get_constant(self, name):
|
||||
"""Resolves a symbol name into a constant
|
||||
|
||||
If the symbol isn't found, it raises a SymbolError exception
|
||||
"""
|
||||
|
||||
def resolve(self, symbol):
|
||||
@ property
|
||||
def constants(self):
|
||||
"""Returns an iterator of the constant symbols"""
|
||||
|
||||
### Required Structure symbol functions
|
||||
|
||||
def get_structure(self, name):
|
||||
"""Resolves a symbol name into an object template
|
||||
|
||||
If the symbol isn't found it raises a SymbolNotFound exception
|
||||
If the symbol isn't found it raises a SymbolError exception
|
||||
"""
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
"""Returns an iterator of the symbol names"""
|
||||
def structures(self):
|
||||
"""Returns an iterator of the structure symbols"""
|
||||
|
||||
### Native Type Handler
|
||||
|
||||
@property
|
||||
def natives(self):
|
||||
"""Returns None or a symbol_space for handling space specific native types"""
|
||||
return self._native_symbols
|
||||
return self._native_structures
|
||||
|
||||
### Functions for overriding classes
|
||||
|
||||
def set_symbol_class(self, symbol, clazz):
|
||||
"""Overrides the object class for a specific symbol
|
||||
def set_structure_class(self, name, clazz):
|
||||
"""Overrides the object class for a specific structure symbol
|
||||
|
||||
Symbol *must* be present in self.symbols
|
||||
Name *must* be present in self.structures
|
||||
"""
|
||||
|
||||
def get_symbol_class(self, symbol):
|
||||
"""Returns the class associated with a symbol"""
|
||||
def get_structure_class(self, name):
|
||||
"""Returns the class associated with a structure symbol"""
|
||||
|
||||
def del_symbol_class(self, symbol):
|
||||
"""Removes the associated class override for a specific symbol"""
|
||||
def del_structure_class(self, name):
|
||||
"""Removes the associated class override for a specific structure symbol"""
|
||||
|
||||
### Helper functions that can be overridden
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the number of items in the symbol list"""
|
||||
return len(self.symbols)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Resolves a symbol name into an object template
|
||||
|
||||
Note, this method cannot sub-resolve throughout a whole symbol space
|
||||
"""
|
||||
return self.resolve(key)
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns an iterator of the available keys"""
|
||||
return self.symbols
|
||||
|
||||
def __contains__(self, symbol):
|
||||
"""Determines whether a symbol exists in the list or not"""
|
||||
return symbol in self.symbols
|
||||
# ### Helper functions that can be overridden
|
||||
#
|
||||
# def __len__(self):
|
||||
# """Returns the number of items in the symbol list"""
|
||||
# return len(self.structures)
|
||||
#
|
||||
# def __getitem__(self, key):
|
||||
# """Resolves a symbol name into an object template
|
||||
#
|
||||
# Note, this method cannot sub-resolve throughout a whole symbol space
|
||||
# """
|
||||
# return self.get_structure(key)
|
||||
#
|
||||
# def __iter__(self):
|
||||
# """Returns an iterator of the available keys"""
|
||||
# return self.structures
|
||||
#
|
||||
# def __contains__(self, symbol):
|
||||
# """Determines whether a symbol exists in the list or not"""
|
||||
# return symbol in self.structures
|
||||
|
||||
class NativeTableInterface(SymbolTableInterface):
|
||||
"""Class to distinguish NativeSymbolLists from other symbol lists"""
|
||||
|
||||
def constant(self):
|
||||
raise exceptions.SymbolError("NativeTables never hold constants")
|
||||
|
||||
@property
|
||||
def constants(self):
|
||||
return []
|
||||
|
||||
@@ -65,27 +65,65 @@ class Integer(PrimitiveObject, int):
|
||||
def __new__(cls, context, layer_name, offset, symbol_name, struct_format, **kwargs):
|
||||
return int.__new__(cls, cls._struct_value(struct_format, context, layer_name, offset, symbol_name))
|
||||
|
||||
def write(self, value):
|
||||
"""Writes the object into the layer of the context at the current offset"""
|
||||
if isinstance(value, int):
|
||||
data = struct.pack(self._struct_format, value)
|
||||
return self._context.memory.write(self._layer_name, self._offset, data)
|
||||
raise TypeError("Integer objects require an integer to be written")
|
||||
|
||||
class Float(PrimitiveObject, float):
|
||||
"""Primitive Object that handles double or floating point numbers"""
|
||||
|
||||
def __new__(cls, context, layer_name, offset, symbol_name, struct_format, **kwargs):
|
||||
return float.__new__(cls, cls._struct_value(struct_format, context, layer_name, offset, symbol_name))
|
||||
|
||||
def write(self, value):
|
||||
"""Writes the object into the layer of the context at the current offset"""
|
||||
if isinstance(value, float):
|
||||
data = struct.pack(self._struct_format, value)
|
||||
return self._context.memory.write(self._layer_name, self._offset, data)
|
||||
raise TypeError("Float objects require a float to be written")
|
||||
|
||||
class Bytes(PrimitiveObject, bytes):
|
||||
"""Primitive Object that handles specific series of bytes"""
|
||||
|
||||
def __init__(self, context, layer_name, offset, symbol_name, size = None, parent = None, length = 1, **kwargs):
|
||||
bytes.__init__()
|
||||
PrimitiveObject.__init__(self, context, layer_name, offset, symbol_name, size, parent, struct_format = str(length) + 's')
|
||||
self.length = length
|
||||
|
||||
def __new__(cls, context, layer_name, offset, symbol_name, length = 1, **kwargs):
|
||||
return bytes.__new__(cls, cls._struct_value(str(length) + "s", context, layer_name, offset, symbol_name))
|
||||
|
||||
def write(self, value):
|
||||
"""Writes the object into the layer of the context at the current offset"""
|
||||
if isinstance(value, bytes):
|
||||
data = struct.pack(self._struct_format, value)
|
||||
return self._context.memory.write(self._layer_name, self._offset, data)
|
||||
raise TypeError("Bytes objects require a bytes type to be written")
|
||||
|
||||
class String(PrimitiveObject, str):
|
||||
"""Primitive Object that handles string values
|
||||
|
||||
length: specifies the maximum possible length that the string could hold in memory
|
||||
"""
|
||||
|
||||
def __init__(self, context, layer_name, offset, symbol_name, size = None, parent = None, length = 1, **kwargs):
|
||||
str.__init__()
|
||||
PrimitiveObject.__init__(self, context, layer_name, offset, symbol_name, size, parent, struct_format = str(length) + 's')
|
||||
self.length = length
|
||||
|
||||
def __new__(cls, context, layer_name, offset, symbol_name, length = 1, **kwargs):
|
||||
return str.__new__(cls, cls._struct_value(str(length) + "s", context, layer_name, offset, symbol_name))
|
||||
|
||||
def write(self, value):
|
||||
"""Writes the object into the layer of the context at the current offset"""
|
||||
if isinstance(value, str):
|
||||
data = struct.pack(self._struct_format, value)
|
||||
return self._context.memory.write(self._layer_name, self._offset, data)
|
||||
raise TypeError("String objects require a string to be written")
|
||||
|
||||
class Pointer(Integer):
|
||||
"""Pointer which points to another object"""
|
||||
def __init__(self, context, layer_name, offset, symbol_name, size = None, parent = None, struct_format = None, target = None):
|
||||
@@ -147,6 +185,9 @@ class BitField(PrimitiveObject, int):
|
||||
return [arguments['target']]
|
||||
return []
|
||||
|
||||
def write(self, value):
|
||||
raise NotImplementedError("Writing to BitFields is not yet implemented")
|
||||
|
||||
class Enumeration(interfaces.objects.ObjectInterface):
|
||||
"""Returns an object made up of choices"""
|
||||
# FIXME: Add in body for the enumeration object
|
||||
@@ -154,6 +195,9 @@ class Enumeration(interfaces.objects.ObjectInterface):
|
||||
def template_children(cls, arguments):
|
||||
return []
|
||||
|
||||
def write(self, value):
|
||||
raise NotImplementedError("Writing to Enumerations is not yet implemented")
|
||||
|
||||
class Array(interfaces.objects.ObjectInterface, collections.Sequence):
|
||||
"""Object which can contain a fixed number of an object type"""
|
||||
def __init__(self, context, layer_name, offset, symbol_name, size = None, parent = None, count = 0, target = None):
|
||||
@@ -198,6 +242,9 @@ class Array(interfaces.objects.ObjectInterface, collections.Sequence):
|
||||
"""Returns the length of the array"""
|
||||
return self._count
|
||||
|
||||
def write(self, value):
|
||||
raise NotImplementedError("Writing to Arrays is not yet implemented")
|
||||
|
||||
class Struct(interfaces.objects.ObjectInterface):
|
||||
"""Object which can contain members that are other objects"""
|
||||
|
||||
@@ -253,3 +300,6 @@ class Struct(interfaces.objects.ObjectInterface):
|
||||
self._concrete_members[attr] = member
|
||||
return member
|
||||
raise AttributeError("'" + self._symbol_name + "' Struct has no attribute '" + attr + "'")
|
||||
|
||||
def write(self, value):
|
||||
raise TypeError("Structs cannot be written to directly, invidivual members must be written instead")
|
||||
|
||||
@@ -56,5 +56,5 @@ class ReferenceTemplate(interfaces.objects.Template):
|
||||
It should not return any attributes
|
||||
"""
|
||||
def __call__(self, context, *args, **kwargs):
|
||||
template = context.symbol_space.resolve(self._symbol_name)
|
||||
template = context.symbol_space.get_structure(self._symbol_name)
|
||||
return template(context = context, *args, **kwargs)
|
||||
|
||||
@@ -14,18 +14,18 @@ class SymbolSpace(collections.Mapping):
|
||||
proceed down through the ranks if a namespace isn't specified.
|
||||
"""
|
||||
|
||||
def __init__(self, native_symbols):
|
||||
if not isinstance(native_symbols, interfaces.symbols.NativeTableInterface):
|
||||
raise TypeError("SymbolSpace native_symbols must be NativeSymbolInterface")
|
||||
def __init__(self, native_structures):
|
||||
if not isinstance(native_structures, interfaces.symbols.NativeTableInterface):
|
||||
raise TypeError("SymbolSpace native_structures must be NativeSymbolInterface")
|
||||
self._dict = collections.OrderedDict()
|
||||
self._native_symbols = native_symbols
|
||||
self._native_structures = native_structures
|
||||
# Permanently cache all resolved symbols
|
||||
self._resolved = {}
|
||||
|
||||
@property
|
||||
def natives(self):
|
||||
"""Returns the native_types for this symbol space"""
|
||||
return self._native_symbols
|
||||
return self._native_structures
|
||||
|
||||
def __len__(self):
|
||||
return len(self._dict)
|
||||
@@ -46,7 +46,7 @@ class SymbolSpace(collections.Mapping):
|
||||
|
||||
def remove(self, key):
|
||||
"""Removes a named symbol_list from the space"""
|
||||
# Reset the resolved list, since we're removing ssome symbols
|
||||
# Reset the resolved list, since we're removing some symbols
|
||||
self._resolved = {}
|
||||
del self._dict[key]
|
||||
|
||||
@@ -56,16 +56,16 @@ class SymbolSpace(collections.Mapping):
|
||||
if len(symarr) == 2:
|
||||
table_name = symarr[0]
|
||||
symbol_name = symarr[1]
|
||||
return self._dict[table_name].resolve(symbol_name)
|
||||
elif symbol in self.natives:
|
||||
return self.natives.resolve(symbol)
|
||||
raise exceptions.SymbolNotFoundException("Malformed symbol name")
|
||||
return self._dict[table_name].get_structure(symbol_name)
|
||||
elif symbol in self.natives.structures:
|
||||
return self.natives.get_structure(symbol)
|
||||
raise exceptions.SymbolError("Malformed symbol name")
|
||||
|
||||
def resolve(self, symbol):
|
||||
def get_structure(self, symbol):
|
||||
"""Takes a symbol name and resolves it
|
||||
|
||||
This method ensures that all referenced templatess (inlcuding self-referential templates)
|
||||
are satifsfied as ObjectTemplates
|
||||
This method ensures that all referenced templates (including self-referential templates)
|
||||
are satisfied as ObjectTemplates
|
||||
"""
|
||||
# Traverse down any resolutions
|
||||
if symbol not in self._resolved:
|
||||
|
||||
@@ -17,18 +17,18 @@ class NativeTable(interfaces.symbols.NativeTableInterface):
|
||||
native_class, _native_struct = self._native_dictionary[native_type]
|
||||
self._overrides[native_type] = native_class
|
||||
# Create this once early, because it may get used a lot
|
||||
self._symbols = set(self._native_dictionary.keys()).union(set(['Enumeration', 'array', 'BitField', 'void', 'pointer']))
|
||||
self._structures = set(self._native_dictionary.keys()).union(set(['Enumeration', 'array', 'BitField', 'void', 'pointer']))
|
||||
|
||||
def get_symbol_class(self, symbol):
|
||||
ntype, fmt = native_types.get(symbol, (objects.Integer, ''))
|
||||
def get_structure_class(self, name):
|
||||
ntype, fmt = native_types.get(name, (objects.Integer, ''))
|
||||
return ntype
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
"""Returns an iterator of the symbol names"""
|
||||
return self._symbols
|
||||
def structures(self):
|
||||
"""Returns an iterator of the structure symbol names"""
|
||||
return self._structures
|
||||
|
||||
def resolve(self, symbol_name):
|
||||
def get_structure(self, symbol_name):
|
||||
"""Resolves a symbol name into an object template
|
||||
|
||||
symbol_space is used to resolve any target symbols if they don't exist in this list
|
||||
@@ -37,16 +37,16 @@ class NativeTable(interfaces.symbols.NativeTableInterface):
|
||||
if symbol_name == 'void':
|
||||
return objects.templates.ObjectTemplate(objects.Void, symbol_name = symbol_name)
|
||||
elif symbol_name == 'array':
|
||||
return objects.templates.ObjectTemplate(objects.Array, symbol_name = symbol_name, count = 0, target = self.resolve('void'))
|
||||
return objects.templates.ObjectTemplate(objects.Array, symbol_name = symbol_name, count = 0, target = self.get_structure('void'))
|
||||
elif symbol_name == 'Enumeration':
|
||||
return objects.templates.ObjectTemplate(objects.Enumeration, symbol_name = symbol_name, target = self.resolve('void'), choices = {})
|
||||
return objects.templates.ObjectTemplate(objects.Enumeration, symbol_name = symbol_name, target = self.get_structure('void'), choices = {})
|
||||
elif symbol_name == 'BitField':
|
||||
return objects.templates.ObjectTemplate(objects.BitField, symbol_name = symbol_name, start_bit = 0, end_bit = 0)
|
||||
|
||||
_native_type, native_format = self._native_dictionary[symbol_name]
|
||||
if symbol_name == 'pointer':
|
||||
additional = {'target': self.resolve('void')}
|
||||
return objects.templates.ObjectTemplate(self.get_symbol_class(symbol_name), #pylint: disable-msg=W0142
|
||||
additional = {'target': self.get_structure('void')}
|
||||
return objects.templates.ObjectTemplate(self.get_structure_class(symbol_name), #pylint: disable-msg=W0142
|
||||
symbol_name = symbol_name,
|
||||
struct_format = native_format,
|
||||
**additional)
|
||||
|
||||
@@ -36,22 +36,22 @@ from volatility.framework import exceptions, objects, interfaces
|
||||
class VTypeSymbolTable(interfaces.symbols.SymbolTableInterface):
|
||||
"""Symbol Table that handles vtype datastructures"""
|
||||
|
||||
def __init__(self, name, vtype_dictionary, native_symbols = None):
|
||||
interfaces.symbols.SymbolTableInterface.__init__(self, name, native_symbols)
|
||||
def __init__(self, name, vtype_dictionary, native_structures = None):
|
||||
interfaces.symbols.SymbolTableInterface.__init__(self, name, native_structures)
|
||||
self._vtypedict = vtype_dictionary
|
||||
self._overrides = {}
|
||||
|
||||
def get_symbol_class(self, symbol):
|
||||
return self._overrides.get(symbol, objects.Struct)
|
||||
def get_structure_class(self, name):
|
||||
return self._overrides.get(name, objects.Struct)
|
||||
|
||||
def set_symbol_class(self, symbol, clazz):
|
||||
if symbol not in self.symbols:
|
||||
raise ValueError("Symbol " + symbol + " not in " + self.name + " SymbolTable")
|
||||
self._overrides[symbol] = clazz
|
||||
def set_structure_class(self, name, clazz):
|
||||
if name not in self.structures:
|
||||
raise ValueError("Symbol " + name + " not in " + self.name + " SymbolTable")
|
||||
self._overrides[name] = clazz
|
||||
|
||||
def del_symbol_class(self, symbol):
|
||||
if symbol in self._overrides:
|
||||
del self._overrides[symbol]
|
||||
def del_structure_class(self, name):
|
||||
if name in self._overrides:
|
||||
del self._overrides[name]
|
||||
|
||||
def _vtypedict_to_template(self, dictionary):
|
||||
"""Converts a vtypedict into an object template"""
|
||||
@@ -60,9 +60,9 @@ class VTypeSymbolTable(interfaces.symbols.SymbolTableInterface):
|
||||
|
||||
symbol_name = dictionary[0]
|
||||
|
||||
if symbol_name in self.natives:
|
||||
if symbol_name in self.natives.structures:
|
||||
# The symbol is a native type
|
||||
native_template = self.natives.resolve(symbol_name)
|
||||
native_template = self.natives.get_structure(symbol_name)
|
||||
|
||||
# Add specific additional parameters, etc
|
||||
update = {}
|
||||
@@ -87,19 +87,19 @@ class VTypeSymbolTable(interfaces.symbols.SymbolTableInterface):
|
||||
return objects.templates.ReferenceTemplate(symbol_name = self.name + "!" + symbol_name)
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
def structures(self):
|
||||
"""Returns an iterator of the symbol names"""
|
||||
return self._vtypedict.keys()
|
||||
|
||||
def resolve(self, symbol_name):
|
||||
def get_structure(self, symbol_name):
|
||||
"""Resolves an individual symbol"""
|
||||
if symbol_name not in self._vtypedict:
|
||||
raise exceptions.SymbolNotFoundException
|
||||
raise exceptions.SymbolError
|
||||
size, curdict = self._vtypedict[symbol_name]
|
||||
members = {}
|
||||
for member_name in curdict:
|
||||
relative_offset, vtypedict = curdict[member_name]
|
||||
member = (relative_offset, self._vtypedict_to_template(vtypedict))
|
||||
members[member_name] = member
|
||||
object_class = self.get_symbol_class(symbol_name)
|
||||
object_class = self.get_structure_class(symbol_name)
|
||||
return objects.templates.ObjectTemplate(object_class = object_class, symbol_name = symbol_name, size = size, members = members)
|
||||
|
||||
Reference in New Issue
Block a user