ufrisk / MemProcFS

MemProcFS
GNU Affero General Public License v3.0
2.8k stars 352 forks source link

rust: yara SIGSEGV #291

Closed kaarposoft closed 1 month ago

kaarposoft commented 1 month ago

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.

ufrisk commented 1 month ago

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:

https://github.com/ufrisk/MemProcFS/blob/810c3f30cda07c03cadab18d2875802c99fe402b/vmmrust/memprocfs/src/lib_memprocfs.rs#L7746

I should probably add in some null checks before using that string.

ufrisk commented 1 month ago

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.

kaarposoft commented 1 month ago

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.

kaarposoft commented 1 month ago

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:

kaarposoft commented 1 month ago

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
[...]
ufrisk commented 1 month ago

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.

kaarposoft commented 1 month ago

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 {
ufrisk commented 1 month ago

Thanks for confirming. Ill fix this when I'm back home this evening.

ufrisk commented 1 month ago

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

https://github.com/ufrisk/MemProcFS/blob/f99fe8e5c23d923c0a04c842a72fbe45f71b3a17/vmmrust/memprocfs/src/lib_memprocfs.rs#L5090

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.

kaarposoft commented 1 month ago

Thank you for the fix. I will check tomorrow.

kaarposoft commented 1 month ago

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)

ufrisk commented 1 month ago

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...