rust: yara SIGSEGV
kaarposoft opened this issue ยท 12 comments
TL;DR: using MemProcFS Yara from Rust results in SIGSEGV
I am using yarac 4.5.0 (https://github.com/VirusTotal/yara/archive/refs/tags/v4.5.0.tar.gz compiled from source)
to compile the YARA forge 20240505 core rule set (https://github.com/YARAHQ/yara-forge/releases/download/20240505/yara-forge-rules-core.zip)
Scanning a memory image with yara works fine:
/opt/ez/yara/yara/yara -sS -C investigation_405308_memprocfs_forensics_2568.tmp/rules.yc 1_mem_DESKTOP-I45312N/memdumpX64.dmp
size of AC transition table : 228985
average length of AC matches lists : 1.500630
number of rules : 6451
number of strings : 38715
number of AC matches : 77365
number of AC matches in root node : 0
number of AC matches in top 100 longest lists
1: 238
[...]
ELASTIC_Macos_Backdoor_Keyboardrecord_832F7Bac 1_mem_DESKTOP-I45312N/memdumpX64.dmp
[...]
With MemProcFs version 5.9.13, running and mounting with memprocfs also works fine:
/opt/ez/memprocfs/memprocfs -f 1_mem_DESKTOP-I45312N/memdumpX64.dmp -forensic-yara-rules investigation_405308_memprocfs_forensics_2568.tmp/rules.yc -forensic 1 -disable-python -mount ~/mnt
Initialized 64-bit Windows 10.0.22621
============================== MemProcFS ==============================
- Author: Ulf Frisk - pcileech@frizk.net
- Info: https://github.com/ufrisk/MemProcFS
[...]
- Version: 5.9.13 (Linux)
[...]
- Tag: 22621_c1c0d8af
- Operating System: Windows 10.0.22621 (X64)
==========================================================================
[FORENSIC] Forensic mode completed in 23s.
However, running a Rust program using memprocfs = "5.9.6" results in 11 (SIGSEGV) (core dumped) after analyzing 4.7 GiB of the 8GiB image.
I have tried with the pre-compiled https://github.com/ufrisk/MemProcFS/releases/download/v5.9/MemProcFS_files_and_binaries_v5.9.13-linux_x64-20240510.tar.gz
as well as with locally build MemProcFs, LeechCore and vmmyara.
Building the Rust program in debug mode and locally build MemProcFs, LeechCore and vmmyara with export CFLAGS=-g, gdb shows this in the core file:
coredumpctl debug
[...]
Message: Process 222722 (ez) of user 1000 dumped core.
[...]
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
[...]
Program terminated with signal SIGSEGV, Segmentation fault.
#0 __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:74
74 ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.
[Current thread is 1 (Thread 0x7a186f98c640 (LWP 222758))]
[...]
(gdb) bt
#0 __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:74
#1 0x000062b66de7c223 in core::ffi::c_str::const_strlen::strlen_rt (s=<optimized out>, s=<optimized out>, s=<optimized out>, s=<optimized out>, s=<optimized out>)
at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ffi/c_str.rs:717
#2 core::ffi::c_str::const_strlen (ptr=<optimized out>, ptr=<optimized out>, ptr=<optimized out>, ptr=<optimized out>, ptr=<optimized out>)
at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ffi/c_str.rs:721
#3 core::ffi::c_str::CStr::from_ptr (ptr=<optimized out>, ptr=<optimized out>, ptr=<optimized out>, ptr=<optimized out>, ptr=<optimized out>)
at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ffi/c_str.rs:265
#4 memprocfs::VmmYara::impl_yara_cb (ctx=0x7ffcbb2c6eb0, yrm=0x7a186f989fa0, _pb_buffer=<optimized out>, _cb_buffer=<optimized out>) at src/lib_memprocfs.rs:7744
#5 0x00007a188056a4e6 in VmmSearch_SearchRegion_YaraCB (pvContext=0x7ffcbb2c6eb0, pRuleMatch=0x7a186f989fa0, pbBuffer=0x7a17ec000cbc "", cbBuffer=1048576)
at vmmyarautil.c:514
#6 0x00007a186e90723f in VmmYara_ScanMemoryCB (context=0x7a17ef284f70, message=<optimized out>, rule=0x7a17ec121b80, pContextCB=0x7a186f98aaf0) at vmmyara.c:339
#7 0x00007a186e911c5e in yr_scanner_scan_mem_blocks () from /opt/ez/MemProcFS/files/vmmyara.so
#8 0x00007a186e912198 in yr_scanner_scan_mem () from /opt/ez/MemProcFS/files/vmmyara.so
#9 0x00007a186e9106c7 in yr_rules_scan_mem () from /opt/ez/MemProcFS/files/vmmyara.so
#10 0x00007a186e90798a in VmmYara_ScanMemory (hVmmYaraRules=<optimized out>, pbBuffer=<optimized out>, cbBuffer=<optimized out>, flags=<optimized out>,
pfnCallback=<optimized out>, pvContext=<optimized out>, timeout=0) at vmmyara.c:368
#11 0x00007a188056c34e in VmmYara_ScanMemory (hVmmYaraRules=0x7a17eefb7dd0, pbBuffer=0x7a17ec000cbc "", cbBuffer=1048576, flags=9,
pfnCallback=0x7a188056a465 <VmmSearch_SearchRegion_YaraCB>, pvContext=0x7ffcbb2c6eb0, timeout=0) at vmmyarawrap.c:206
#12 0x00007a188056ba81 in VmmYaraUtil_SearchRegion (H=H@entry=0x7a18802ee010, ctxi=ctxi@entry=0x7a17ec000ca0, ctxs=ctxs@entry=0x7ffcbb2c6eb0) at vmmyarautil.c:537
#13 0x00007a188056badf in VmmYaraUtil_SearchRange (H=H@entry=0x7a18802ee010, ctxi=ctxi@entry=0x7a17ec000ca0, ctxs=ctxs@entry=0x7ffcbb2c6eb0, vaMax=10737418239)
at vmmyarautil.c:557
#14 0x00007a188056bebb in VmmYaraUtil_SearchSingleProcess (H=H@entry=0x7a18802ee010, pProcess=pProcess@entry=0x0, ctxs=ctxs@entry=0x7ffcbb2c6eb0,
ppObAddressResult=ppObAddressResult@entry=0x7a186f98ac30) at vmmyarautil.c:672
#15 0x00007a1880476d8b in VMMDLL_YaraSearch_Impl (H=H@entry=0x7a18802ee010, dwPID=dwPID@entry=4294967295, pYaraConfig=pYaraConfig@entry=0x7ffcbb2c6eb0,
ppva=ppva@entry=0x0, pcva=pcva@entry=0x0) at vmmdll.c:877
#16 0x00007a1880476ec2 in VMMDLL_YaraSearch (H=0x7a18802ee010, dwPID=4294967295, pYaraConfig=0x7ffcbb2c6eb0, ppva=0x0, pcva=0x0) at vmmdll.c:900
#17 0x000062b66de81d86 in memprocfs::{impl#87}::impl_start::{closure#0} () at src/lib_memprocfs.rs:7412
#18 std::sys_common::backtrace::__rust_begin_short_backtrace<memprocfs::{impl#87}::impl_start::{closure_env#0}, bool> (
f=<error reading variable: Cannot access memory at address 0x0>) at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/sys_common/backtrace.rs:155
#19 0x000062b66de80f0a in std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure#0}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool> ()
at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/thread/mod.rs:529
#20 core::panic::unwind_safe::{impl#23}::call_once<bool, std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool>> (self=...) at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/panic/unwind_safe.rs:272
#21 std::panicking::try::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool>>, bool> (data=<optimized out>) at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/panicking.rs:554
#22 std::panicking::try<bool, core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool>>> (f=...) at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/panicking.rs:518
#23 std::panic::catch_unwind<core::panic::unwind_safe::AssertUnwindSafe<std::thread::{impl#0}::spawn_unchecked_::{closure#1}::{closure_env#0}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool>>, bool> (f=...) at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/panic.rs:142
#24 std::thread::{impl#0}::spawn_unchecked_::{closure#1}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool> ()
at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/std/src/thread/mod.rs:528
#25 core::ops::function::FnOnce::call_once<std::thread::{impl#0}::spawn_unchecked_::{closure_env#1}<memprocfs::{impl#93}::impl_start::{closure_env#0}, bool>, ()>
() at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ops/function.rs:250
--Type <RET> for more, q to quit, c to continue without paging--
#26 0x000062b66e86f765 in alloc::boxed::{impl#47}::call_once<(), dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global> ()
at library/alloc/src/boxed.rs:2015
#27 alloc::boxed::{impl#47}::call_once<(), alloc::boxed::Box<dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global>, alloc::alloc::Global> ()
at library/alloc/src/boxed.rs:2015
#28 std::sys::pal::unix::thread::{impl#2}::new::thread_start () at library/std/src/sys/pal/unix/thread.rs:108
#29 0x00007a1880694ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#30 0x00007a1880726850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
Considering that MemProcFS is now version 5.9.13, whereas crates.io shows 5.9.6, I have also tried with
memprocfs = { path = "/opt/ez/MemProcFS/vmmrust/memprocfs" }
referencing the latest memprocfs (5.9.13)
But I still get the SIGSEGV
I might try to debug this further, but my knowledge of the MemProcFs and Yara codebases is very limited.
Any help you could provide to try to debug and fix this issue would be much appreciated.
There seems to be some issue with the c_str conversion possibly.
Maybe it's null? Or maybe it's not a valid utf-8 sequence? But I don't understand why Rust would crash in that case, I thought it would check against at least null and at least panic? and handle non-valid utf-8 byte sequences.
I haven't been able to trigger the issue. But I suspect it may be some mistakes in the rust wrapper library:
MemProcFS/vmmrust/memprocfs/src/lib_memprocfs.rs
Line 7746 in 810c3f3
I should probably add in some null checks before using that string.
I'm assuming you're using the API to perform a yara search, and that it does not crash by itself if doing a yara search with the forensics mode? The trace you posted indicate you use the api.
That is correct. When running directly with memprocfs -f [...] -forensic-yara-rules [...] -forensic 1 disable-python -mount [...]
then memprocfs does NOT crash. When using the rust API it DOES crash.
The code you mention in MemProcFS/vmmrust/memprocfs/src/lib_memprocfs.rs
line 7746 is wrapped in an usafe block starting at line 7717. Also CStr::from_ptr is itself unsafe.
This basically means, that you are responsible for ensuring memory safety, not rust!
In particular, before the the call to pub unsafe from_ptr<'a>(ptr: *const i8) -> &'a CStr
you must ensure:
- The memory pointed to by ptr must contain a valid nul terminator at the end of the string.
- ptr must be valid for reads of bytes up to and including the nul terminator.
- The memory referenced by the returned CStr must not be mutated for the duration of lifetime 'a.
- The nul terminator must be within isize::MAX from ptr
I have now compiled memprocfs with export CFLAGS="-g -O0"
, and the stack trace now indeed shows a null pointer: CStr::from_ptr (ptr=0x0)
Trace:
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:74
#1 0x00005b61a383f4d3 in core::ffi::c_str::const_strlen::strlen_rt ()
at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ffi/c_str.rs:717
#2 core::ffi::c_str::const_strlen () at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ffi/c_str.rs:721
#3 core::ffi::c_str::CStr::from_ptr (ptr=0x0) at /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04/library/core/src/ffi/c_str.rs:265
#4 memprocfs::VmmYara::impl_yara_cb (ctx=0x7ffdcfb567b0, yrm=0x7aaf889fcfa0, _pb_buffer=<optimized out>, _cb_buffer=<optimized out>)
at src/lib_memprocfs.rs:7746
#5 0x00007aaf9936a4e6 in VmmSearch_SearchRegion_YaraCB (pvContext=0x7ffdcfb567b0, pRuleMatch=0x7aaf889fcfa0, pbBuffer=0x7aaf34000cbc "",
cbBuffer=1048576) at vmmyarautil.c:514
#6 0x00007aaf88eda23f in VmmYara_ScanMemoryCB (context=0x7aaf3724b730, message=<optimized out>, rule=0x7aaf34121b80,
pContextCB=0x7aaf889fdaf0) at vmmyara.c:339
#7 0x00007aaf88ee4c5e in yr_scanner_scan_mem_blocks () from /opt/ez/MemProcFS/files/vmmyara.so
#8 0x00007aaf88ee5198 in yr_scanner_scan_mem () from /opt/ez/MemProcFS/files/vmmyara.so
[...]
I'll add an extra nullptr check and hope it resolves the issue. I'm kind of doing it blindly here since I'm unable to replicate, but mbe I can simulate a nullptr with some code changes in the native version...
I'll post here when its updated.
This seems to fix the issue:
index 12caa3e..552a3f2 100644
--- a/vmmrust/memprocfs/src/lib_memprocfs.rs
+++ b/vmmrust/memprocfs/src/lib_memprocfs.rs
@@ -7743,7 +7743,7 @@ impl VmmYara<'_> {
let mut match_strings = Vec::new();
let cmatch_strings = std::cmp::min((*yrm).cStrings as usize, 8);
for i in 0..cmatch_strings {
- let match_string = String::from(CStr::from_ptr((*yrm).strings[i].szString).to_str().unwrap_or(""));
+ let match_string = ((*yrm).strings[i].szString).as_ref().map_or_else(||"".to_string(), |p|String::from(CStr::from_ptr(p).to_str().unwrap_or("")));
let cmatch = std::cmp::min((*yrm).strings[i].cMatch as usize, 16);
let mut addresses = Vec::new();
for j in 0..cmatch {
Thanks for confirming. Ill fix this when I'm back home this evening.
Can you check if the issue is resolved in the latest MemProcFS at crates.io - 5.9.13? If the issue is resolved you may close this issue. And huge thanks for reporting and debugging ๐
I ended up creating two new helper functions, cstr_to_string and cstr_to_string_lossy
MemProcFS/vmmrust/memprocfs/src/lib_memprocfs.rs
Line 5090 in f99fe8e
cleaner code and increased overall stability just in case the same issue occurs somewhere else as well. an extra null check doesn't cost that much, and the code becomes a bit nicer to read as well.
Thank you for the fix. I will check tomorrow.
I can confirm that MemProcFS at crates.io version 5.9.13 solves the issue.
With this version, I no longer experience crashes when using MemProcFS yara from the Rust API.
Thanks a lot for the fix!
(For the next major release allowing API changes, I would suggest to change all the relevant String
to Option<String>
, as I believe None
in Rust would be a better representation of NULL
in C than the empty string)
Awesome and many thanks for confirming this issue is now resolved. It should be resolved in a bunch of other potential places as well. Many thanks for the debugging help as well ๐
I'm going to keep things as-is though. Having the empty string would more convenient for the user rather than having to deal with an Option in all kinds of structs in the absolute majority of cases.
If the user really really wish to check against the empty string it's possible to do it already, but I'm guessing that situation would be quite rare. Also there are many other places where the lossy utf8 conversion is made and there are bound to be all kinds of bad strings in memory analysis due to corrupt memory. It's not always possible to keep things perfect. And it's already possible to do the check...