Skip to content

Latest commit

 

History

History
979 lines (836 loc) · 39.5 KB

Royal_Cat.md

File metadata and controls

979 lines (836 loc) · 39.5 KB

Royal Cat

Category: Reversing, 400 Points

Description

Hello agent!

The Queen's royal cat is missing from the grounds. Your mission is to find and bring him home safely.

Hint : You should have a look in the ctfroom.

A binary file was attached.

Solution

Let's run the attached binary:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./Meow
Meow Meow...
Error opening file: No such file or directory

So it's trying to open a file. Which file is that? Perhaps strace can help us find out:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# strace ./Meow
execve("./Meow", ["./Meow"], 0x7ffcac392040 /* 21 vars */) = 0
open("/proc/self/exe", O_RDONLY)        = 3
mmap(NULL, 967990, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb65d57e000
mmap(0x7fb65d57e000, 967592, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x7fb65d57e000
mprotect(0x7fb65d669000, 5430, PROT_READ|PROT_EXEC) = 0
readlink("/proc/self/exe", "/media/sf_CTFs/matrix/Royal_Cat/"..., 4095) = 36
mmap(0x7fb65d66b000, 2105344, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb65d66b000
mmap(0x7fb65d66b000, 1888, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb65d66b000
mprotect(0x7fb65d66b000, 1888, PROT_READ) = 0
mmap(0x7fb65d66c000, 2088045, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x1000) = 0x7fb65d66c000
mprotect(0x7fb65d66c000, 2088045, PROT_READ|PROT_EXEC) = 0
mmap(0x7fb65d86a000, 568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x1ff000) = 0x7fb65d86a000
mprotect(0x7fb65d86a000, 568, PROT_READ) = 0
mmap(0x7fb65d86b000, 4216, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x1ff000) = 0x7fb65d86b000
mprotect(0x7fb65d86b000, 4216, PROT_READ|PROT_WRITE) = 0
open("/lib64/ld-linux-x86-64.so.2", O_RDONLY) = 4
read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\20\0\0\0\0\0\0"..., 1024) = 1024
mmap(NULL, 184320, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb65d551000
mmap(0x7fb65d551000, 3944, PROT_READ, MAP_PRIVATE|MAP_FIXED, 4, 0) = 0x7fb65d551000
mmap(0x7fb65d552000, 127056, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 4, 0x1000) = 0x7fb65d552000
mmap(0x7fb65d572000, 31556, PROT_READ, MAP_PRIVATE|MAP_FIXED, 4, 0x21000) = 0x7fb65d572000
mmap(0x7fb65d57b000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 4, 0x29000) = 0x7fb65d57b000
mmap(0x7fb65d57d000, 376, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb65d57d000
close(4)                                = 0
mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb65d550000
close(3)                                = 0
munmap(0x7fb65d57e000, 967990)          = 0
brk(NULL)                               = 0x7fb65e356000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=101561, ...}) = 0
mmap(NULL, 101561, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb65d652000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0n\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1839792, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb65d650000
mmap(NULL, 1852680, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fb65d38b000
mprotect(0x7fb65d3b0000, 1662976, PROT_NONE) = 0
mmap(0x7fb65d3b0000, 1355776, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7fb65d3b0000
mmap(0x7fb65d4fb000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x170000) = 0x7fb65d4fb000
mmap(0x7fb65d546000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7fb65d546000
mmap(0x7fb65d54c000, 13576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb65d54c000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fb65d651540) = 0
mprotect(0x7fb65d546000, 12288, PROT_READ) = 0
mprotect(0x7fb65d86b000, 4096, PROT_READ) = 0
mprotect(0x7fb65d57b000, 4096, PROT_READ) = 0
munmap(0x7fb65d652000, 101561)          = 0
ptrace(PTRACE_TRACEME)                  = -1 EPERM (Operation not permitted)
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0), ...}) = 0
brk(NULL)                               = 0x7fb65e356000
brk(0x7fb65e377000)                     = 0x7fb65e377000
write(1, "I'm being debugged!\n", 20I'm being debugged!
)   = 20
exit_group(1)                           = ?
+++ exited with 1 +++

Looks like it has some basic anti-debug mechanism which uses ptrace and aborts when it detects a debugger.

Perhaps strings?

root@kali:/media/sf_CTFs/matrix/Royal_Cat# strings ./Meow | head
UPX!
m@/P
 G&8
/lib64
nux-x86-
.so.
v+_8/
fopen
y{rror
telluts

Well, no useful filename found there, but notice how the first string is "UPX!". This is usually the signature of executables packed with UPX - an open source executable packer. In order to investigate the executable we probably want to decompress it first:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# upx -d ./Meow
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   2101968 <-    969180   46.11%   linux/amd64   Meow

Unpacked 1 file.

The strings look much better now:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# strings ./Meow
/lib64/ld-linux-x86-64.so.2
fopen
perror
ftell
puts
feof
fgetc
memset
fseek
fclose
ptrace
__cxa_finalize
__libc_start_main
libc.so.6
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u/UH
[]A\A]A^A_
I'm being debugged!
Meow Meow...
RoyalCat
Error opening file
It seems you have got an interesting file...
Maybe you should look at the CTFRoom !
I think you are missing something.
;*3$"
GCC: (Debian 9.3.0-15) 9.3.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.7452
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
final.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
_ITM_deregisterTMCloneTable
puts@@GLIBC_2.2.5
_edata
fclose@@GLIBC_2.2.5
memset@@GLIBC_2.2.5
fgetc@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
__data_start
ftell@@GLIBC_2.2.5
feof@@GLIBC_2.2.5
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
fseek@@GLIBC_2.2.5
validate
ptrace@@GLIBC_2.2.5
__bss_start
main
fopen@@GLIBC_2.2.5
perror@@GLIBC_2.2.5
__TMC_END__
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5
.symtab
.strtab
.shstrtab
.interp
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.got.plt
.data
.bss
.comment

Let's proceed to view the binary in a disassembler. Note that due to excessive usage of the stack in the main function, Ghidra fails to open the binary unless we uncheck the "stack" analysis option. This means that we can't view the C decompilation output for the main function.

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined main()
             undefined         AL:1           <RETURN>
                             main                                            XREF[4]:     Entry Point(*), 
                                                                                          _start:5555555550fd(*), 
                                                                                          5555557530f0, 5555557531b8(*)  
    555555555296 55              PUSH       RBP
    555555555297 48 89 e5        MOV        RBP,RSP
    55555555529a 48 81 ec        SUB        RSP,0xcbb00
                 00 bb 0c 00
    5555555552a1 b9 00 00        MOV        ECX,0x0
                 00 00
    5555555552a6 ba 01 00        MOV        EDX,0x1
                 00 00
    5555555552ab be 00 00        MOV        ESI,0x0
                 00 00
    5555555552b0 bf 00 00        MOV        EDI,0x0
                 00 00
    5555555552b5 b8 00 00        MOV        EAX,0x0
                 00 00
    5555555552ba e8 e1 fd        CALL       ptrace                                           long ptrace(__ptrace_request __r
                 ff ff
    5555555552bf 48 83 f8 ff     CMP        RAX,-0x1
    5555555552c3 75 16           JNZ        LAB_5555555552db
    5555555552c5 48 8d 3d        LEA        RDI,[s_I'm_being_debugged!_555555753008]         = "I'm being debugged!"
                 3c dd 1f 00
    5555555552cc e8 5f fd        CALL       puts                                             int puts(char * __s)
                 ff ff
    5555555552d1 b8 01 00        MOV        EAX,0x1
                 00 00
    5555555552d6 e9 16 d9        JMP        LAB_555555752bf1
                 1f 00
                             LAB_5555555552db                                XREF[1]:     5555555552c3(j)  
    5555555552db 48 8d 3d        LEA        RDI,[s_Meow_Meow..._55555575301c]                = "Meow Meow..."
                 3a dd 1f 00
    5555555552e2 e8 49 fd        CALL       puts                                             int puts(char * __s)
                 ff ff
    5555555552e7 c7 45 f8        MOV        dword ptr [RBP + -0x8],0x0
                 00 00 00 00
    5555555552ee 48 8d 35        LEA        RSI,[DAT_555555753029]                           = 72h    r
                 34 dd 1f 00
    5555555552f5 48 8d 3d        LEA        RDI,[s_RoyalCat_55555575302b]                    = "RoyalCat"
                 2f dd 1f 00
    5555555552fc e8 af fd        CALL       fopen                                            FILE * fopen(char * __filename, 
                 ff ff
    555555555301 48 89 45 f0     MOV        qword ptr [RBP + -0x10],RAX
    555555555305 48 83 7d        CMP        qword ptr [RBP + -0x10],0x0
                 f0 00
    55555555530a 75 16           JNZ        LAB_555555555322
    55555555530c 48 8d 3d        LEA        RDI,[s_Error_opening_file_555555753034]          = "Error opening file"
                 21 dd 1f 00
    555555555313 e8 a8 fd        CALL       perror                                           void perror(char * __s)
                 ff ff
    555555555318 b8 ff ff        MOV        EAX,0xffffffff
                 ff ff
    55555555531d e9 cf d8        JMP        LAB_555555752bf1
                 1f 00
                             LAB_555555555322                                XREF[1]:     55555555530a(j)  
    555555555322 83 7d f8 00     CMP        dword ptr [RBP + -0x8],0x0
    555555555326 0f 84 c0        JZ         LAB_555555752bec
                 d8 1f 00
    55555555532c 48 8b 45 f0     MOV        RAX,qword ptr [RBP + -0x10]
    555555555330 ba 02 00        MOV        EDX,0x2
                 00 00
    555555555335 be 00 00        MOV        ESI,0x0
                 00 00
    55555555533a 48 89 c7        MOV        RDI,RAX
    55555555533d e8 4e fd        CALL       fseek                                            int fseek(FILE * __stream, long 
                 ff ff
    555555555342 48 8b 45 f0     MOV        RAX,qword ptr [RBP + -0x10]
    555555555346 48 89 c7        MOV        RDI,RAX
    555555555349 e8 22 fd        CALL       ftell                                            long ftell(FILE * __stream)
                 ff ff
    55555555534e 89 45 ec        MOV        dword ptr [RBP + -0x14],EAX
    555555555351 48 8b 45 f0     MOV        RAX,qword ptr [RBP + -0x10]
    555555555355 ba 00 00        MOV        EDX,0x0
                 00 00
    55555555535a be 00 00        MOV        ESI,0x0
                 00 00
    55555555535f 48 89 c7        MOV        RDI,RAX
    555555555362 e8 29 fd        CALL       fseek                                            int fseek(FILE * __stream, long 
                 ff ff
    555555555367 48 8d 85        LEA        RAX,[RBP + -0xcbb00]
                 00 45 f3 ff
    55555555536e ba e0 ba        MOV        EDX,0xcbae0
                 0c 00
    555555555373 be 00 00        MOV        ESI,0x0
                 00 00
    555555555378 48 89 c7        MOV        RDI,RAX
    55555555537b e8 d0 fc        CALL       memset                                           void * memset(void * __s, int __
                 ff ff
    555555555380 c7 85 00        MOV        dword ptr [RBP + -0xcbb00],0x238
                 45 f3 ff 
                 38 02 00 00
    55555555538a c7 85 04        MOV        dword ptr [RBP + -0xcbafc],0x1d4
                 45 f3 ff 
                 d4 01 00 00
    555555555394 c7 85 08        MOV        dword ptr [RBP + -0xcbaf8],0x2b8
                 45 f3 ff 
                 b8 02 00 00
    
    ; ... many more MOV instructions ...

    5555557522aa c7 85 44        MOV        dword ptr [RBP + -0x1bc],0x8e
                 fe ff ff 
                 8e 00 00 00
    5555557522b4 c7 85 48        MOV        dword ptr [RBP + -0x1b8],0x2ec
                 fe ff ff 
                 ec 02 00 00
    5555557522be c7 85 4c        MOV        dword ptr [RBP + -0x1b4],0x1ca
                 fe ff ff 
                 ca 01 00 00
    5555557522c8 c7 45 fc        MOV        dword ptr [RBP + -0x4],0x32e54
                 54 2e 03 00
    5555557522cf e9 c0 08        JMP        LAB_555555752b94
                 00 00

We can try to reconstruct our own decompilation from the assembly:

void main()
{
    uint32_t    counter;        // RBP + -0x4
    uint32_t    dummy;          // RBP + -0x8
    FILE*       p_file;         // RBP + -0x10
    uint32_t    file_size;      // RBP + -0x14
    uint32_t    array[208568];  // RBP + -0xcbb00

    if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1)
    {
        puts("I'm being debugged!");
        return 1;
    }

    puts("Meow Meow...");

    dummy = 0;

    p_file = fopen("RoyalCat", 'r');
    if (p_file == NULL)
    {
        perror("Error opening file");
        return -1;
    }

    if (dummy == 0)
    {
        return 0;
    }

    fseek(p_file, 0, SEEK_END);

    file_size = ftell(p_file);

    fseek(p_file, 0, SEEK_SET);

    memset(array, 0, sizeof(array));

    array[0] = 0x238;
    array[1] = 0x1d4;
    array[2] = 0x2b8;
    // ... many more assignments
    array[208465] = 0x8e;
    array[208466] = 0x2ec;
    array[208467] = 0x1ca;

    counter = 208468;
}

So what we see here is:

  • A simple anti-debug trick with ptrace
  • Another anti-debug anti-anything trick with dummy? Looks like we'll need to patch the program to bypass this
  • An attempt to open a file named RoyalCat and identify its size
  • Filling an array with many different values up to a certain point
  • Initializing a counter to the number of values filled in the array

Let's continue analyzing what happens when we jump to LAB_555555752b94:

                             LAB_555555752b94                                XREF[1]:     5555557522cf(j)  
    555555752b94 8b 45 fc        MOV        EAX,dword ptr [RBP + -0x4]
    555555752b97 3b 45 ec        CMP        EAX,dword ptr [RBP + -0x14]
    555555752b9a 0f 8c 34        JL         middle_label
                 f7 ff ff

Well, over here we check if counter < file_size and jump to middle_label if so.

                             middle_label                                    XREF[1]:     555555752b9a(j)  
    5555557522d4 81 7d fc        CMP        dword ptr [RBP + -0x4],0x32e53
                 53 2e 03 00
    5555557522db 0f 8e 9d        JLE        LAB_555555752b7e
                 08 00 00
    5555557522e1 81 7d fc        CMP        dword ptr [RBP + -0x4],0x32e54
                 54 2e 03 00
    5555557522e8 0f 8e 89        JLE        LAB_555555752b77
                 08 00 00
    5555557522ee 81 7d fc        CMP        dword ptr [RBP + -0x4],0x32e55
                 55 2e 03 00
    5555557522f5 0f 8e 75        JLE        LAB_555555752b70
                 08 00 00
    
    ; This continues ...

    5555557527b1 81 7d fc        CMP        dword ptr [RBP + -0x4],0x32eb4
                 b4 2e 03 00
    5555557527b8 7e 30           JLE        LAB_5555557527ea
    5555557527ba 81 7d fc        CMP        dword ptr [RBP + -0x4],0x32eb5
                 b5 2e 03 00
    5555557527c1 7e 1d           JLE        LAB_5555557527e0
    5555557527c3 81 7d fc        CMP        dword ptr [RBP + -0x4],0x32eb6
                 b6 2e 03 00
    5555557527ca 7e 0a           JLE        LAB_5555557527d6
    5555557527cc b8 96 00        MOV        EAX,0x96
                 00 00
    5555557527d1 e9 ad 03        JMP        end_label
                 00 00
                             LAB_5555557527d6                                XREF[1]:     5555557527ca(j)  
    5555557527d6 b8 40 01        MOV        EAX,0x140
                 00 00
    5555557527db e9 a3 03        JMP        end_label
                 00 00
                             LAB_5555557527e0                                XREF[1]:     5555557527c1(j)  
    5555557527e0 b8 9c 00        MOV        EAX,0x9c
                 00 00
    5555557527e5 e9 99 03        JMP        end_label
                 00 00

    ; This continues ...

                             LAB_555555752b70                                XREF[1]:     5555557522f5(j)  
    555555752b70 b8 de 02        MOV        EAX,0x2de
                 00 00
    555555752b75 eb 0c           JMP        end_label
                             LAB_555555752b77                                XREF[1]:     5555557522e8(j)  
    555555752b77 b8 cc 01        MOV        EAX,0x1cc
                 00 00
    555555752b7c eb 05           JMP        end_label
                             LAB_555555752b7e                                XREF[1]:     5555557522db(j)  
    555555752b7e b8 00 00        MOV        EAX,0x0
                 00 00
                             end_label                                       XREF[100]:   5555557527d1(j), 5555557527db(j), 
                                                                                          5555557527e5(j), 5555557527ef(j), 
                                                                                          5555557527f9(j), 555555752803(j), 
                                                                                          55555575280d(j), 555555752817(j), 
                                                                                          555555752821(j), 55555575282b(j), 
                                                                                          555555752835(j), 55555575283f(j), 
                                                                                          555555752849(j), 555555752853(j), 
                                                                                          55555575285d(j), 555555752867(j), 
                                                                                          555555752871(j), 55555575287b(j), 
                                                                                          555555752885(j), 55555575288f(j), 
                                                                                          [more]
    555555752b83 8b 55 fc        MOV        EDX,dword ptr [RBP + -0x4]
    555555752b86 48 63 d2        MOVSXD     RDX,EDX
    555555752b89 89 84 95        MOV        dword ptr [RBP + RDX*0x4 + -0xcbb00],EAX
                 00 45 f3 ff
    555555752b90 83 45 fc 01     ADD        dword ptr [RBP + -0x4],0x1
                             LAB_555555752b94                                XREF[1]:     5555557522cf(j)  
    555555752b94 8b 45 fc        MOV        EAX,dword ptr [RBP + -0x4]
    555555752b97 3b 45 ec        CMP        EAX,dword ptr [RBP + -0x14]
    555555752b9a 0f 8c 34        JL         middle_label
                 f7 ff ff

So this is some kind of loop initializing some more array members:

while (counter < file_size)
{
    switch(counter)
    {
        case 0x32e53: array[counter] = 0x0;     break;
        case 0x32e54: array[counter] = 0x1cc;   break;
        case 0x32e55: array[counter] = 0x2de;   break;

        // Many more ...

        case 0x32eb5: array[counter] = 0x9c;    break;
        case 0x32eb6: array[counter] = 0x140;   break;
        case 0x32eb7: array[counter] = 0x96;    break;
    }

    counter += 1;
}

So what we have until now is that the array gets automatically initialized up to index 208468, and if the input file size is larger than 208468 bytes, the file_size - 208468 remaining array members get initializes via the loop, up to a file size of 208568 (which is 0x32eb7 + 1). But what is the array used for?

the remaining part of the function is:

    555555752ba0 48 8d 95        LEA        RDX,[RBP + -0xcbb00]
                 00 45 f3 ff
    555555752ba7 48 8b 45 f0     MOV        RAX,qword ptr [RBP + -0x10]
    555555752bab 48 89 d6        MOV        RSI,RDX
    555555752bae 48 89 c7        MOV        RDI,RAX
    555555752bb1 e8 0f 26        CALL       validate                                         undefined validate()
                 e0 ff
    555555752bb6 85 c0           TEST       EAX,EAX
    555555752bb8 74 1f           JZ         LAB_555555752bd9
    555555752bba 48 8d 3d        LEA        RDI,[s_It_seems_you_have_got_an_interes_555555   = "It seems you have got an inte
                 87 04 00 00
    555555752bc1 e8 6a 24        CALL       puts                                             int puts(char * __s)
                 e0 ff
    555555752bc6 48 8d 3d        LEA        RDI,[s_Maybe_you_should_look_at_the_CTF_555555   = "Maybe you should look at the 
                 ab 04 00 00
    555555752bcd e8 5e 24        CALL       puts                                             int puts(char * __s)
                 e0 ff
    555555752bd2 b8 00 00        MOV        EAX,0x0
                 00 00
    555555752bd7 eb 18           JMP        LAB_555555752bf1
                             LAB_555555752bd9                                XREF[1]:     555555752bb8(j)  
    555555752bd9 48 8d 3d        LEA        RDI,[s_I_think_you_are_missing_somethin_555555   = "I think you are missing somet
                 c0 04 00 00
    555555752be0 e8 4b 24        CALL       puts                                             int puts(char * __s)
                 e0 ff
    555555752be5 b8 00 00        MOV        EAX,0x0
                 00 00
    555555752bea eb 05           JMP        LAB_555555752bf1
                             LAB_555555752bec                                XREF[1]:     555555555326(j)  
    555555752bec b8 00 00        MOV        EAX,0x0
                 00 00
                             LAB_555555752bf1                                XREF[4]:     5555555552d6(j), 55555555531d(j), 
                                                                                          555555752bd7(j), 555555752bea(j)  
    555555752bf1 c9              LEAVE
    555555752bf2 c3              RET

Which translated to C is something like:

if (validate(p_file, array) != 0)
{
    puts("It seems you have got an interesting file...");
    puts("Maybe you should look at the CTFRoom !");
}
else
{
    puts("I think you are missing something.");

}

return 0;

Fortunately, Ghidra is able to decompile validate successfully so we don't need to do it manually:

undefined8 validate(FILE *p_file,int *p_array)
{
  int iVar1;
  int iVar2;
  uint uStack12;
  
  uStack12 = 0;
  while( true ) {
    iVar1 = fgetc(p_file);
    iVar2 = feof(p_file);
    if (iVar2 != 0) {
      fclose(p_file);
      return 1;
    }
    if (((uStack12 & 1) == 0) && (((iVar1 + 0x45) * 4 ^ 100U) != p_array[(int)uStack12])) break;
    if (((uStack12 - ((int)uStack12 >> 0x1f) & 1) + ((int)uStack12 >> 0x1f) == 1) &&
       (((iVar1 + 0x62) * 2 ^ 0x52U) != p_array[(int)uStack12])) {
      return 0;
    }
    uStack12 = uStack12 + 1;
  }
  return 0;
}

So what this function does is read the file byte by byte, perform some manipulation on each byte and compare it to the matching array member, and if no mismatch is found up to the end of the file, return success. Since the function runs just up to the end of the file, we can apparently get it to print success by providing it an empty file. But first, we have to patch the program in order to get it past this logic:

    5555555552e7 c7 45 f8        MOV        dword ptr [RBP + -0x8],0x0
                 00 00 00 00
    ; ...

    555555555322 83 7d f8 00     CMP        dword ptr [RBP + -0x8],0x0
    555555555326 0f 84 c0        JZ         LAB_555555752bec 

    ; ...

                                LAB_555555752bec                                XREF[1]:     555555555326(j)  
    555555752bec b8 00 00        MOV        EAX,0x0
                 00 00
                             LAB_555555752bf1                                XREF[4]:     5555555552d6(j), 55555555531d(j), 
                                                                                          555555752bd7(j), 555555752bea(j)  
    555555752bf1 c9              LEAVE
    555555752bf2 c3              RET 

We'll patch it by setting [RBP + -0x8] to 0x1 instead of 0x0. To do so, we change c7 45 f8 00 00 00 00 to ``c7 45 f8 00 00 00 01`.

root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./Meow
Meow Meow...
Error opening file: No such file or directory
root@kali:/media/sf_CTFs/matrix/Royal_Cat# touch RoyalCat
root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./Meow
Meow Meow...
root@kali:/media/sf_CTFs/matrix/Royal_Cat# cp Meow MeowPatched
root@kali:/media/sf_CTFs/matrix/Royal_Cat# printf '\x01' | dd conv=notrunc of=MeowPatched bs=1 seek=$((0x12ED))
1+0 records in
1+0 records out
1 byte copied, 0.00130419 s, 0.8 kB/s
root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./MeowPatched
Meow Meow...
It seems you have got an interesting file...
Maybe you should look at the CTFRoom !

Of course, this is just an empty file we've provided, nothing interesting there. Let's see if we can get the same output for a file of length 1. But first, what happens if we get it wrong?

root@kali:/media/sf_CTFs/matrix/Royal_Cat# printf '\xff' > RoyalCat
root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./MeowPatched
Meow Meow...
I think you are missing something.

Now, can we get it right? We have two different expected values based on the index of the current byte:

    if (((uStack12 & 1) == 0) && (((iVar1 + 0x45) * 4 ^ 100U) != p_array[(int)uStack12])) break;
    if (((uStack12 - ((int)uStack12 >> 0x1f) & 1) + ((int)uStack12 >> 0x1f) == 1) &&
       (((iVar1 + 0x62) * 2 ^ 0x52U) != p_array[(int)uStack12])) {
      return 0;
    }

If the index is even (((uStack12 & 1) == 0)) then the expected value is ((iVar1 + 0x45) * 4 ^ 100U). If the index satisfies the condition ((uStack12 - ((int)uStack12 >> 0x1f) & 1) + ((int)uStack12 >> 0x1f) == 1) then the expected value is ((iVar1 + 0x62) * 2 ^ 0x52U). But what is this condition?

>>> for uStack12 in range(10):
...     print(((uStack12 - (uStack12 >> 0x1f) & 1) + (uStack12 >> 0x1f) == 1))
...
False
True
False
True
False
True
False
True
False
True
>>> for uStack12 in range(0x32eb7 + 1):
...     assert(((uStack12 - (uStack12 >> 0x1f) & 1) + (uStack12 >> 0x1f) == 1) == ( (uStack12 & 1) == 1))
...
>>>

So for our case, we can just consider this a fancy way to check if the index is odd.

The first index is 0 so we need to use the even case condition. We saw before that array[0] = 0x238;. Now we need to find a value so that ((value + 0x45) * 4 ^ 100U) == 0x238.

>>> reverse_even = lambda x: hex(((x ^ 100)//4)-0x45)
>>> reverse_even(0x238)
'0x52'

Let's try it:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# printf '\x52' > RoyalCat
root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./MeowPatched
Meow Meow...
It seems you have got an interesting file...
Maybe you should look at the CTFRoom !

Luckily, we got it and didn't hit any overflow / division issues. Let's try one odd index. We know that array[1] = 0x1d4;.

>>> reverse_odd = lambda x: hex(((x ^ 0x52)//2)-0x62)
>>> reverse_odd(0x1d4)
'0x61'

Let's try it:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# printf '\x52\x61' > RoyalCat
root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./MeowPatched
Meow Meow...
It seems you have got an interesting file...
Maybe you should look at the CTFRoom !

Ok, great. So we understand the mechanism. Now, we need to automate this so that the full file gets reconstructed. We could try brute forcing the file byte-by-byte with a script like this:

from subprocess import check_output
from pwn import *

royal_cat_bytes = bytearray()

with log.progress('Brute forcing RoyalCat') as p:
    while True:
        p.status(f"Searching for byte #{len(royal_cat_bytes)}")
        royal_cat_bytes.append(0)
        found = False
        for b in range(256):
            royal_cat_bytes[-1] = b
            with open("RoyalCat", "wb") as f:
                f.write(royal_cat_bytes)
            out = check_output(["./MeowPatched"])
            if b"It seems you have got an interesting file" in out:
                found = True
                break
        if not found:
            break

with open("RoyalCat", "wb") as f:
    f.write(royal_cat_bytes[:-1])

But that would take ages, since for each byte we'd need to run the program 256 times in the worst case. Instead, we'll dump the array from memory and use that to reconstruct the file just like we did in the manual examples. To ensure that the array is fully initialized, we need to provide any file of 0x32eb7+1 bytes.

root@kali:/media/sf_CTFs/matrix/Royal_Cat# python -c "print (b'\xff' * (0x32eb7+1))" > RoyalCat

We also need to bypass the ptrace anti-debug method. Instead of patching again, we'll do that with this trick:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# nano ptrace.c
root@kali:/media/sf_CTFs/matrix/Royal_Cat# cat ptrace.c
long ptrace(int request, int pid, void *addr, void *data) {
    return 0;
}

root@kali:/media/sf_CTFs/matrix/Royal_Cat# gcc -shared ptrace.c -o ptrace.so

Now we can run the program in the debugger and set a breakpoint at validate:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# gdb -ex 'set environment LD_PRELOAD=./ptrace.so' -n ./MeowPatched
GNU gdb (Debian 8.3.1-1) 8.3.1
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./MeowPatched...
(No debugging symbols found in ./MeowPatched)
(gdb) b validate
Breakpoint 1 at 0x11c9
(gdb) r
Starting program: /media/sf_CTFs/matrix/Royal_Cat/MeowPatched
Meow Meow...

Breakpoint 1, 0x00005555555551c9 in validate ()
(gdb)

We want to dump array which is the second parameter to validate.

(gdb) set disassembly-flavor intel
(gdb) disas validate, +20
Dump of assembler code from 0x5555555551c5 to 0x5555555551d9:
   0x00005555555551c5 <validate+0>:     push   rbp
   0x00005555555551c6 <validate+1>:     mov    rbp,rsp
=> 0x00005555555551c9 <validate+4>:     sub    rsp,0x20
   0x00005555555551cd <validate+8>:     mov    QWORD PTR [rbp-0x18],rdi
   0x00005555555551d1 <validate+12>:    mov    QWORD PTR [rbp-0x20],rsi
   0x00005555555551d5 <validate+16>:    mov    DWORD PTR [rbp-0x4],0x0
End of assembler dump.
(gdb) p/x $rsi
$1 = 0x7ffffff32780

We want to dump the array to a file:

(gdb) p/x ( ($rsi) + ((0x32eb7+1)*4) )
$2 = 0x7fffffffe260
(gdb) dump binary memory array.bin 0x7ffffff32780 0x7fffffffe260

Let's double check the values we got with the expected values we saw earlier:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# xxd -g 4 -e array.bin | head -n 1
00000000: 00000238 000001d4 000002b8 00000154  8...........T...
root@kali:/media/sf_CTFs/matrix/Royal_Cat# xxd -g 4 -e array.bin | tail -n 1
000cbad0: 00000144 0000009c 00000140 00000096  D.......@.......

Looks good. Now, let's use the values to recreate the file:

reverse_even = lambda x: bytes([((x ^ 100) // 4) - 0x45])
reverse_odd  = lambda x: bytes([((x ^ 0x52) // 2) - 0x62])

with open("array.bin", "rb") as f, open("RoyalCat", "wb") as o:
    index = 0
    while (dword := f.read(4)) != b"":
        value = int.from_bytes(dword, byteorder='little')
        b = reverse_even(value) if (index % 2 == 0) else reverse_odd(value)
        o.write(b)
        index += 1

And what do we get?

root@kali:/media/sf_CTFs/matrix/Royal_Cat# python3 solve.py
root@kali:/media/sf_CTFs/matrix/Royal_Cat# ./MeowPatched
Meow Meow...
It seems you have got an interesting file...
Maybe you should look at the CTFRoom !
root@kali:/media/sf_CTFs/matrix/Royal_Cat# file RoyalCat
RoyalCat: RAR archive data, v5

A RAR file! let's extract it:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# unrar l RoyalCat

UNRAR 5.91 freeware      Copyright (c) 1993-2020 Alexander Roshal

Archive: RoyalCat
Details: RAR 5

 Attributes      Size     Date    Time   Name
----------- ---------  ---------- -----  ----
*   ..A....    220164  2021-02-02 13:53  Meow.jpeg
----------- ---------  ---------- -----  ----
               220164                    1

root@kali:/media/sf_CTFs/matrix/Royal_Cat# unrar x RoyalCat

UNRAR 5.91 freeware      Copyright (c) 1993-2020 Alexander Roshal


Extracting from RoyalCat

Enter password (will not be echoed) for Meow.jpeg:

Looks like this file is password protected, and we don't know the password. Time to visit the CTF Room as suggested. It contains a live stream of a ticking bomb together with some LEDs and a servo.

We are requested to enter the cat's name in order to proceed. We tried everything here. We searched every bit in the binary for a cat's name. We tried to submit common cat names, and tried them too as the RAR password. We tried to crack the RAR with rockyou.txt, or with a general brute force attack. We also tried searching for the cat name in other challenges (this was the last challenge we had left in order to unlock "Exclusive Bomb"). We reverse-searched the image. We searched for real-life royal cats in Google. We tried the Cheshire Cat from "Alice in Wonderland", since there's another reversing challenge named "Hatter" and since the arrow under the question mark in the CTF room reminded us of his famous grin. We spent hours on this. Eventually, while thinking about additional ways to brute force the RAR, it occurred to us to try hashcat... and it worked!

At some point the creators changed the logo in the CTF room from the cat image above to the hashcat logo, making it pretty much trivial to pass this part.

Anyway, after entering the correct name, the servo in the background displayed the following string: A?l?d!D3?u?l9. This is a hashcat pattern for brute-forcing the RAR password. We can follow this guide to crack the password.

root@kali:/media/sf_CTFs/matrix/Royal_Cat# ~/utils/john/run/rar2john RoyalCat > rar.john
root@kali:/media/sf_CTFs/matrix/Royal_Cat# cat rar.john
RoyalCat:$rar5$16$dd0d97236c58b730c8fd7c4d85c653f2$15$6af89efb4eb5dab620c901e4e8f2141b$8$907d86ab9b525ba2
root@kali:/media/sf_CTFs/matrix/Royal_Cat#  cat rar.john  | grep -E -o '(\$RAR3\$[^:]+)|(\$rar5\$.*)' > rar.hash
root@kali:/media/sf_CTFs/matrix/Royal_Cat#  cat rar.hash
$rar5$16$dd0d97236c58b730c8fd7c4d85c653f2$15$6af89efb4eb5dab620c901e4e8f2141b$8$907d86ab9b525ba2
root@kali:/media/sf_CTFs/matrix/Royal_Cat# hashcat -m 13000  -a 3 rar.hash 'A?l?d!D3?u?l9' --force
hashcat (v5.1.0) starting...

OpenCL Platform #1: The pocl project
====================================
* Device #1: pthread-Intel(R) Core(TM)2 Duo CPU     T8100  @ 2.10GHz, 512/1483 MB allocatable, 2MCU

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates

Applicable optimizers:
* Zero-Byte
* Single-Hash
* Single-Salt
* Brute-Force
* Slow-Hash-SIMD-LOOP

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.

* Device #1: build_opts '-cl-std=CL1.2 -I OpenCL -I /usr/share/hashcat/OpenCL -D LOCAL_MEM_TYPE=2 -D VENDOR_ID=64 -D CUDA_ARCH=0 -D AMD_ROCM=0 -D VECT_SIZE=4 -D DEVICE_TYPE=2 -D DGST_R0=0 -D DGST_R1=1 -D DGST_R2=2 -D DGST_R3=3 -D DGST_ELEM=4 -D KERN_TYPE=13000 -D _unroll'
* Device #1: Kernel m13000-pure.750cfacb.kernel not found in cache! Building may take a while...
* Device #1: Kernel markov_le.92f06ff5.kernel not found in cache! Building may take a while...
* Device #1: Kernel amp_a3.92f06ff5.kernel not found in cache! Building may take a while...
[s]tatus [p]ause [b]ypass [c]heckpoint [q]uit => 

$rar5$16$dd0d97236c58b730c8fd7c4d85c653f2$15$6af89efb4eb5dab620c901e4e8f2141b$8$907d86ab9b525ba2:An7!D3Bu9

Session..........: hashcat
Status...........: Cracked
Hash.Type........: RAR5
Hash.Target......: $rar5$16$dd0d97236c58b730c8fd7c4d85c653f2$15$6af89e...525ba2
Time.Started.....: Fri Feb 19 09:55:17 2021 (4 mins, 0 secs)
Time.Estimated...: Fri Feb 19 09:59:17 2021 (0 secs)
Guess.Mask.......: A?l?d!D3?u?l9 [9]
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:      122 H/s (7.93ms) @ Accel:256 Loops:64 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 29184/175760 (16.60%)
Rejected.........: 0/29184 (0.00%)
Restore.Point....: 28672/175760 (16.31%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:32768-32799
Candidates.#1....: Av0!D3Jr9 -> Ay3!D3Ng9

Started: Fri Feb 19 09:54:39 2021
Stopped: Fri Feb 19 09:59:18 2021

The password found by hashcat is An7!D3Bu9:

root@kali:/media/sf_CTFs/matrix/Royal_Cat# unrar x -pAn7\!D3Bu9 RoyalCat

UNRAR 5.91 freeware      Copyright (c) 1993-2020 Alexander Roshal


Extracting from RoyalCat

Extracting  Meow.jpeg                                                 OK
All OK

The flag was in the image: MCL{Ar3_Y0U_Look1n9_FoR_M3?}