Skip to content

Commit

Permalink
226 implement filelist method (#227)
Browse files Browse the repository at this point in the history
* Setting up

* Initial works.

* Test eldritch side.

* Docs

* Fixed display macro.
  • Loading branch information
hulto authored Jul 22, 2023
1 parent 21563f3 commit bc4c246
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 2 deletions.
39 changes: 39 additions & 0 deletions docs/_docs/user-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,45 @@ The <b>file.is_dir</b> method checks if a path exists and is a directory. If it

The <b>file.is_file</b> method checks if a path exists and is a file. If it doesn't exist or is not a file it will return `False`.

### file.list
`file.list(path: str) -> List<Dict>`

The <b>file.list</b> method returns a list of files at the specified path. The path is relative to your current working directory and can be traveresed with `../`.
Each file is represented by a Dict type.
Here is an example of the Dict layout:

```JSON
[
{
"file_name": "implants",
"size": 4096,
"owner": "root",
"group": "0",
"permissions": "40755",
"modified": "2023-07-09 01:35:40 UTC",
"type": "Directory"
},
{
"file_name": "README.md",
"size": 750,
"owner": "root",
"group": "0",
"permissions": "100644",
"modified": "2023-07-08 02:49:47 UTC",
"type": "File"
},
{
"file_name": ".git",
"size": 4096,
"owner": "root",
"group": "0",
"permissions": "40755",
"modified": "2023-07-10 21:14:06 UTC",
"type": "Directory"
}
]
```

### file.mkdir
`file.mkdir(path: str) -> None`

Expand Down
1 change: 1 addition & 0 deletions implants/lib/eldritch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ allocative = { workspace = true }
allocative_derive = { workspace = true }
anyhow = { workspace = true }
async-recursion = { workspace = true }
chrono = { workspace = true }
derive_more = { workspace = true }
eval = { workspace = true }
flate2 = { workspace = true }
Expand Down
31 changes: 30 additions & 1 deletion implants/lib/eldritch/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod exists_impl;
mod hash_impl;
mod is_dir_impl;
mod is_file_impl;
mod list_impl;
mod mkdir_impl;
mod read_impl;
mod remove_impl;
Expand All @@ -16,13 +17,16 @@ mod template_impl;
mod timestomp_impl;
mod write_impl;

use std::fmt;

use allocative::Allocative;
use derive_more::Display;

use starlark::values::dict::Dict;
use starlark::collections::SmallMap;
use starlark::environment::{Methods, MethodsBuilder, MethodsStatic};
use starlark::values::none::NoneType;
use starlark::values::{StarlarkValue, Value, UnpackValue, ValueLike, ProvidesStaticType};
use starlark::values::{StarlarkValue, Value, UnpackValue, ValueLike, ProvidesStaticType, Heap};
use starlark::{starlark_type, starlark_simple_value, starlark_module};
use serde::{Serialize,Serializer};

Expand Down Expand Up @@ -59,6 +63,27 @@ impl<'v> UnpackValue<'v> for FileLibrary {
}
}

#[derive(Debug, Display)]
enum FileType {
File,
Directory,
Link,
Unknown,
}

#[derive(Debug, Display)]
#[display(fmt = "{} {} {} {} {} {} {}", name, file_type, size, owner, group, permissions, time_modified)]
struct File {
name: String,
file_type: FileType,
size: u64,
owner: String,
group: String,
permissions: String,
time_modified: String,
}


// This is where all of the "file.X" impl methods are bound
#[starlark_module]
fn methods(builder: &mut MethodsBuilder) {
Expand Down Expand Up @@ -98,6 +123,10 @@ fn methods(builder: &mut MethodsBuilder) {
if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); }
is_file_impl::is_file(path)
}
fn list<'v>(this: FileLibrary, starlark_heap: &'v Heap, path: String) -> anyhow::Result<Vec<Dict<'v>>> {
if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); }
list_impl::list(starlark_heap, path)
}
fn mkdir(this: FileLibrary, path: String) -> anyhow::Result<NoneType> {
if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); }
mkdir_impl::mkdir(path)?;
Expand Down
197 changes: 197 additions & 0 deletions implants/lib/eldritch/src/file/list_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use anyhow::Result;
use starlark::{values::{dict::Dict, Heap, Value}, collections::SmallMap, const_frozen_string};
use sysinfo::{System, SystemExt, UserExt};
use std::fs::DirEntry;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
#[cfg(target_os = "macos")]
use std::os::macos::fs::MetadataExt;
#[cfg(target_os = "linux")]
use std::os::linux::fs::MetadataExt;
#[cfg(target_os = "windows")]
use std::os::windows::fs::MetadataExt;
use chrono::{Utc, DateTime, NaiveDateTime};
use super::{File, FileType};

const UNKNOWN: &str = "UNKNOWN";

// https://stackoverflow.com/questions/6161776/convert-windows-filetime-to-second-in-unix-linux
#[cfg(target_os = "windows")]
fn windows_tick_to_unix_tick(windows_tick: u64) -> i64{
const WINDOWS_TICK: u64 = 10000000;
const SEC_TO_UNIX_EPOCH: u64 = 11644473600;
return (windows_tick / WINDOWS_TICK - SEC_TO_UNIX_EPOCH) as i64;
}

fn create_file_from_dir_entry(dir_entry: DirEntry) -> Result<File> {
let mut sys = System::new();
sys.refresh_users_list();

let file_name = match dir_entry.file_name().into_string() {
Ok(local_file_name) => local_file_name,
Err(_) => return Err(anyhow::anyhow!("file.list: Unable to convert file name to string.")),
};

let file_type = match dir_entry.file_type() {
Ok(tmp_file_type) => {
if tmp_file_type.is_dir() {
FileType::Directory
} else if tmp_file_type.is_file() {
FileType::File
} else if tmp_file_type.is_symlink() {
FileType::Link
} else {
FileType::Unknown
}
},
Err(_) => FileType::Unknown,
};

let dir_entry_metadata = dir_entry.metadata()?;

let file_size = dir_entry_metadata.len();

#[cfg(unix)]
let owner_username = match sysinfo::Uid::try_from(dir_entry_metadata.st_uid() as usize) {
Ok(local_uid) => match sys.get_user_by_id(&local_uid) {
Some(user_name_string) => user_name_string.name().to_string(),
None => UNKNOWN.to_string(),
},
Err(_) => UNKNOWN.to_string(),
};

#[cfg(not(unix))]
let owner_username = {
UNKNOWN.to_string()
};

#[cfg(unix)]
let group_id = {
dir_entry_metadata.st_gid().to_string()
};
#[cfg(not(unix))]
let group_id = {
UNKNOWN // This is bad but windows file ownership is very different.
};

#[cfg(unix)]
let timestamp = {
dir_entry_metadata.st_mtime()
};
#[cfg(not(unix))]
let timestamp = {
let win_timestamp = dir_entry_metadata.last_write_time();
windows_tick_to_unix_tick(win_timestamp)
};

#[cfg(unix)]
let permissions ={
format!("{:o}", dir_entry_metadata.permissions().mode())
};
#[cfg(target_os = "windows")]
let permissions = {
if dir_entry.metadata()?.permissions().readonly() {
"Read only"
} else {
"Not read only"
}
};

let naive_datetime = match NaiveDateTime::from_timestamp_opt(timestamp, 0) {
Some(local_naive_datetime) => local_naive_datetime,
None => return Err(anyhow::anyhow!("Failed to get time from timestamp for file {}", file_name)),
};
let time_modified: DateTime<Utc> = DateTime::from_utc(naive_datetime, Utc);

Ok(File {
name: file_name,
file_type: file_type,
size: file_size,
owner: owner_username,
group: group_id.to_string(),
permissions: permissions.to_string(),
time_modified: time_modified.to_string(),
})

}

fn handle_list(path: String) -> Result<Vec<File>> {
let paths = std::fs::read_dir(path)?;
let mut final_res = Vec::new();
for path in paths {
final_res.push(create_file_from_dir_entry(path?)?);
}
Ok(final_res)
}

fn create_dict_from_file(starlark_heap: &Heap, file: File) -> Result<Dict>{
let res: SmallMap<Value, Value> = SmallMap::new();
let mut tmp_res = Dict::new(res);

let tmp_value1 = starlark_heap.alloc_str(&file.name);
tmp_res.insert_hashed(const_frozen_string!("file_name").to_value().get_hashed().unwrap(), tmp_value1.to_value());

let file_size = file.size as i32;
tmp_res.insert_hashed(const_frozen_string!("size").to_value().get_hashed().unwrap(), Value::new_int(file_size));

let tmp_value2 = starlark_heap.alloc_str(&file.owner);
tmp_res.insert_hashed(const_frozen_string!("owner").to_value().get_hashed().unwrap(), tmp_value2.to_value());

let tmp_value3 = starlark_heap.alloc_str(&file.group);
tmp_res.insert_hashed(const_frozen_string!("group").to_value().get_hashed().unwrap(), tmp_value3.to_value());

let tmp_value4 = starlark_heap.alloc_str(&file.permissions);
tmp_res.insert_hashed(const_frozen_string!("permissions").to_value().get_hashed().unwrap(), tmp_value4.to_value());

let tmp_value5 = starlark_heap.alloc_str(&file.time_modified);
tmp_res.insert_hashed(const_frozen_string!("modified").to_value().get_hashed().unwrap(), tmp_value5.to_value());

let tmp_value6 = starlark_heap.alloc_str(&file.file_type.to_string());
tmp_res.insert_hashed(const_frozen_string!("type").to_value().get_hashed().unwrap(), tmp_value6.to_value());

Ok(tmp_res)
}

pub fn list(starlark_heap: &Heap, path: String) -> Result<Vec<Dict>> {
let mut final_res: Vec<Dict> = Vec::new();
let file_list = match handle_list(path) {
Ok(local_file_list) => local_file_list,
Err(local_err) => return Err(anyhow::anyhow!("Failed to get file list: {}", local_err.to_string())),
};
for file in file_list {
let tmp_res = create_dict_from_file(starlark_heap, file)?;
final_res.push(tmp_res);
}
Ok(final_res)
}

#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;

#[test]
fn test_file_list() -> anyhow::Result<()>{
let test_dir = tempdir()?;
let expected_dirs = ["never gonna", "give you up", ".never gonna"];
let expected_files = ["let_you_down", ".or desert you"];

for dir in expected_dirs {
let test_dir_to_create = test_dir.path().join(dir);
std::fs::create_dir(test_dir_to_create)?;
}

for dir in expected_files {
let test_dir_to_create = test_dir.path().join(dir);
std::fs::File::create(test_dir_to_create)?;
}

let binding = Heap::new();
let list_res = list(&binding, test_dir.path().to_str().unwrap().to_string())?;
println!("{:?}", list_res);
assert_eq!(list_res.len(), (expected_dirs.len()+expected_files.len()));

Ok(())
}

}
2 changes: 1 addition & 1 deletion implants/lib/eldritch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ mod tests {
a.globals(globals);
a.all_true(
r#"
dir(file) == ["append", "compress", "copy", "download", "exists", "hash", "is_dir", "is_file", "mkdir", "read", "remove", "rename", "replace", "replace_all", "template", "timestomp", "write"]
dir(file) == ["append", "compress", "copy", "download", "exists", "hash", "is_dir", "is_file", "list", "mkdir", "read", "remove", "rename", "replace", "replace_all", "template", "timestomp", "write"]
dir(process) == ["kill", "list", "name"]
dir(sys) == ["dll_inject", "exec", "get_ip", "get_os", "is_linux", "is_macos", "is_windows", "shell"]
dir(pivot) == ["arp_scan", "bind_proxy", "ncat", "port_forward", "port_scan", "smb_exec", "ssh_exec", "ssh_password_spray"]
Expand Down

0 comments on commit bc4c246

Please sign in to comment.