Для решения нужно подобрать ключ, после чего программа отобразит флаг.

Бинарные файлы:

Solution

Первым делом получу инфу о бинаре с помощью DIEC:

Info:
    File name: /spbctf_4_x86_64/spbctf_4_x86_64
    Size: 8856
    File type: ELF64
    String: ELF(AMD64)
    Extension: elf
    Operation system: Ubuntu Linux(16.04.4,ABI: 2.6.32)
    Architecture: AMD64
    Mode: 64-bit
    Type: EXEC
    Endianness: LE

Пойду изучать бинарь статически. Таблица импортов не затерта, что сильно упрощает анализ.

Пробежавшись взглядом по функции main, я нашел следующий вызов:

IMG

Он то нам и нужен. Полезным делом будет переименовывать переменные и указывать тип для них, так как в дальнейшем это упростит анализ. Использовать вызовы printf для этого очень удобно.

В функцию передается форматная строка, из которой можно вытащить тип переменных, а иногда даже имя:

IMG

Красотища какая… Вот весь код функции main:

{
    void* fsbase;
    int64_t rax = *(uint64_t*)((char*)fsbase + 0x28);
    int32_t var_278;
    __builtin_memcpy(&var_278, "\x08\x00\x00\x00\x07\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x09\x00\x00\x00\x0a\x00\x00\x00", 0x28);
    char* flag;
    __builtin_strncpy(&flag, "cPK}[aYr^@ZZR`C]TBP_\\Y_U\x7fUWE", 0x1d);
    int32_t result;
    
    if (argc > 1)
    {
        int32_t rax_4 = strlen(argv[1]);
        int32_t var_284_1 = 0;
        
        while (true)
        {
            void var_248;
            
            if (var_284_1 >= rax_4)
            {
                bubble_sort_sequence_executor_aka_transposition_performer(&var_278, &var_248, rax_4);
                int32_t var_280_1 = 1;
                
                for (int32_t i = 0; i <= 9; i += 1)
                {
                    if (&var_278[((int64_t)i)] != (i + 1))
                        var_280_1 = 0;
                }
                
                if (var_280_1 == 0)
                    puts("Hmmm, Not exactly what I was ask…");
                else
                {
                    int32_t var_284_2 = 0;
                    
                    while (((int64_t)var_284_2) < strlen(&flag))
                    {
                        *(uint8_t*)(&flag + ((int64_t)var_284_2)) ^= argv[1][((int64_t)(((int64_t)var_284_2) % rax_4))];
                        var_284_2 += 1;
                    }
                    
                    printf("Your flag: %s\n", &flag);
                }
                
                result = 0;
                break;
            }
            
            char rax_10 = argv[1][((int64_t)var_284_1)];
            int32_t rax_11;
            
            if ((rax_10 <= 0x2f || rax_10 > 0x39))
                rax_11 = 0;
            else
                rax_11 = 1;
            
            if (rax_11 == 0)
            {
                puts("Only 0-9");
                result = 2;
                break;
            }
            
            *(uint32_t*)(&var_248 + (((int64_t)var_284_1) << 2)) = (((int32_t)argv[1][((int64_t)var_284_1)]) - 0x30);
            var_284_1 += 1;
        }
    }
    else
    {
        puts("Go away, lazy student!");
        result = 1;
    }
    
    if (rax == *(uint64_t*)((char*)fsbase + 0x28))
        return result;
    
    __stack_chk_fail();
    /* no return */
}

Буду разбираться, что же происходит в коде.

Вот тут копируется строка в переменную flag:

char* flag;
__builtin_strncpy(&flag, "cPK}[aYr^@ZZR`C]TBP_\\Y_U\x7fUWE", 0x1d);

Можно сделать вывод, что флаг имеет длину 29.

    if (argc > 1) {
	    ...
    } else {
        puts("Go away, lazy student!");
        result = 1;
    }

Вот и первая проверка. Значит, для получения флага нужно передавать какой-то аргумент через командную строку.

int32_t rax_4 = strlen(argv[1]);
int32_t var_284_1 = 0;

Получаем длину переданного аргумента. Сразу переименую переменную:

int32_t input_len = strlen(argv[1]);
int32_t var_284_1 = 0;

Разберем следующий кусок кода. Писать о каждом переименовании переменных слишком запарно. Откройте бинарь и сопоставьте. Воть. Если внимательно изучить функцию main, то можно увидеть, что в первую очередь выполняется данный кусок кода:

int32_t *input_nums;

if ((c <= '/' || c > '9'))
	rax_9 = 0;
else
	rax_9 = 1;
	
if (rax_9 == 0) {
	puts("Only 0-9");
	result = 2;
	break;
}
*(uint32_t*)(&input_nums + (((int64_t)pos) << 2)) = (((int32_t)argv[1][((int64_t)pos)]) - 0x30);

Вот эта строчка выглядит максимально неясно)

*(uint32_t*)(&input_nums + (((int64_t)pos) << 2)) = (((int32_t)argv[1][((int64_t)pos)]) - 0x30);

Ее можно упростить до следующего вида:

input_name[pos] = argv[pos] - `\0`;

Стало понятнее. В качестве входных параметром нужно передавать строку. А именно в данной строке из ascii символов получают int.

Поехали дальше. После преобразования всех символов в int выполняется следующее условие, и мы проваливаемся дальше:

if (pos >= input_len_1)

Вот так выглядит следующая проверка:

bubble_sort_sequence_executor_aka_transposition_performer(&key, &input_nums, input_len_1);
int32_t ret = 1;

for (int32_t i = 0; i <= 9; i += 1)
{
	if (key[((int64_t)i)] != (i + 1))
	ret = 0;
}

if (ret == 0)
	puts("Hmmm, Not exactly what I was ask…");
else
{
	int32_t j = 0;

	while (((int64_t)j) < strlen(&flag))
	{
		*(uint8_t*)(&flag + ((int64_t)j)) ^= argv[1][((int64_t)(((int64_t)j) % input_len_1))];
		j += 1;
	}
	printf("Your flag: %s\n", &flag);
}
                
result = 0;
break;

Изучу содержимое функции bubble_sort_sequence_executor_aka_transposition_performer:

int64_t bubble_sort_sequence_executor_aka_transposition_performer(int32_t* key, uint8_t* input_nums, int32_t len)
{
    int32_t i;
    
    for (i = 0; i < len; i += 1)
    {
        int32_t tmp = key[((int64_t)input_nums[((int64_t)i)])];
        key[((int64_t)input_nums[((int64_t)i)])] = key[(((int64_t)input_nums[((int64_t)i)]) + 1)];
        key[(((int64_t)input_nums[((int64_t)i)]) + 1)] = tmp;
    }
    
    return i;
}

Выглядит не очень. Давайте разбираться. Мы проходим по всем элементам массива input_nums. В tmp мы сохраняем input_nums[i]. В key[input_nums[i]] сохраняется key[input_nums[i] + 1)]. Далее в key[input_nums[i] + 1] сохраняется tmp. По факту данная функция меняет местами 2 соседние ячейки массива.

Вот тут видно, что в массиве key индекс должен совпадать со значением в ячейке. А значит нужно отсортировать массив:

for (int32_t i = 0; i <= 9; i += 1)
{
	if (key[((int64_t)i)] != (i + 1))
	ret = 0;
}

bubble_sort на что-то намекает… Но вот на что…

IMG

Нам нужна сортировка пузырьком. Можно написать самому или же нагуглить реализацию:

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swaps.append(j)
                swapped = True

        if not swapped:
            break

    return arr

Но это еще не все. Отсортировать то мы можем, но нам нужно запомнить индексы ячеек, которые мы меняли местами:

def bubble_sort(arr):
    swaps = []
    n = len(arr)
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swaps.append(j)
                swapped = True

        if not swapped:
            break

    print(''.join([str(n) for n in swaps]))
    return arr

Великолепно. Реализую все решение целиком:

def xor(s: bytes, key: list[int]) -> bytes:
   return bytes([s[i] ^ key[i % len(key)] for i in range(len(s))])


def perm(key, input_nums):
    for i in input_nums:
        tmp = key[i]

        key[i] = key[i + 1]
        key[i + 1] = tmp


def bubble_sort(arr):
    swaps = []
    n = len(arr)
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swaps.append(j)
                swapped = True

        if not swapped:
            break

    return swaps


# main()
if __name__ == '__main__':
    s = b'cPK}[aYr^@ZZR`C]TBP_\\Y_U\x7fUWE'
    key = [8, 7, 5, 4, 1, 3, 2, 6, 9, 10]
    swaps = bubble_sort(key.copy())
    perm(key, swaps)
    xor_key = ''.join([str(n) for n in swaps]).encode()

    print(xor(s, xor_key).decode())

Вы можете его скачать для своих экспериментов.

Ключ получился следующий:

012345601234501230121

А вот и искомый флаг:

SayNoToBoringProgrammingLabs

В этом можно убедиться, запустив бинарный файл:

root@2a01d28f0022:/rev# ./spbctf_4_x86_64 012345601234501230121
Your flag: SayNoToBoringProgrammingLabs