CSI CTF 2020 Reversing – RicknMorty

Overview

El reto corresponde a este ctf . Si quieren descargarlo pueden encontrarlo en GitHub . En este reto vemos un binario ejecutable de Linux 64 bit. Cuando ejecutamos nos da una serie de par de números aleatorios y un input del usuario para ingresar un numero mediante el uso de la función scanf .

El binario no usa ningún custom packer ni esta despojado de sus símbolos lo cual hace la tarea de ingeniería inversa mas asequible. Es un binario que esta funcionando en un servidor en remoto que nos permitirá ejecutar la función system y poder recibir la flag. El pseudocodigo que nos brinda Ghidra es el siguiente:

undefined8 main(void)
{
    bool bVar1;
    undefined4 uVar2;
    int32_t iVar3;
    int64_t iVar4;
    int64_t arg3;
    int64_t var_40h;
    int64_t var_38h;
    int64_t var_30h;
    undefined8 timer;
    int64_t var_20h;
    int64_t var_18h;
    int64_t var_10h;
    int32_t var_8h;
    uint32_t var_4h;
    
    setbuf(_reloc.stdin, 0);
    setbuf(_reloc.stdout, 0);
    setbuf(_reloc.stderr);
    uVar2 = time(&timer);
    srand(uVar2);
    time(&var_30h);
    // rbp-0x4
    bVar1 = true;
    var_8h = 0;
    while( true ) {
        iVar3 = rand();
        if (iVar3 % 3 + 4 < var_8h) break;
        iVar3 = rand();
        iVar4 = (int64_t)(iVar3 % 10 + 6);
        iVar3 = rand();
        arg3 = (int64_t)(iVar3 % 10 + 6);
        printf("%d %d\n", iVar4, arg3);
        __isoc99_scanf("%lld", &var_40h);
        iVar3 = function1(iVar4, arg3, arg3);
        iVar4 = function2(iVar3 + 3);
        if (iVar4 != var_40h) {
            bVar1 = false;
        }
        var_8h = var_8h + 1;
    }
    time(&var_38h);
    printf((double)(var_38h - var_30h), "fun() took %f seconds to execute \n");
    if ((!bVar1) || (*(double *)0x402060 < (double)(var_38h - var_30h))) {
        printf("Nahh.");
    } else {
        puts(*(double *)0x402060, "Hey, you got me!");
        system("cat flag.txt");
    }
    return 0;
}

Según podemos apreciar el código la función printf nos desvela que la función se ejecuta en x segundos, esto podría ser una pista de que nos obligue a tener que desarrollar un script que envíe de manera automática los bytes al servidor. A nivel reversing estático es interesante darnos cuenta de la variable bVar1. Esta variable indica si ha sido seteado a False o es True (Se declara con ese valor al principio del código). Si se modifica a False nos imprimirá con el output de Nahh como hemos podido observar previamente. Vemos en la estructura de control if como se compara una variable iVar4 con la variable var_40h en la que nosotros si tenemos control. Si es distinto, entra en la estructura de control y seta a False bVar1, por lo tanto deberemos de evitar que entre ahi.

De momento tenemos controlado de que la variable bVar1 siempre tiene que estar como True. ¿Pero como podemos hacer para que siempre se mantenga así? Vimos como en la estructura de control aparece una nueva variable iVar4. Esta variable es una constante que tiene que ser igual a nuestro input que enviamos al servidor . Corresponde al valor de retorno de una función function2.

Es función recibe como argumentos la variable iVar3+3 siendo una constante que denominaremos k. La constante k es el valor de retorno de una función function1 cuyos argumentos que recibe son los valores random que se calculan y que el servidor nos envía cuando interactuamos con la aplicación. Sabiendo los valores random que nos envía usando la librería pwntools y realizando ingeniería inversa en la función function1, podríamos saber el valor de la constante k para poder resolver el reto y saber previamente que enviarle al servidor como input.

Solver

Como no puede ser de otra manera, deberemos de poder replicar el código en ensamblador del algoritmo de la función function1 usando Python. En el script estará identificada dicha función con el nombre de constant. Una vez calcula el valor constante denominado k se le aplica la suma +3 al valor. Este valor comenzara siempre con 4,5,6,n. La función function2 simplemente multiplica desde el numero 1 hasta el máximo valor correspondiente al argumento que se le pasa, si por ejemplo se le pasa de valor el numero 4, el valor de la variable iVar4 seria 24.

1*1=1;1*2=2;2*3=6;6*4=24

Con esto ya tendríamos la solución, si resulta que el valor de retorno de la función function1 es igual a 1, al sumarse 3 corresponde a 4 y mirando la el diccionario del script ese valor corresponde a 24. El valor a ingresar por parte del usuario a la aplicación es 24.

from pwn import *


context.log_level = 'debug'

#Reversed algorithm of function1 from asm
def constant(arg1,arg2,arg3):
    var4 = 0
    var8 = 1
    var18 = arg1
    var20 = arg2

    #loop
    eax = var8
    while (var18 >= eax):
        ecx = var8 
        eax = var18
        rdx = 0x0 # cqo
        rdx = eax%ecx
        if rdx != 0x0:
            var8 += 1
            eax = var8
        else:   
            ecx = var8
            eax = var20
            rdx = 0x0 # cqo
            rdx = eax%ecx
            if rdx != 0x0:
                var8 += 1
                eax = var8
            else:
                var4 = var8
                var8 += 1
                eax = var8

    while (var20 >= eax):
        ecx = var8 
        eax = var18
        rdx = 0x0 # cqo
        rdx = eax%ecx
        if rdx != 0x0:
            var8 += 1
            eax = var8
        else:   
            ecx = var8
            eax = var20
            rdx = 0x0 # cqo
            rdx = eax%ecx
            if rdx != 0x0:
                var8 += 1
                eax = var8
            else:
                var4 = var8
                var8 += 1
                eax = var8

    return var4

def recvbytes():
    byte = io.recv(6)
    print(byte)
    if "fun()" in byte.decode("utf-8"):
        io.interactive() #Just recv flag bytes!
    arg2 = byte[2:-1].decode("utf-8").split()
    arg1 = byte[0:2].decode("utf-8").split()
    print(type(int(arg2[0])))
    print(arg1)
    k = constant(int(arg1[0]),int(arg2[0]),int(arg2[0]))
    print("Return Value of function1: ",k)
    suma = k+3
    print("Final value: ",suma)
    function2 = {4:24,5:120,6:720,7:5040,8:40320,9:362880,10:3628800,11:39916800,12:479001600,13:6227020800,14:87178291200,15:1307674368000,16:20922789888000,17:355687428096000,18:6402373705728000}

    io.sendline(str(function2[suma]))


# main
io = remote('chall.csivit.com',30827)
while True:
    recvbytes()

Si tienen alguna duda o quieren contactar conmigo os dejo mi Twitter @naivenom