Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Memory search

Use case

Quickly search the previously dumped memory regions for a specific value in multiple encodings.

Explanation

We convert the search value to multiple byte patterns: ASCII, UTF-16 (LE/BE), 32/64-bit ints (LE/BE) and raw hex (+ reversed). Then we search for all of them in the dumped .bin files. yara-x does the search based on the rule generated by mkrule.

Alternatively, r2 could be used as well. But yr is significantly faster.

Usage

$ cargo build --release
$ target/release/mkrule 285735461 > rule.yar
$ cat rule.yar
rule searchmem
{
    strings:
        $ascii        = "285735461" ascii nocase
        $utf16_le     = { 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31 00 }
        $utf16_be     = { 00 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31 }
        $int32_le     = { 25 fa 07 11 }
        $int32_be     = { 11 07 fa 25 }
        $int64_le     = { 25 fa 07 11 00 00 00 00 }
        $int64_be     = { 00 00 00 00 11 07 fa 25 }
        $hex          = { 28 57 35 46 1? }
        $hex_rev      = { 1? 46 35 57 28 }
    condition:
        any of them
}
$ yr scan rule.yar memdump_5542/bin -s
searchmem memdump_5542/bin/2000000-e000000.bin
0x2d02bc:9:$ascii: 285735461
0x2d02e8:9:$ascii: 285735461
0x2d0304:9:$ascii: 285735461
0x2d04b4:18:$utf16_le: 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31 00
0x2d04d4:18:$utf16_le: 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31 00
0x2d04f4:18:$utf16_le: 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31 00
0x2d0515:18:$utf16_le: 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31 00
0x2d04b3:18:$utf16_be: 00 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31
0x2d04d3:18:$utf16_be: 00 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31
0x2d04f3:18:$utf16_be: 00 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31
0x2d0514:18:$utf16_be: 00 32 00 38 00 35 00 37 00 33 00 35 00 34 00 36 00 31
0x2d0564:4:$int32_le: 25 fa 07 11
0x2d05e4:4:$int32_le: 25 fa 07 11
0x2d05a4:4:$int32_be: 11 07 fa 25
0x2d0630:4:$int32_be: 11 07 fa 25
0x2d05e4:8:$int64_le: 25 fa 07 11 00 00 00 00
0x2d062c:8:$int64_be: 00 00 00 00 11 07 fa 25
0x2d0644:5:$hex: 28 57 35 46 10
0x2d065c:5:$hex_rev: 10 46 35 57 28
$ cat memdump_5542/dumpmem.log | grep -E "0*2000000-0*e000000 rw-p"
02000000-0e000000 rw-p 00000000 00:00 0                                  [anon:dalvik-main space]

Code

use std::process::exit;

fn print_string(label: &str, body: String) {
    // format specifiers:
    // https://doc.rust-lang.org/std/fmt/
    println!("        ${label:12} = {body}")
}

// deadbeef -> { DE AD BE EF }
fn hex(bytes: &[u8]) -> String {
    let pairs: Vec<String> = bytes.iter().map(|b| format!("{b:02x}")).collect();
    format!("{{ {} }}", pairs.join(" "))
}

// "285735461" --> ["28", "57", "35", "46", "1?"]
fn parse_hex(val: &str) -> Option<Vec<String>> {
    let mut pairs: Vec<String> = Vec::new();
    for i in (0..val.len()).step_by(2) {
        let pair = val.get(i..(i + 2).min(val.len()))?;
        if !pair.bytes().all(|b| b.is_ascii_hexdigit()) {
            return None;
        }
        let pair = pair.to_ascii_lowercase();
        if pair.len() == 1 {
            pairs.push(format!("{pair}?"));
        } else {
            pairs.push(pair);
        }
    }
    Some(pairs)
}

/*
rule searchmem
{
    strings:
        $ascii        = "1234" ascii nocase
        $utf16_le     = { 31 00 32 00 33 00 34 00 }
        $utf16_be     = { 00 31 00 32 00 33 00 34 }
        $int32_big    = { 00 00 04 D2 }
        $int32_little = { D2 04 00 00 }
        $int64_big    = { 00 00 00 00 00 00 04 D2 }
        $int64_little = { D2 04 00 00 00 00 00 00 }
        $hex          = { 12 34 }
        $hex_reversed = { 34 12 }
    condition:
        any of them
}
*/
fn print_rule(val: &str) {
    println!("rule searchmem");
    println!("{{");
    println!("    strings:");

    print_string("ascii", format!("\"{val}\" ascii nocase"));

    let utf16_le: Vec<u8> = val.encode_utf16().flat_map(u16::to_le_bytes).collect();
    let utf16_be: Vec<u8> = val.encode_utf16().flat_map(u16::to_be_bytes).collect();

    print_string("utf16_le", hex(&utf16_le));
    print_string("utf16_be", hex(&utf16_be));

    if let Ok(n) = val.parse::<u32>() {
        print_string("int32_le", hex(&n.to_le_bytes()));
        print_string("int32_be", hex(&n.to_be_bytes()));
    }

    if let Ok(n) = val.parse::<u64>() {
        print_string("int64_le", hex(&n.to_le_bytes()));
        print_string("int64_be", hex(&n.to_be_bytes()));
    }

    if let Some(pairs) = parse_hex(val) {
        let mut rev = pairs.clone();
        rev.reverse();
        print_string("hex", format!("{{ {} }}", pairs.join(" ")));
        print_string("hex_rev", format!("{{ {} }}", rev.join(" ")));
    }

    println!("    condition:");
    println!("        any of them");
    println!("}}");
}

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    if args.len() != 1 || args[0].is_empty() {
        println!("usage: mkrule <value>");
        println!("e.g.: mkrule 1234 > rule.yar");
        exit(1);
    }

    print_rule(&args[0]);
}

View on GitHub.