This commit is contained in:
pozm 2024-02-11 18:37:49 +00:00
parent 4183549d17
commit c8606ec99b
No known key found for this signature in database
GPG Key ID: 5AB655AFC8AAA822
6 changed files with 289 additions and 48 deletions

222
Cargo.lock generated
View File

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.79" version = "1.0.79"
@ -63,6 +72,25 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -110,6 +138,25 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "cstr" name = "cstr"
version = "0.2.11" version = "0.2.11"
@ -130,6 +177,37 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "dll-syringe" name = "dll-syringe"
version = "0.15.2" version = "0.15.2"
@ -175,13 +253,20 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "gdke" name = "gdke"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dll-syringe", "dll-syringe",
"poggers", "poggers",
"rust-embed",
"windows", "windows",
] ]
@ -204,12 +289,36 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]] [[package]]
name = "goblin" name = "goblin"
version = "0.6.1" version = "0.6.1"
@ -312,6 +421,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "libredox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
"bitflags 2.4.1",
"libc",
"redox_syscall",
]
[[package]] [[package]]
name = "libudis86-sys" name = "libudis86-sys"
version = "0.2.1" version = "0.2.1"
@ -418,6 +538,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "path-absolutize" name = "path-absolutize"
version = "3.1.1" version = "3.1.1"
@ -513,12 +639,34 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.7.1" version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [ dependencies = [
"aho-corasick",
"memchr",
"regex-syntax", "regex-syntax",
] ]
@ -556,6 +704,42 @@ dependencies = [
"slice-pool2", "slice-pool2",
] ]
[[package]]
name = "rust-embed"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"shellexpand",
"syn 2.0.48",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
dependencies = [
"globset",
"sha2",
"walkdir",
]
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@ -624,6 +808,26 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shellexpand"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
dependencies = [
"dirs",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.2.0" version = "1.2.0"
@ -761,6 +965,22 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "which" name = "which"
version = "4.4.2" version = "4.4.2"

View File

@ -1,7 +1,7 @@
workspace = { members = ["gdkeinj"] } workspace = { members = ["gdkeinj"] }
[package] [package]
name = "gdke" name = "gdke"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
[profile.release] [profile.release]
@ -19,6 +19,10 @@ strip = false
# members = ["gdke-gui"] # members = ["gdke-gui"]
[dependencies] [dependencies]
rust-embed = { version = "*", features = [
"include-exclude",
"interpolate-folder-path",
] }
anyhow = "*" anyhow = "*"
windows = { features = [ windows = { features = [
"Win32_Foundation", "Win32_Foundation",

View File

@ -23,21 +23,20 @@ pub fn main() {
("E8 ? ? ? ? 85 C0 0F 84 ? ? ? ? 49 8B 8C 24 ? ? ? ?", -0x3c), ("E8 ? ? ? ? 85 C0 0F 84 ? ? ? ? 49 8B 8C 24 ? ? ? ?", -0x3c),
); );
let sock = UdpSocket::bind("127.0.0.1:29849").unwrap(); let sock = UdpSocket::bind("127.0.0.1:29849").unwrap();
let mut buf = [1; 1];
sock.connect("127.0.0.1:28713").expect("uanble to connect"); sock.connect("127.0.0.1:28713").expect("uanble to connect");
let proc = Process::this_process(); let proc = Process::this_process();
let modd = proc.get_base_module().unwrap(); let modd = proc.get_base_module().unwrap();
println!("sending data, waiting for sig ver"); println!("sending data, waiting for sig ver");
sock.send(&buf); let buf = [1; 1];
sock.send(&buf).ok();
let mut sig_type = [0; 4]; let mut sig_type = [0; 4];
sock.recv(&mut sig_type); sock.recv(&mut sig_type).unwrap();
let int_sig = u32::from_ne_bytes(sig_type); let int_sig = u32::from_ne_bytes(sig_type);
let sig = sigs.get(&int_sig).expect("sig type match not compatible"); let sig = sigs.get(&int_sig).expect("sig type match not compatible");
let mut addr = modd.scan(sig.0).unwrap().unwrap() as isize; //+ sig.1 as isize; let addr = modd.scan(sig.0).unwrap().unwrap() as isize;
// addr += sig.1 as isize;
let ptr_to_fn = (addr as usize + size_of::<u8>()) as *const u8; let ptr_to_fn = (addr as usize + size_of::<u8>()) as *const u8;
let mut addr_offset = [0; 4]; let mut addr_offset = [0; 4];
unsafe { std::ptr::copy(ptr_to_fn, addr_offset.as_mut_ptr(), 4) }; unsafe { std::ptr::copy(ptr_to_fn, addr_offset.as_mut_ptr(), 4) };
@ -50,16 +49,17 @@ pub fn main() {
unsafe { unsafe {
let open_and_parse = std::mem::transmute::<isize, open_and_parse_t>(fn_ptr as isize); let open_and_parse = std::mem::transmute::<isize, open_and_parse_t>(fn_ptr as isize);
let opp = OpenAndParse let opp = OpenAndParse
.initialize(open_and_parse, move |this, base, key, mode| { .initialize(open_and_parse, move |_, _, key, _| {
let mut read_key = [0u8; 32]; let mut read_key = [0u8; 32];
let ptr_to_key = (key as usize + 8) as *const *const u8; let ptr_to_key = (key as usize + 8) as *const *const u8;
std::ptr::copy(*ptr_to_key, read_key.as_mut_ptr(), 32); std::ptr::copy(*ptr_to_key, read_key.as_mut_ptr(), 32);
sock2.send(read_key.as_slice()); sock2.send(read_key.as_slice()).unwrap();
std::thread::sleep(Duration::from_secs(1000))
// panic!("good ridance.") // panic!("good ridance.")
}) })
.unwrap(); .unwrap();
opp.enable(); opp.enable().expect("failed to enable detour");
println!("detour enabled {}", opp.is_enabled()); println!("detour enabled {}", opp.is_enabled());
} }
sock.send(&[]); sock.send(&[]).ok();
} }

View File

@ -1,26 +1,23 @@
pub mod versioning; pub mod versioning;
use std::{ use std::{
error::Error,
ffi::{c_void, CStr, CString}, ffi::{c_void, CStr, CString},
io::Read, io::Write,
mem::{size_of, transmute}, mem::{size_of, transmute},
net::UdpSocket, net::UdpSocket,
path::Path, path::Path,
ptr::{addr_of, null, null_mut},
time::Duration,
}; };
use dll_syringe::{process::OwnedProcess, Syringe}; use dll_syringe::{process::OwnedProcess, Syringe};
use poggers::{structures::process::Process, traits::Mem}; use poggers::{exports::HANDLE, structures::process::Process, traits::Mem};
use rust_embed::RustEmbed;
use windows::{ use windows::{
core::{PCSTR, PSTR}, core::{PCSTR, PSTR},
Win32::{ Win32::{
Foundation::{BOOL, HINSTANCE}, Foundation::BOOL,
System::{ System::{
ProcessStatus::{K32GetModuleInformation, MODULEINFO},
SystemServices::IMAGE_DOS_HEADER, SystemServices::IMAGE_DOS_HEADER,
Threading::{ Threading::{
CreateProcessA, TerminateProcess, CREATE_SUSPENDED, PEB, PROCESS_BASIC_INFORMATION, CreateProcessA, TerminateProcess, CREATE_SUSPENDED, PROCESS_BASIC_INFORMATION,
PROCESS_INFORMATION, STARTUPINFOA, PROCESS_INFORMATION, STARTUPINFOA,
}, },
}, },
@ -28,23 +25,33 @@ use windows::{
}; };
use windows::{ use windows::{
Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}, Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation},
Win32::System::{ Win32::System::{Diagnostics::Debug::IMAGE_NT_HEADERS64, Threading::ResumeThread},
Diagnostics::Debug::{GetThreadContext, CONTEXT, IMAGE_NT_HEADERS64},
Threading::{ResumeThread, SuspendThread},
},
}; };
fn create_pstr(c_str: &CStr) -> PSTR { fn create_pstr(c_str: &CStr) -> PSTR {
PSTR::from_raw(c_str.as_ptr() as *mut u8) PSTR::from_raw(c_str.as_ptr() as *mut u8)
} }
#[derive(RustEmbed)]
#[folder = "$CARGO_MANIFEST_DIR/target/release"]
#[include = "gdkeinj.dll"]
struct GdkeInj;
pub unsafe fn spawn_and_inject(proc: &str) { struct ProcKiller(HANDLE);
impl Drop for ProcKiller {
fn drop(&mut self) {
unsafe {
TerminateProcess(self.0, 0).ok();
}
}
}
pub unsafe fn spawn_and_inject(proc: &str) -> anyhow::Result<[u8; 32]> {
let pth = Path::new(proc); let pth = Path::new(proc);
if !pth.is_file() { if !pth.is_file() {
panic!("file does not exist"); panic!("file does not exist");
} }
let cmd_line_c = CString::new(proc).expect("invalid cstr"); let cmd_line_c = CString::new(proc).expect("invalid cstr");
let start_up_info = STARTUPINFOA { let start_up_info = STARTUPINFOA {
wShowWindow: 0,
..Default::default() ..Default::default()
}; };
let mut proc_info = PROCESS_INFORMATION { let mut proc_info = PROCESS_INFORMATION {
@ -62,23 +69,24 @@ pub unsafe fn spawn_and_inject(proc: &str) {
mod_name, mod_name,
&start_up_info, &start_up_info,
&mut proc_info, &mut proc_info,
); )?;
// patch entry point... // patch entry point...
let mut ptr_to_pbi: PROCESS_BASIC_INFORMATION = std::mem::zeroed(); let mut ptr_to_pbi: PROCESS_BASIC_INFORMATION = std::mem::zeroed();
let stat = NtQueryInformationProcess( NtQueryInformationProcess(
proc_info.hProcess, proc_info.hProcess,
ProcessBasicInformation, ProcessBasicInformation,
&mut ptr_to_pbi as *mut _ as *mut c_void, &mut ptr_to_pbi as *mut _ as *mut c_void,
size_of::<PROCESS_BASIC_INFORMATION>() as u32, size_of::<PROCESS_BASIC_INFORMATION>() as u32,
&mut 0, &mut 0,
); );
let _pkr = ProcKiller(proc_info.hProcess);
let proc = Process::find_pid(proc_info.dwProcessId).unwrap(); let proc = Process::find_pid(proc_info.dwProcessId).unwrap();
let image_base_addr: *const c_void = proc let image_base_addr: *const c_void = proc
.read(ptr_to_pbi.PebBaseAddress as usize + 0x10) .read(ptr_to_pbi.PebBaseAddress as usize + 0x10)
.expect("the"); .expect("the");
let mut headers = [0; 4096]; let mut headers = [0; 4096];
proc.raw_read(image_base_addr as usize, headers.as_mut_ptr(), 4096); proc.raw_read(image_base_addr as usize, headers.as_mut_ptr(), 4096)?;
let dos_hdr = transmute::<*const u8, *const IMAGE_DOS_HEADER>(headers.as_ptr()); let dos_hdr = transmute::<*const u8, *const IMAGE_DOS_HEADER>(headers.as_ptr());
let nt_hdrs = transmute::<*const u8, *const IMAGE_NT_HEADERS64>( let nt_hdrs = transmute::<*const u8, *const IMAGE_NT_HEADERS64>(
headers headers
@ -92,7 +100,7 @@ pub unsafe fn spawn_and_inject(proc: &str) {
.read(code_entry as usize) .read(code_entry as usize)
.expect("failed to read entry"); .expect("failed to read entry");
let pay_load: [u8; 2] = [0xEB, 0xFE]; let pay_load: [u8; 2] = [0xEB, 0xFE];
proc.write(code_entry as usize, &pay_load); proc.write(code_entry as usize, &pay_load)?;
// //
// resume the thread // resume the thread
ResumeThread(proc_info.hThread); ResumeThread(proc_info.hThread);
@ -101,33 +109,35 @@ pub unsafe fn spawn_and_inject(proc: &str) {
{ {
let target = OwnedProcess::from_pid(proc.get_pid()).unwrap(); let target = OwnedProcess::from_pid(proc.get_pid()).unwrap();
let syrnge = Syringe::for_process(target); let syrnge = Syringe::for_process(target);
let injmod = syrnge let dll_loc = if cfg!(debug_assertions) {
.inject(format!( String::from(concat!(
"./target/{}/gdkeinj.dll", env!("CARGO_MANIFEST_DIR"),
if cfg!(debug_assertions) { "/target/debug/gdkeinj.dll"
"debug"
} else {
"release"
}
)) ))
.unwrap(); } else {
let gdke_inj_dll =
GdkeInj::get("gdkeinj.dll").expect("failed to get dll from embeded resources");
let tmp = std::env::temp_dir();
let loc = tmp.join("gdkeinj.dll");
let mut file = std::fs::File::create(&loc).unwrap();
file.write_all(&gdke_inj_dll.data).unwrap();
loc.to_str().map(|x| x.to_string()).unwrap()
};
println!("injecting dll ({})", dll_loc);
syrnge.inject(dll_loc).unwrap();
println!("waiting until udp is ok "); println!("waiting until udp is ok ");
let (_, addr) = sock.recv_from(&mut [0]).unwrap(); let (_, addr) = sock.recv_from(&mut [0]).unwrap();
sock.send_to(&1_u32.to_ne_bytes(), addr).unwrap(); sock.send_to(&1_u32.to_ne_bytes(), addr).unwrap();
let _ = sock.recv(&mut []); sock.recv(&mut [])?;
} }
// we're done. let's kill the process. // we're done. let's kill the process.
println!("done, running code",); println!("done, running code",);
proc.write(code_entry as usize, &entry_insts); proc.write(code_entry as usize, &entry_insts)?;
println!("waiting for call."); println!("waiting for call.");
let mut key = [0; 32]; let mut key = [0; 32];
let _ = sock.recv(&mut key); sock.recv(&mut key)?;
println!("recieved key: "); println!("recieved key, term");
for val in key { Ok(key)
print!("{:x}", val);
}
println!("\ncomplete.");
TerminateProcess(proc_info.hProcess, 1);
} }

View File

@ -2,6 +2,13 @@ use gdke::spawn_and_inject;
fn main() { fn main() {
unsafe { unsafe {
spawn_and_inject(&std::env::args().nth(1).unwrap()); let key =
spawn_and_inject(&std::env::args().nth(1).unwrap()).expect("failed to resolve key");
print!("key: ");
for val in key {
print!("{:x}", val);
}
println!("\npress enter to close");
let _ = std::io::stdin().read_line(&mut String::new());
} }
} }

View File

@ -1,5 +1,5 @@
use std::{ use std::{
io::{BufRead, BufReader, Cursor, Stdin, Stdout}, io::{BufRead, Cursor},
path::Path, path::Path,
process::{Command, Stdio}, process::{Command, Stdio},
}; };