Rev0x01

Flag: StarHack{KdgWZUZfUEi9Tl2}

Challenge

Description


Ce programme est trop grand mais il est inutile. Pouvez-vous extraire le flag ?

Solution

Ici, on ne peut pas débugger directement. On va passer par une attaque par bruteforce grâce à un side-channel : le nombre d'instructions CPU.

Quand on décompile le binaire, on s'aperçoit que les caractères sont vérifiés 1 par 1 et dès qu'un est mauvais, le processus se termine. Autrement dit, si on a la bonne lettre, on fera un tour de boucle en plus et nécessairement, on exécutera plus d'instructions. C'est ça qu'on peut détecter :

En jaune, on déduit que notre input doit faire 15 de longueur.

En bleu, c'est la condition qui quitte la fonctionne si le caractère n'est pas le bon.

On va passer par valgrind pour compter le nombre d'instructions puisque perf stat ne fonctionne pas sur la majorité des VM (pour ceux comme moi qui sont sur VMWare ou autre).

J'ai simplement modifié le script de https://github.com/nebhrajani-a/taCTF pour mettre notre charset et éviter de tester des caractères intuiles, ça donne :

#!/usr/bin/python3

import argparse
import sys
import re
import subprocess
import tempfile
from string import ascii_lowercase, ascii_uppercase, digits, punctuation

not_allowed_chars = ['"', "'", "(", ")", ">", "<", "`", "|", "\\",
                     "#", ";", "&"]
punctuation = list(punctuation.strip(" "))
for char in not_allowed_chars:
    punctuation.remove(char)
punctuation = ''.join(punctuation)


def make_bold(text, index):
    return text[:index] + '\033[1m' + text[index] + '\033[0m' + text[index+1:]


def get_charset(code):
    return "NZKqdWgZZUfEUiT9lV2f"


def get_instruction_count(test_str, binary_filename):
    with tempfile.NamedTemporaryFile() as tmp:
        command = f'echo "{test_str}" | valgrind --tool=callgrind --callgrind-out-file={tmp.name}\
        {binary_filename}'
        try:
            with subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE) as valout:
                valout = valout.stderr.read()
                valout = int(re.findall('Collected : \d+',
                                        valout.decode())[0][12:])
                return valout
        except IndexError:
            print('here')
            pass


def find_char_at(test_str, location, binary_filename, verbose, charset_code):
    if verbose:
        print("Testing: ", make_bold(test_str, location), "at", location)
    charset = get_charset(charset_code)
    maximum = 0
    bestchoice = ""
    for char in charset:
        test = test_str[:location] + char + test_str[location+1:]
        val = get_instruction_count(test, binary_filename)
        if val is not None:
            if val > maximum:
                maximum = val
                bestchoice = test
        if verbose:
            print("    ", char, ":", val)
    return bestchoice


def find_length(binary_filename, maxlen, verbose):
    maximum = -1
    init_counts = get_instruction_count("a", binary_filename)
    length = -1
    for i in range(1, maxlen+1):
        val = get_instruction_count("a"*i, binary_filename)
        if verbose:
            print("a"*i, ":", val)
        if val is not None:
            if val > maximum:
                maximum = val
                length = i
    if init_counts != maximum:
        return length

def find_string(binary_filename, maxlen=35,
                verbose=False, reverse=False,
                lengthgiven=False, length=0,
                charset_code=0,
                flag_format=""):
    if not lengthgiven:
        length = find_length(binary_filename, maxlen, verbose)
        if length is None:
            print("[taCTF] I couldn't guess the length, sorry. Try -l LENGTH.")
            sys.exit()
    print("Length guess:", length)
    length_diff = length - len(flag_format)
    candidate = flag_format + 'a'*length_diff
    if not reverse:
        for i in range(len(flag_format), length):
            candidate = find_char_at(candidate, i, binary_filename, verbose,
                                     charset_code)
            print(candidate)
    else:
        for i in range(length-1, len(flag_format)-1, -1):
            candidate = find_char_at(candidate, i, binary_filename, verbose,
                                     charset_code)
            print(candidate)
    return candidate


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Run an instruction counting based timing attack on a given binary.')
    parser.add_argument('filename', type=str,
                        help='The file to run taCTF against')
    parser.add_argument('-v', '--verbose', action='store_true',
                        help="Verbose mode, prints out what it's up to at every iteration")
    parser.add_argument('-r', '--reverse', action='store_true',
                        help='Try the timing attack starting at the end of the string.')
    parser.add_argument('-f', '--flag-format', type=str, default="",
                        help='Flag format: first N known characters in string.')
    parser.add_argument('-ml', '--max-length', type=int, default=35,
                        help='Maximum length to check strings till, if length unknown.')
    parser.add_argument('-l', '--length', type=int,
                        help='Length of string, if known.')
    parser.add_argument('-c', '--charset', type=int, default=0,
                        help='Set the charset code. Default is 0 (all chars).')
    args = parser.parse_args()

    lengthgivenbool = False
    if args.length is not None:
        lengthgivenbool = True
    find_string(args.filename, maxlen=args.max_length,
                verbose=args.verbose,
                reverse=args.reverse,
                lengthgiven=lengthgivenbool, length=args.length,
                charset_code=args.charset,
                flag_format=args.flag_format)

On lance avec python3 taCTF.py "./task2" -l 15.

┌──(kali㉿kali)-[~/Documents/Reverse]
└─$ python3 taCTF.py "./task2" -l 15                                                                                                                                                                                                                                130 ⨯
Length guess: 15
Kaaaaaaaaaaaaaa
Kdaaaaaaaaaaaaa
Kdgaaaaaaaaaaaa
KdgWaaaaaaaaaaa
KdgWZaaaaaaaaaa
KdgWZUaaaaaaaaa
KdgWZUZaaaaaaaa
KdgWZUZfaaaaaaa
KdgWZUZfUaaaaaa
KdgWZUZfUEaaaaa
KdgWZUZfUEiaaaa
KdgWZUZfUEi9aaa
KdgWZUZfUEi9Taa
KdgWZUZfUEi9Tla
KdgWZUZfUEi9Tl2
                                                                                                                                                                                                                                                                          
┌──(kali㉿kali)-[~/Documents/Reverse]
└─$ ./task2 
give me The correct flag: KdgWZUZfUEi9Tl2
Good Job
Go ahead and submit
StarHack{KdgWZUZfUEi9Tl2}

Dernière mise à jour

Cet article vous a-t-il été utile ?