Update base code for windows 7, 8, 8.1
Because the tag is different in lower version of Windows, need to
change the tag in scan algorithm
4b29cf1986/volatility/framework/plugins/windows/poolscanner.py (L229)
This commit is contained in:
parent
abb7a70b72
commit
8cb553eb11
@ -1,2 +1,5 @@
|
|||||||
[target.x86_64-pc-windows-msvc]
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
# CRT static to make run on machine without VC++
|
||||||
|
# https://github.com/rust-lang/rust/pull/66801#issuecomment-558947376
|
||||||
|
# >> "-Clink-args=/subsystem:console,5.02"
|
||||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||||
|
78
README.md
78
README.md
@ -1,6 +1,16 @@
|
|||||||
# LPUS (A live pool-tag scanning solution)
|
# LPUS (A live pool-tag scanning solution)
|
||||||
|
|
||||||
This is the frontend to the live pool tag scanning solution, the backend is a driver (which is now closed source).
|
This is the frontend to the live pool tag scanning solution, the backend is a
|
||||||
|
driver (which is now closed source).
|
||||||
|
|
||||||
|
Works on Windows 7 and above (Vista not tested, but 7 ok and 10 ok), and on x64
|
||||||
|
systems only. (I hardcoded the address as u64 so only 64 systems should run this).
|
||||||
|
|
||||||
|
> The binary is runable, without crashing. But I still need to add some
|
||||||
|
manual instructions on referencing the structs and offset on some places.
|
||||||
|
> Windows 10, versions 2018, 2019 and 2020 is tested and works.
|
||||||
|
|
||||||
|
Windows XP is not supported: Windows XP Win32Api is missing here and there.
|
||||||
|
|
||||||
## How this works
|
## How this works
|
||||||
|
|
||||||
@ -21,7 +31,7 @@ use lpus::{
|
|||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let mut driver = DriverState::new();
|
let mut driver = DriverState::new();
|
||||||
println!("NtLoadDriver() -> 0x{:x}", driver.startup());
|
println!("NtLoadDriver() -> 0x{:x}", driver.startup());
|
||||||
driver.scan_pool(b"Tag ", |pool_addr, header, data_addr| {
|
driver.scan_pool(b"Tag ", "_STRUCT_NAME", |pool_addr, header, data_addr| {
|
||||||
})?;
|
})?;
|
||||||
println!("NtUnloadDriver() -> 0x{:x}", driver.shutdown());
|
println!("NtUnloadDriver() -> 0x{:x}", driver.shutdown());
|
||||||
}
|
}
|
||||||
@ -33,3 +43,67 @@ Parsing the struct data is up to you.
|
|||||||
You can use `driver.deref_addr(addr, &value)` to dereference an address in kernel space
|
You can use `driver.deref_addr(addr, &value)` to dereference an address in kernel space
|
||||||
and `driver.pdb_store.get_offset_r("offset")?` to get an offset from PDB file.
|
and `driver.pdb_store.get_offset_r("offset")?` to get an offset from PDB file.
|
||||||
|
|
||||||
|
We also have a set of functions for scanning a specific tag/object.
|
||||||
|
|
||||||
|
- `pub fn scan_eprocess(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn scan_file(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn scan_ethread(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn scan_mutant(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn scan_driver(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn scan_kernel_module(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
|
||||||
|
And a list traversing the kernel object:
|
||||||
|
|
||||||
|
- `pub fn traverse_loadedmodulelist(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn traverse_activehead(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- missing symbols `pub fn traverse_afdendpoint(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn traverse_kiprocesslist(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn traverse_handletable(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
- `pub fn traverse_unloadeddrivers(driver: &DriverState) -> BoxResult<Vec<Value>>`
|
||||||
|
|
||||||
|
## Things to note
|
||||||
|
|
||||||
|
Right now, we only have one symbol file of ntoskrnl.exe. While we may need more
|
||||||
|
symbols, kernel32.sys, win32k.sys, tcpis.sys... This will be a future update
|
||||||
|
where symbols are combined into one big `HashMap` but still retain the module. I
|
||||||
|
haven't tested the debug symbols of others binary, I wonder if the PDB file even
|
||||||
|
exists.
|
||||||
|
|
||||||
|
The pdb file is not restricted in ntoskrnl.exe, I might need to split to a
|
||||||
|
smaller module or such.
|
||||||
|
|
||||||
|
Also the symbols list is parsed directly from the PDB file, but some structs
|
||||||
|
(like the callback routine members or network structs) are missing. Right now a
|
||||||
|
simple hardcoded to add in a struct member is used, but it would break if the
|
||||||
|
OS running have a different layout.
|
||||||
|
|
||||||
|
The HashMap of symbols/struct is now using string and u32 to store member
|
||||||
|
offset and types, this should be changed into something that would be type-safe
|
||||||
|
and more functional.
|
||||||
|
|
||||||
|
I also follow a few Volatility implementation on Rootkit, The art of Memory
|
||||||
|
forensics Chapter 13. Scanning in Windows 10 yields promising result, though I
|
||||||
|
haven't tested on any malware to see if we can have the "same" result.
|
||||||
|
|
||||||
|
At the pace of development, I seperate the binary to functionalities for
|
||||||
|
testing, I would add a CLI and a REPL.
|
||||||
|
|
||||||
|
One last thing, the backend doesn't have any check on address referencing, so
|
||||||
|
one may get a blue screen, eventhough I tried to avoid it, I'm not 100% sure it
|
||||||
|
would not crash the system.
|
||||||
|
|
||||||
|
## Future works
|
||||||
|
|
||||||
|
- [ ] An interactive repl (1)
|
||||||
|
- [ ] More kernel modules symbols (2)
|
||||||
|
- [ ] Implementation of more technique (reference Volatility here)
|
||||||
|
- [ ] Quick and easy way to add manual struct, symbols (3)
|
||||||
|
|
||||||
|
(1) This is quite hard to work out, because we have to make the *types* works.
|
||||||
|
The currently chosen repl is based on Lisp, because lisp is cool. If the repl
|
||||||
|
is online, we can combine everything into one binary.
|
||||||
|
|
||||||
|
(2) We may need to download it all and combine to one `HashMap`, with their
|
||||||
|
types as a specific struct. (Try to avoid string).
|
||||||
|
|
||||||
|
(3) Have no idea on this.
|
||||||
|
@ -160,9 +160,12 @@ impl fmt::Display for Address {
|
|||||||
if let Some(p) = &self.pointer {
|
if let Some(p) = &self.pointer {
|
||||||
write!(f, "*({}) + 0x{:x}", *p, self.offset)
|
write!(f, "*({}) + 0x{:x}", *p, self.offset)
|
||||||
}
|
}
|
||||||
else {
|
else if self.offset != 0 {
|
||||||
write!(f, "0x{:x} + 0x{:x}", self.base, self.offset)
|
write!(f, "0x{:x} + 0x{:x}", self.base, self.offset)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
write!(f, "0x{:x}", self.base)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,13 +191,12 @@ impl DriverState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let data_addr = Address::from_base(pool_addr.address() + pool_header_size);
|
let data_addr = Address::from_base(pool_addr.address() + pool_header_size);
|
||||||
|
|
||||||
let success = handler(pool_addr, &header, data_addr)?;
|
let success = handler(pool_addr, &header, data_addr)?;
|
||||||
if success {
|
if success {
|
||||||
ptr += chunk_size; /* skip this chunk */
|
ptr += chunk_size; // skip this chunk
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ptr += 0x4; /* search next */
|
ptr += 0x4; // search next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
@ -298,7 +297,7 @@ impl DriverState {
|
|||||||
pub fn get_nonpaged_range(&self, ntosbase: &Address) -> BoxResult<[Address; 2]> {
|
pub fn get_nonpaged_range(&self, ntosbase: &Address) -> BoxResult<[Address; 2]> {
|
||||||
// TODO: Add support for other Windows version here
|
// TODO: Add support for other Windows version here
|
||||||
match self.windows_ffi.short_version {
|
match self.windows_ffi.short_version {
|
||||||
WindowsVersion::Windows10FastRing => {
|
WindowsVersion::WindowsFastRing => {
|
||||||
let mistate = ntosbase.clone() + self.pdb_store.get_offset_r("MiState")?;
|
let mistate = ntosbase.clone() + self.pdb_store.get_offset_r("MiState")?;
|
||||||
let path_first_va: String = vec![
|
let path_first_va: String = vec![
|
||||||
"_MI_SYSTEM_INFORMATION",
|
"_MI_SYSTEM_INFORMATION",
|
||||||
@ -335,6 +334,13 @@ impl DriverState {
|
|||||||
let last_va = Address::from_base(self.decompose(&mistate, &path_last_va)?);
|
let last_va = Address::from_base(self.decompose(&mistate, &path_last_va)?);
|
||||||
Ok([first_va, last_va])
|
Ok([first_va, last_va])
|
||||||
},
|
},
|
||||||
|
WindowsVersion::Windows7 => {
|
||||||
|
let path_first_va = ntosbase.clone() + self.pdb_store.get_offset_r("MmNonPagedPoolStart")?;
|
||||||
|
let path_last_va = ntosbase.clone() + self.pdb_store.get_offset_r("MiNonPagedPoolEnd")?;
|
||||||
|
let first_va = Address::from_base(self.deref_addr_new(path_first_va.address()));
|
||||||
|
let last_va = Address::from_base(self.deref_addr_new(path_last_va.address()));
|
||||||
|
Ok([first_va, last_va])
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
Err("Windows version for nonpaged pool algorithm is not implemented".into())
|
Err("Windows version for nonpaged pool algorithm is not implemented".into())
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,10 @@ pub struct OffsetData {
|
|||||||
// TODO: Move to WindowsScanStrategy and return the corresponding struct base on Windows version
|
// TODO: Move to WindowsScanStrategy and return the corresponding struct base on Windows version
|
||||||
impl OffsetData {
|
impl OffsetData {
|
||||||
pub fn new(pdb_store: &PdbStore, windows_version: WindowsVersion) -> Self {
|
pub fn new(pdb_store: &PdbStore, windows_version: WindowsVersion) -> Self {
|
||||||
|
// TODO: Fix the backend so that only neccessary fields are used
|
||||||
|
// This is too much, most of the functionality has been move to the frontend
|
||||||
match windows_version {
|
match windows_version {
|
||||||
WindowsVersion::Windows10FastRing => Self {
|
WindowsVersion::WindowsFastRing => Self {
|
||||||
eprocess_name_offset: pdb_store.get_offset("_EPROCESS.ImageFileName").unwrap_or(0u64),
|
eprocess_name_offset: pdb_store.get_offset("_EPROCESS.ImageFileName").unwrap_or(0u64),
|
||||||
eprocess_link_offset: pdb_store.get_offset("_EPROCESS.ActiveProcessLinks").unwrap_or(0u64),
|
eprocess_link_offset: pdb_store.get_offset("_EPROCESS.ActiveProcessLinks").unwrap_or(0u64),
|
||||||
list_blink_offset: pdb_store.get_offset("_LIST_ENTRY.Blink").unwrap_or(0u64),
|
list_blink_offset: pdb_store.get_offset("_LIST_ENTRY.Blink").unwrap_or(0u64),
|
||||||
@ -51,6 +53,20 @@ impl OffsetData {
|
|||||||
large_page_size_offset: pdb_store.get_offset("PoolBigPageTableSize").unwrap_or(0u64),
|
large_page_size_offset: pdb_store.get_offset("PoolBigPageTableSize").unwrap_or(0u64),
|
||||||
pool_chunk_size: pdb_store.get_offset("_POOL_HEADER.struct_size").unwrap_or(0u64),
|
pool_chunk_size: pdb_store.get_offset("_POOL_HEADER.struct_size").unwrap_or(0u64),
|
||||||
},
|
},
|
||||||
|
WindowsVersion::Windows7 => Self {
|
||||||
|
eprocess_name_offset: pdb_store.get_offset("_EPROCESS.ImageFileName").unwrap_or(0u64),
|
||||||
|
eprocess_link_offset: pdb_store.get_offset("_EPROCESS.ActiveProcessLinks").unwrap_or(0u64),
|
||||||
|
list_blink_offset: pdb_store.get_offset("_LIST_ENTRY.Blink").unwrap_or(0u64),
|
||||||
|
process_head_offset: pdb_store.get_offset("PsActiveProcessHead").unwrap_or(0u64),
|
||||||
|
mistate_offset: pdb_store.get_offset("MiState").unwrap_or(0u64),
|
||||||
|
hardware_offset: pdb_store.get_offset("_MI_SYSTEM_INFORMATION.Hardware").unwrap_or(0u64),
|
||||||
|
system_node_offset: pdb_store.get_offset("_MI_HARDWARE_STATE.SystemNodeInformation").unwrap_or(0u64),
|
||||||
|
first_va_offset: pdb_store.get_offset("_MI_SYSTEM_NODE_INFORMATION.NonPagedPoolFirstVa").unwrap_or(0u64),
|
||||||
|
last_va_offset: pdb_store.get_offset("_MI_SYSTEM_NODE_INFORMATION.NonPagedPoolLastVa").unwrap_or(0u64),
|
||||||
|
large_page_table_offset: pdb_store.get_offset("PoolBigPageTable").unwrap_or(0u64),
|
||||||
|
large_page_size_offset: pdb_store.get_offset("PoolBigPageTableSize").unwrap_or(0u64),
|
||||||
|
pool_chunk_size: pdb_store.get_offset("_POOL_HEADER.struct_size").unwrap_or(0u64),
|
||||||
|
},
|
||||||
// TODO: Add other version of Windows here
|
// TODO: Add other version of Windows here
|
||||||
// TODO: Warn user of unknown windows version, because BSOD will occur
|
// TODO: Warn user of unknown windows version, because BSOD will occur
|
||||||
_ => Self {
|
_ => Self {
|
||||||
|
14
src/lib.rs
14
src/lib.rs
@ -143,10 +143,13 @@ pub fn scan_eprocess(driver: &DriverState) -> BoxResult<Vec<Value>> {
|
|||||||
|
|
||||||
let eprocess_ptr = &try_eprocess_ptr;
|
let eprocess_ptr = &try_eprocess_ptr;
|
||||||
|
|
||||||
|
println!("EPROCESS: 0x{:x}", eprocess_ptr.address());
|
||||||
|
|
||||||
let pid: u64 = driver.decompose(eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
let pid: u64 = driver.decompose(eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
||||||
let ppid: u64 = driver.decompose(eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
let ppid: u64 = driver.decompose(eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
||||||
let image_name: Vec<u8> = driver.decompose_array(eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
let image_name: Vec<u8> = driver.decompose_array(eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
||||||
let unicode_str_ptr = driver.address_of(eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?;
|
let unicode_str_ptr = driver.address_of(eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")
|
||||||
|
.unwrap_or(0); // ImageFilePointer is Windows 10+
|
||||||
|
|
||||||
let eprocess_name =
|
let eprocess_name =
|
||||||
if let Ok(name) = from_utf8(&image_name) {
|
if let Ok(name) = from_utf8(&image_name) {
|
||||||
@ -525,7 +528,8 @@ pub fn traverse_activehead(driver: &DriverState) -> BoxResult<Vec<Value>> {
|
|||||||
let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
||||||
let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
||||||
let image_name: Vec<u8> = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
let image_name: Vec<u8> = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
||||||
let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?;
|
let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let eprocess_name =
|
let eprocess_name =
|
||||||
if let Ok(name) = from_utf8(&image_name) {
|
if let Ok(name) = from_utf8(&image_name) {
|
||||||
@ -608,7 +612,8 @@ pub fn traverse_kiprocesslist(driver: &DriverState) -> BoxResult<Vec<Value>> {
|
|||||||
let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
||||||
let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
||||||
let image_name: Vec<u8> = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
let image_name: Vec<u8> = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
||||||
let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?;
|
let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let eprocess_name =
|
let eprocess_name =
|
||||||
if let Ok(name) = from_utf8(&image_name) {
|
if let Ok(name) = from_utf8(&image_name) {
|
||||||
@ -652,7 +657,8 @@ pub fn traverse_handletable(driver: &DriverState) -> BoxResult<Vec<Value>> {
|
|||||||
let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
let pid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.UniqueProcessId")?;
|
||||||
let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
let ppid: u64 = driver.decompose(&eprocess_ptr, "_EPROCESS.InheritedFromUniqueProcessId")?;
|
||||||
let image_name: Vec<u8> = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
let image_name: Vec<u8> = driver.decompose_array(&eprocess_ptr, "_EPROCESS.ImageFileName", 15)?;
|
||||||
let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")?;
|
let unicode_str_ptr = driver.address_of(&eprocess_ptr, "_EPROCESS.ImageFilePointer.FileName")
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let eprocess_name =
|
let eprocess_name =
|
||||||
if let Ok(name) = from_utf8(&image_name) {
|
if let Ok(name) = from_utf8(&image_name) {
|
||||||
|
@ -29,14 +29,17 @@ const STR_DRIVER_REGISTRY_PATH: &str = "\\Registry\\Machine\\System\\CurrentCont
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum WindowsVersion {
|
pub enum WindowsVersion {
|
||||||
|
Windows7,
|
||||||
|
Windows8,
|
||||||
|
Windows10Legacy,
|
||||||
Windows10_2015,
|
Windows10_2015,
|
||||||
Windows10_2016,
|
Windows10_2016,
|
||||||
Windows10_2017,
|
Windows10_2017,
|
||||||
Windows10_2018,
|
Windows10_2018,
|
||||||
Windows10_2019,
|
Windows10_2019,
|
||||||
Windows10_2020,
|
Windows10_2020,
|
||||||
Windows10FastRing,
|
WindowsFastRing,
|
||||||
Windows10VersionUnknown
|
WindowsUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -142,11 +145,19 @@ impl WindowsFFI {
|
|||||||
rtl_get_version(&mut version_info);
|
rtl_get_version(&mut version_info);
|
||||||
|
|
||||||
let short_version = match version_info.dwBuildNumber {
|
let short_version = match version_info.dwBuildNumber {
|
||||||
|
// 2600 => WindowsVersion::WindowsXP,
|
||||||
|
// 6000 | 6001 | 6002 => WindowsVersion::WindowsVista,
|
||||||
|
7600 | 7601 => WindowsVersion::Windows7,
|
||||||
|
9200 | 9600 => WindowsVersion::Windows8,
|
||||||
|
10240 => WindowsVersion::Windows10Legacy,
|
||||||
|
10586 => WindowsVersion::Windows10_2015,
|
||||||
|
14393 => WindowsVersion::Windows10_2016,
|
||||||
|
15063 | 16299 => WindowsVersion::Windows10_2017,
|
||||||
17134 | 17763 => WindowsVersion::Windows10_2018,
|
17134 | 17763 => WindowsVersion::Windows10_2018,
|
||||||
18362 | 18363 => WindowsVersion::Windows10_2019,
|
18363 | 18362 => WindowsVersion::Windows10_2019,
|
||||||
19041 => WindowsVersion::Windows10_2020,
|
19041 => WindowsVersion::Windows10_2020,
|
||||||
_ if version_info.dwBuildNumber >= 19536 => WindowsVersion::Windows10FastRing,
|
x if x >= 19536 => WindowsVersion::WindowsFastRing,
|
||||||
_ => WindowsVersion::Windows10VersionUnknown
|
_ => WindowsVersion::WindowsUnknown
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
Loading…
Reference in New Issue
Block a user