Default

screen2

WHAT THE HELL?

Okay, this was SUPER overwhelming to look at. I’ve never worked with yara before and now I have to deal with this mess?

After staring at this eyesore for a bit 3 patterns stood out:

  1. Conditions based on the filesize var which is = to 85 bytes
  2. Arbitrary comparisons such as uint8(62) > 1
  3. Hash comparisons such as hash.md5(50, 2) == "657dae0913ee12be6fb2a6f687aae1c7"

Reading the documentation for yara we find that uintXX() is used to access data at a given position, it can be an offset or virtual address. Both 16 and 32 bit integers are considered to be little-endian (VERY IMPORTANT).

Alright then it seems we have the tools to begin solving this challenge.

Another pattern that becomes clear after reading yara docs is the use of and to separate the conditions of each rule. We can use this to break the rule down to a more readable format.

screen2

After some more parsing I got this array of numbers.

[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 41, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 68, 68, 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 78, 78, 78, 78, 78, 78, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 81, 81, 81, 81, 81, 81, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, 84]

85 individual offsets, with 6 conditions to be met each. This is slowly becoming more manageable

After some more parsing more patterns became clear which helped a lot since it made writing an interpreter for the conditions much simpler

screen2

At this point it’s pretty obvious that this is an 85 character string, each set of conditions gives you a byte of the string.

Since conditions that require a hash match are only 2 bytes long they’re trivial to bruteforce.

We’re also given conditions such as uint8(20) & 128 == 0

I admit initially I was trying to find the values through ALL the conditions, meaning those with multiple possible values which was a massive headache.

This is the script I used to get the flag, it’s not pretty but it does the job.

from z3 import *
import re
import hashlib, zlib

conditions = "" #conditions str go here

def categorize():

    split_conditions = conditions.split('and')
    u8_positions = []
    u32_positions = []
    hash_condition = []

    for condition in split_conditions:

        if "uint8" in condition:

            u8_positions.append(condition)

        if "uint32" in condition:

            u32_positions.append(condition)

        elif "hash" in condition:

            hash_condition.append(condition

    return u8_positions,u32_positions,hash_condition

  
  

def sort_offset_conditions(conditions_list, hashes=False):
    sorted = []
    for i in range(85):
        for condition in conditions_list:


            if f'({i})' in condition and hashes == False:

                if "==" in condition:

                    sorted.append(dict({"offset": i,"condition": condition}))

            if (f'({i},' in condition) and not (f'uint32({i},' in condition):

                sorted.append(dict({"offset": i,"condition": condition}))
    return sorted

  

def get_offset_conditions(sorted_conditions, offset):

    return [cdict for cdict in sorted_conditions if cdict["offset"] == offset]

  

def solve_condition(offset_conditions,):

    s = Solver()

    filesize = BitVec('filesize', 64)

    x = BitVec('x',64)


    comp_op = ''
    op = ''
    v1 = ''
    v2 = ''
    result = ''

  

    for dconditions in offset_conditions:

        condition = dconditions["condition"]
        condition = re.sub(r'uint8\(\d+\)',"x",condition)
        condition = re.sub(r'uint32\(\d+\)',"x",condition)
        
        split_condition = (condition.split(' '))
        split_condition = split_condition[1:-1]

        comp_op = split_condition[-2]

        result = split_condition[-1]

        if len(split_condition) > 4:

            v1 = split_condition[0]

            op = split_condition[1]

            v2 = split_condition[2]

            condition = f"({v1}{op}{v2}){comp_op}{result}"

        print(f"condition: {condition}")
        eval(f"s.add({condition})")
    s.check()
    sat = s.model()

    return sat[x]


def main():
    flag = []
    categorized32 = categorize()[1]
    categorized8 = categorize()[0]
    hashes = categorize()[2]


    sorted_conditions32 = sort_offset_conditions(categorized32)
    sorted_conditions8 = sort_offset_conditions(categorized8)
    sorted_hashes = sort_offset_conditions(hashes, True)

    def bruteforce(cond):

       # print(cond)

        cond = cond.replace(" ","")

        split_cond = cond.split("==")

        _hash = split_cond[1].strip('"')

        if "hash.md5" in cond:

            for i in range(0, 65536):

                bytestr = i.to_bytes(2, 'little')  

                result = hashlib.md5(bytestr).hexdigest()

                if result == _hash:
                    return bytestr
                else:
                    result = None

        elif "hash.crc32" in cond:

            for i in range(0, 65536):

                bytestr = i.to_bytes(2, 'little')  

                result = hex(zlib.crc32(bytestr))

                if result == _hash:

                    return bytestr
                else:
                    result = None

        elif "hash.sha256" in cond:

            for i in range(0, 65536):

                bytestr = i.to_bytes(2, 'little')  

                result = hashlib.sha256(bytestr).hexdigest()

                if result == _hash:
                    return bytestr
                else:
                    result = None

        return result


    for i in range(85):

        offset_conditions32 = get_offset_conditions(sorted_conditions32, i)

        offset_conditions8 = get_offset_conditions(sorted_conditions8, i)

        offset_hash = get_offset_conditions(sorted_hashes, i)

        print(offset_conditions8)

        if offset_conditions32:

            solved32 = solve_condition(offset_conditions32)

            if solved32 != None:
                out = str(BV2Int(solved32))

                outchr = int(re.findall(r'\((\d+)\)',out)[0])
                hexa = hex(outchr)[2:]


                solved = bytes.fromhex(hexa).decode('utf-8')[::-1]
                flag.append(solved)

        elif offset_conditions8:

            solved8 = solve_condition(offset_conditions8)

            if solved8 != None:

                out = str(BV2Int(solved8))

                outchr = int(re.findall(r'\((\d+)\)',out)[0])

                flag.append(chr(outchr))

        if len(offset_hash) == 1:

            r = bruteforce(offset_hash[0]["condition"]).decode('utf-8')

            flag.append(r)

        elif len(offset_hash) > 1:

            cond = offset_hash[1]

            r = bruteforce(cond["condition"]).decode('utf-8')

            flag.append(r)

        else:

            pass

    print(f"\n\nflag: {str(''.join(flag))}")

if __name__ =="__main__":

    main()

    #flag 1RuleADayK33p$Malw4r3Aw4y@flare-on.com