Skip to content

Commit

Permalink
feat: implement telfhash function in elf module
Browse files Browse the repository at this point in the history
  • Loading branch information
vthib committed Dec 31, 2022
1 parent 10d65fc commit 8ada5b4
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 30 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion boreal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition = "2021"
default = ["hash", "object"]

# Enables the "hash" module.
hash = ["md-5", "sha1", "sha2", "hex", "crc32fast"]
hash = ["md-5", "sha1", "sha2", "hex", "crc32fast", "tlsh2"]

# Enables the "pe", "elf" and "macho" modules.
#
Expand Down Expand Up @@ -48,6 +48,7 @@ hex = { version = "0.4", optional = true }
md-5 = { version = "0.10", optional = true }
sha1 = { version = "0.10", optional = true }
sha2 = { version = "0.10", optional = true }
tlsh2 = { version = "0.1", optional = true }

# "object" feature
object = { version = "0.30", optional = true }
Expand Down
84 changes: 73 additions & 11 deletions boreal/src/module/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ impl Module for Elf {
"import_md5",
StaticValue::function(Self::import_md5, vec![], Type::Bytes),
),
#[cfg(feature = "hash")]
(
"telfhash",
StaticValue::function(Self::telfhash, vec![], Type::Bytes),
),
]
.into()
}
Expand Down Expand Up @@ -256,8 +261,11 @@ pub struct Data {
}

pub struct DataSymbol {
lowercase_name: Vec<u8>,
name: Vec<u8>,
shndx: u16,
bind: u8,
type_: u8,
visibility: u8,
}

impl Data {
Expand All @@ -269,14 +277,19 @@ impl Data {
.symbols
.iter()
.filter(|v| f(v))
.map(|s| &s.lowercase_name)
.map(|s| s.name.clone())
.collect();

if symbols.is_empty() {
return None;
}

// Lowercase the symbols, then sort them
for symbol in &mut symbols {
symbol.make_ascii_lowercase();
}
symbols.sort_unstable();

Some(
symbols
.into_iter()
Expand Down Expand Up @@ -529,8 +542,10 @@ fn get_symbols<Elf: FileHeader>(
if let Some(v) = name {
let _r = obj.insert("name", v.to_vec().into());
}
let _r = obj.insert("bind", symbol.st_bind().into());
let _r = obj.insert("type", symbol.st_type().into());
let bind = symbol.st_bind();
let type_ = symbol.st_type();
let _r = obj.insert("bind", bind.into());
let _r = obj.insert("type", type_.into());
let shndx = symbol.st_shndx(e);
let _r = obj.insert("shndx", shndx.into());
if let Ok(v) = symbol.st_value(e).into().try_into() {
Expand All @@ -542,12 +557,12 @@ fn get_symbols<Elf: FileHeader>(

symbols.push(Value::Object(obj));
if let Some(name) = name {
let mut lowercase_name = name.to_vec();

lowercase_name.make_ascii_lowercase();
data_symbols.push(DataSymbol {
lowercase_name,
name: name.to_vec(),
shndx,
bind,
type_,
visibility: symbol.st_other() & 0x3,
});
}
}
Expand All @@ -563,12 +578,59 @@ impl Elf {
use md5::{Digest, Md5};

let data = ctx.module_data.get::<Self>()?;
let import_string = data.get_import_string(|sym| {
sym.shndx == elf::SHN_UNDEF && !sym.lowercase_name.is_empty()
})?;
let import_string =
data.get_import_string(|sym| sym.shndx == elf::SHN_UNDEF && !sym.name.is_empty())?;

let hash = Md5::digest(import_string);

Some(Value::Bytes(hex::encode(hash).into_bytes()))
}

#[cfg(feature = "hash")]
fn telfhash(ctx: &ScanContext, _: Vec<Value>) -> Option<Value> {
const EXCLUDED_STRINGS: &[&[u8]; 8] = &[
b"__libc_start_main",
b"main",
b"abort",
b"cachectl",
b"cacheflush",
b"puts",
b"atol",
b"malloc_trim",
];

let data = ctx.module_data.get::<Self>()?;
let import_string = data.get_import_string(|sym| {
if sym.bind != elf::STB_GLOBAL
|| sym.type_ != elf::STT_FUNC
|| sym.visibility != elf::STV_DEFAULT
{
return false;
}

if sym.name.starts_with(b".") || sym.name.starts_with(b"_") {
return false;
}
if sym.name.ends_with(b"64") {
return false;
}
if sym.name.starts_with(b"str") || sym.name.starts_with(b"mem") {
return false;
}

if EXCLUDED_STRINGS
.iter()
.any(|excluded| *excluded == sym.name)
{
return false;
}

true
})?;

let mut tlsh = tlsh2::Tlsh::new();
tlsh.update(&import_string);

Some(Value::Bytes(dbg!(tlsh.finish(true)).into_bytes()))
}
}
77 changes: 77 additions & 0 deletions boreal/tests/assets/elf/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
To build `elf_with_imports`, first create a dyn.c:

```c
void foo(void) {}
void foo64(void) {}
void FOO64(void) {}
void FOO(int a) { printf("%d\n", a); }

void strstuff(void) {}
void stuffstr(void) {}
void memstuff(void) {}
void stuffmem(void) {}

void STRALLCAPS(void) {}
void ALLCAPSSTR(void) {}
void long_function_to_make_the_tlsh_hash_work() {}

int a_value;

// Non global dynsym
#pragma weak weak_fun
void weak_fun(void) {}
```

then create a elf.c:

```c
extern void foo(void);
extern void foo64(void);
extern void FOO64(void);
extern void FOO(int);

extern void strstuff(void);
extern void stuffstr(void);
extern void memstuff(void);
extern void stuffmem(void);

extern void STRALLCAPS(void);
extern void ALLCAPSSTR(void);
extern void long_function_to_make_the_tlsh_hash_work();

// Non global dynsym
#pragma weak weak_fun
extern void weak_fun(void);

extern int a_value;

// Protected dynsym
extern void __attribute__((visibility ("protected"))) protected_fun(void) {}

int main(void) {
foo();
foo64();
FOO64();
FOO(a_value);

strstuff();
stuffstr();
memstuff();
stuffmem();

STRALLCAPS();
ALLCAPSSTR();
long_function_to_make_the_tlsh_hash_work();
weak_fun();
protected_fun();

return 0;
}
```

And compile them:

```
gcc -shared -o libdyn.so -fPIC dyn.c
gcc -shared -o elf_with_imports -fPIC elf.c -ldyn -L.
```
Binary file added boreal/tests/assets/elf/elf_with_imports
Binary file not shown.
59 changes: 41 additions & 18 deletions boreal/tests/it/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@ use crate::{
utils::check,
};

// These are mostly coverage tests, ensuring all the fields are correctly set and have the same
// values as in libyara
#[track_caller]
fn test(mem: &[u8], condition: &str) {
check(
&format!(
r#"import "elf"
rule test {{
condition:
{}
}}"#,
condition
),
mem,
true,
);
}

#[test]
fn test_coverage_elf32() {
Expand Down Expand Up @@ -555,22 +568,6 @@ rule test {
#[test]
#[cfg(feature = "hash")]
fn test_import_md5() {
#[track_caller]
fn test(mem: &[u8], condition: &str) {
check(
&format!(
r#"import "elf"
rule test {{
condition:
{}
}}"#,
condition
),
mem,
true,
);
}

// No imports
test(ELF32_FILE, "not defined elf.import_md5()");
// No SHN_UNDEF symbols
Expand All @@ -588,3 +585,29 @@ rule test {{
"elf.import_md5() == \"e3545a5c27dd2ed4dd1739a3c3c071b2\"",
);
}

#[test]
#[cfg(feature = "hash")]
fn test_telfhash() {
use crate::utils::check_boreal;

test(ELF32_FILE, "not defined elf.telfhash()");
test(ELF32_SHAREDOBJ, "not defined elf.telfhash()");
test(ELF32_NOSECTIONS, "not defined elf.telfhash()");
test(ELF64_FILE, "not defined elf.telfhash()");
test(ELF32_MIPS_FILE, "not defined elf.telfhash()");
test(ELF_X64_FILE, "not defined elf.telfhash()");

let contents = std::fs::read("tests/assets/elf/elf_with_imports").unwrap();
check_boreal(
r#"
import "elf"
rule test {
condition:
elf.telfhash() ==
"T1C9B012184204F00184540770331E0B111363086015509C464D0ACE88181266C09374FA"
}"#,
&contents,
true,
);
}

0 comments on commit 8ada5b4

Please sign in to comment.