2017-02-05

AlexCTF 2017: Catalyst system - Reversing 150

For this challenge, I wanted to practice using a tool that has the potential to replace GDB in my toolset: radare2. I would also like to acknowledge a friend who helped me with the most difficult parts (figuring out constraint solving with angr) of this challenge: DigitalCold https://twitter.com/digital_cold

Recon

First thing I do is check out the file using file:

-> % file catalyst
catalyst: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter 
/lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3c4646c45da147f57cfa3fe0b9f1022d84fbe85f, stripped

Next, since this was a binary, I just decided I'd run it.

-> % ./catalyst
 ▄▄▄▄▄▄▄▄▄▄▄  ▄            ▄▄▄▄▄▄▄▄▄▄▄  ▄       ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌▐░▌          ▐░░░░░░░░░░░▌▐░▌     ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
▐░█▀▀▀▀▀▀▀█░▌▐░▌          ▐░█▀▀▀▀▀▀▀▀▀  ▐░▌   ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ 
▐░▌       ▐░▌▐░▌          ▐░▌            ▐░▌ ▐░▌  ▐░▌               ▐░▌     ▐░▌          
▐░█▄▄▄▄▄▄▄█░▌▐░▌          ▐░█▄▄▄▄▄▄▄▄▄    ▐░▐░▌   ▐░▌               ▐░▌     ▐░█▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌▐░▌          ▐░░░░░░░░░░░▌    ▐░▌    ▐░▌               ▐░▌     ▐░░░░░░░░░░░▌
▐░█▀▀▀▀▀▀▀█░▌▐░▌          ▐░█▀▀▀▀▀▀▀▀▀    ▐░▌░▌   ▐░▌               ▐░▌     ▐░█▀▀▀▀▀▀▀▀▀ 
▐░▌       ▐░▌▐░▌          ▐░▌            ▐░▌ ▐░▌  ▐░▌               ▐░▌     ▐░▌          
▐░▌       ▐░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄  ▐░▌   ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄      ▐░▌     ▐░▌          
▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌     ▐░▌▐░░░░░░░░░░░▌     ▐░▌     ▐░▌          
 ▀         ▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀       ▀  ▀▀▀▀▀▀▀▀▀▀▀       ▀       ▀           
Welcome to Catalyst systems
Loading....

Running it produced this colorful banner, but then proceeded to "load" for the next several minutes.
Being impatient, I decided to take a look at the binary in radare2 while it "loaded":

-> % radare2 catalyst
[0x00400780]> pd @ main
            ;-- main:
            0x00400d93      55             push rbp
            0x00400d94      4889e5         mov rbp, rsp
            0x00400d97      4883ec20       sub rsp, 0x20
            0x00400d9b      bfe8030000     mov edi, 0x3e8
            0x00400da0      e87bf9ffff     call sym.imp.malloc
            0x00400da5      488945f0       mov qword [rbp - 0x10], rax
            0x00400da9      bfe8030000     mov edi, 0x3e8
            0x00400dae      e86df9ffff     call sym.imp.malloc
            0x00400db3      488945e8       mov qword [rbp - 0x18], rax
            0x00400db7      bf00000000     mov edi, 0
            0x00400dbc      e84ff9ffff     call sym.imp.time
            0x00400dc1      89c7           mov edi, eax
            0x00400dc3      e838f9ffff     call sym.imp.srand
            0x00400dc8      bf88104000     mov edi, 0x401088
            0x00400dcd      e8fef8ffff     call sym.imp.puts
...
<SNIP>
...
            0x00400e31      e89af8ffff     call sym.imp.puts
            0x00400e36      bf90184000     mov edi, str._e_0mWelcome_to_Catalyst_systems ; str._e_0mWelcome_to_Catalyst_systems
            0x00400e3b      e890f8ffff     call sym.imp.puts
            0x00400e40      bfb0184000     mov edi, str.Loading        ; "Loading" @ 0x4018b0
            0x00400e45      b800000000     mov eax, 0
            0x00400e4a      e8a1f8ffff     call sym.imp.printf
            0x00400e4f      488b05721220.  mov rax, qword [obj.stdout] 
            0x00400e56      4889c7         mov rdi, rax
            0x00400e59      e8d2f8ffff     call sym.imp.fflush
...
<SNIP>

We can immediately see at addresses 0x400dcd and afterward a series of puts calls which are used to print the welcome banner to the screen, as well as references to the Welcome to Catalyst... and Loading strings.

Immediately following this code we see the following:

            0x00400e5e      c745fc000000.  mov dword [rbp - 4], 0       #loop counter initialization
        ,=< 0x00400e65      eb3e           jmp 0x400ea5                 #jump to check
       .--> 0x00400e67      e804f9ffff     call sym.imp.rand
       ||   0x00400e6c      89c6           mov esi, eax
       ||   0x00400e6e      8b55fc         mov edx, dword [rbp - 4]
       ||   0x00400e71      89d0           mov eax, edx
       ||   0x00400e73      01c0           add eax, eax
       ||   0x00400e75      01d0           add eax, edx
       ||   0x00400e77      8d4801         lea ecx, dword [rax + 1]    
       ||   0x00400e7a      89f0           mov eax, esi
       ||   0x00400e7c      99             cdq
       ||   0x00400e7d      f7f9           idiv ecx
       ||   0x00400e7f      89d0           mov eax, edx
       ||   0x00400e81      89c7           mov edi, eax
       ||   0x00400e83      e8d8f8ffff     call sym.imp.sleep           #useless waiting
       ||   0x00400e88      bf2e000000     mov edi, 0x2e               
       ||   0x00400e8d      e82ef8ffff     call sym.imp.putchar
       ||   0x00400e92      488b052f1220.  mov rax, qword [obj.stdout] 
       ||   0x00400e99      4889c7         mov rdi, rax
       ||   0x00400e9c      e88ff8ffff     call sym.imp.fflush
       ||   0x00400ea1      8345fc01       add dword [rbp - 4], 1       #increment loop counter
       |`-> 0x00400ea5      837dfc1d       cmp dword [rbp - 4], 0x1d    #see if we've drawn 29 dot (0x2e) characters
       `==< 0x00400ea9      7ebc           jle 0x400e67

We can see here that after printing the Loading string, that at address 0x400e5e, we have a counter initialized to 0, followed by an unconditional jump to address 0x00400ea5 which checks that counter's value against 0x1d. Since it is less the first time (having initialized to 0 at 0x00400e5e), we proceed to jump back up (following the jle 0x400e67) to enter our "Loading" loop.

We see that within this loop, sym.imp.rand is called first before sym.imp.sleep is called (which explains the variable-duration waiting during the "Loading" phase). After each sleep occurs, the loop counter is incremented at 0x400ea1.

Since nobody likes needless waiting, I wanted to avoid this sleep loop. As an extra precaution, I scanned ahead in the binary and found a second "Loading" loop at 0x00400f1d:

[0x00400780]> pd @ 0x00400f1d
            0x00400f1d      c745f8000000.  mov dword [rbp - 8], 0
        ,=< 0x00400f24      eb38           jmp 0x400f5e
       .--> 0x00400f26      e845f8ffff     call sym.imp.rand
       ||   0x00400f2b      89c2           mov edx, eax
       ||   0x00400f2d      8b45f8         mov eax, dword [rbp - 8]
       ||   0x00400f30      8d4801         lea ecx, dword [rax + 1]    
       ||   0x00400f33      89d0           mov eax, edx
       ||   0x00400f35      99             cdq
       ||   0x00400f36      f7f9           idiv ecx
       ||   0x00400f38      89d0           mov eax, edx
       ||   0x00400f3a      89c7           mov edi, eax
       ||   0x00400f3c      e81ff8ffff     call sym.imp.sleep
       ||   0x00400f41      bf2e000000     mov edi, 0x2e               
       ||   0x00400f46      e875f7ffff     call sym.imp.putchar
       ||   0x00400f4b      488b05761120.  mov rax, qword [obj.stdout] 
       ||   0x00400f52      4889c7         mov rdi, rax
       ||   0x00400f55      e8d6f7ffff     call sym.imp.fflush
       ||   0x00400f5a      8345f801       add dword [rbp - 8], 1
       |`-> 0x00400f5e      837df81d       cmp dword [rbp - 8], 0x1d   
       `==< 0x00400f62      7ec2           jle 0x400f26

Patching the Binary

In order to patch the binary we have to re-open it in -write mode:

-> % radare2 -w catalyst
[0x00400780]> pd 1@ 0x00400ea9
        `=< 0x00400ea9      7ebc           jle 0x400e67     #instruction is 2 bytes
[0x00400780]> pd 1@ 0x00400f62
        `=< 0x00400f62      7ec2           jle 0x400f26
[0x00400780]> wx 9090 @ 0x00400ea9                          #so we write 2 'NOP' bytes
[0x00400780]> wx 9090 @ 0x00400f62
[0x00400780]> pd 2 @ 0x00400ea9                             #and verify the jumps were overwritten
            0x00400ea9      90             nop
            0x00400eaa      90             nop
[0x00400780]> pd 2 @ 0x00400f62
            0x00400f62      90             nop
            0x00400f63      90             nop

In the highlighted lines you can see the commands used to print disassembly (pd), write hex (wx), and again print the disassembly for one of the two loading loops. Radare2 writes these bytes directly to disk, which means I was able to close and verify that I could run the program:

[0x00400780]> qy
tobaljackson@binarystudio [06:26:50 PM] [~/AlexCTF/RE3]
-> % ./catalyst
 ▄▄▄▄▄▄▄▄▄▄▄  ▄            ▄▄▄▄▄▄▄▄▄▄▄  ▄       ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌▐░▌          ▐░░░░░░░░░░░▌▐░▌     ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌
▐░█▀▀▀▀▀▀▀█░▌▐░▌          ▐░█▀▀▀▀▀▀▀▀▀  ▐░▌   ▐░▌ ▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ 
▐░▌       ▐░▌▐░▌          ▐░▌            ▐░▌ ▐░▌  ▐░▌               ▐░▌     ▐░▌          
▐░█▄▄▄▄▄▄▄█░▌▐░▌          ▐░█▄▄▄▄▄▄▄▄▄    ▐░▐░▌   ▐░▌               ▐░▌     ▐░█▄▄▄▄▄▄▄▄▄ 
▐░░░░░░░░░░░▌▐░▌          ▐░░░░░░░░░░░▌    ▐░▌    ▐░▌               ▐░▌     ▐░░░░░░░░░░░▌
▐░█▀▀▀▀▀▀▀█░▌▐░▌          ▐░█▀▀▀▀▀▀▀▀▀    ▐░▌░▌   ▐░▌               ▐░▌     ▐░█▀▀▀▀▀▀▀▀▀ 
▐░▌       ▐░▌▐░▌          ▐░▌            ▐░▌ ▐░▌  ▐░▌               ▐░▌     ▐░▌          
▐░▌       ▐░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄  ▐░▌   ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄      ▐░▌     ▐░▌          
▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌     ▐░▌▐░░░░░░░░░░░▌     ▐░▌     ▐░▌          
 ▀         ▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀       ▀  ▀▀▀▀▀▀▀▀▀▀▀       ▀       ▀           
Welcome to Catalyst systems
Loading
Username: HERPDERP
Password: hurrdurr
Logging in
invalid username or password

Relief! Upon quitting radare2 and re-running the binary, I was greeted with a Username and Password prompt, which accepted my input and failed gracefully. Now that I had a binary in hand that wouldn't waste my time, I proceeded to the next phase.

Finding What We Really Want In the Binary (or Life)

At this point, the binary is runnable with input I can control, but it's right around this time I asked myself, "How do I get the flag out of the binary?" Sometimes, the flag is just stored in plaintext in the binary as a string. These kinds of challenges are entry-level type, which this one probably is not, but it doesn't hurt to check.

-> % strings catalyst
...
<SNIP>
__gmon_start__
GLIBC_2.7
GLIBC_2.2.5
<z~(
<z~d
<Z~<
H=VKf\uEH
AWAVA
AUATL
[]A\A]A^A_
your flag is: ALEXCTF{
...
<SNIP>
...
invalid username or password
[0mWelcome to Catalyst systems
Loading
Username: 
Password: 
Logging in
;*3$"
'bA5k
{F<>g
YDr6
TYGCC: (GNU) 6.1.1 20160721 (Red Hat 6.1.1-4)
GCC: (GNU) 6.2.1 20160916 (Red Hat 6.2.1-2)
...
<SNIP>

We can see some strings stored in the binary, including the first part of the flag ALEXCTF{, but nothing that looks like it could be the flag string itself. As to be expected. Next thing to check is whether the flag is stored in an obfuscated or enciphered form which might be easily decoded. To do this, we just need to look in the binary for the winning function, wherever the above highlighted 'victory' string is referenced.

Putting the Reverse in Reverse Engineering

Since the flag is what I'm after, why not try and see how the program makes use of that your flag is: string and work my way backward? Let's load the binary back up in radare and enter Visual mode. After entering visual mode, pressing p once takes you to a assembled byte view. I then used _ (underscore) to display a list of flags that radare2 had identified for me:

0> |
 - 0x00401050  str.your_flag_is:_ALEXCTF_
   0x00401069  str.invalid_username_or_password
...
<SNIP>
...
   0x00401890  str._e_0mWelcome_to_Catalyst_systems
   0x004018b0  str.Loading
   0x004018b8  str.Username:                       
...
<SNIP>

Since the your_flag_is:_ALEXCTF_ string is listed first, pressing <enter> will seek to that memory address:

[0x00401050 39% 912 catalyst]> pd $r @ str.your_flag_is:_ALEXCTF_
            ;-- str.your_flag_is:_ALEXCTF_:
            0x00401050     .string "your flag is: ALEXCTF{" ; len=23
        ,=< 0x00401067      7d00           jge str.invalid_username_or_password ;[1]
        `-> ;-- str.invalid_username_or_password:
        `-> 0x00401069     .string "invalid username or password" ; len=29
            0x00401086      0000           add byte [rax], al

Here we can see that there is a radare flag defined here (the text above 0x401050, starting with ;--), but no XREF defined. To find out where this string is referenced, run our trusty aaa command with :aaa from visual mode. (After executing a command from visual mode with :, simply pressing <enter> will bring you out of cmdline mode):

[0x00401050 39% 996 catalyst]> pd $r @ str.your_flag_is:_ALEXCTF_
            ;-- str.your_flag_is:_ALEXCTF_:
               ; DATA XREF from 0x00400887 (sub.printf_876)
            0x00401050     .string "your flag is: ALEXCTF{" ; len=23
               ; JMP XREF from 0x00401064 (str.your_flag_is:_ALEXCTF_ + 20)
               ; DATA XREF from 0x004008e5 (sub.printf_876)
        ,=< 0x00401067      7d00           jge str.invalid_username_or_password ;[1]
        `-> ;-- str.invalid_username_or_password:
        |      ; XREFS: JMP 0x00401067  DATA 0x00400d7c  DATA 0x00400948  DATA 0x00400a1c  DATA 0x00400c25  DATA 0x00400bf7  DATA 0x00400bc9
        |      ; XREFS: DATA 0x00400b9b DATA 0x00400b6d  DATA 0x00400b3f  DATA 0x00400b11  DATA 0x00400ae3  DATA 0x00400ab5  DATA 0x00400a87
        `-> 0x00401069     .string "invalid username or password" ; len=29             

Here the highlighted lines indicate all the XREF's (cross-references) that were found for these data. Since we're still at our string address, pressing x will bring up radare2's XREF browser:

[GOTO XREF]> 0x00401050
 0 [0] 0x00400887 DATA XREF (sub.printf_876) | 0x00400887   mov edi, str.your_flag_is:_ALEXCTF_      ; "your flag is: ALEXCTF{" @ 0x401050

Pressing 0 or <enter> will take you to the first item on the XREF list. Scrolling up a few lines shows us we're in function sub.printf_876:

[0x00400876 20% 285 catalyst]> pd $r @ sub.printf_876
/ (fcn) sub.printf_876 129
|   sub.printf_876 ();
|           ; var int local_30h @ rbp-0x30
|           ; var int local_28h @ rbp-0x28
|           ; var int local_14h @ rbp-0x14
|              ; CALL XREF from 0x00400fb3 (loc.00400f5e)
|           0x00400876      55             push rbp
|           0x00400877      4889e5         mov rbp, rsp
|           0x0040087a      53             push rbx
|           0x0040087b      4883ec28       sub rsp, 0x28; '('
|           0x0040087f      48897dd8       mov qword [rbp - local_28h], rdi     #Something1 pushed onto stack
|           0x00400883      488975d0       mov qword [rbp - local_30h], rsi     #Something2 pushed onto stack
|           0x00400887      bf50104000     mov edi, str.your_flag_is:_ALEXCTF_ ; "your flag is: ALEXCTF{" @ 0x401050
|           0x0040088c      b800000000     mov eax, 0
|           0x00400891      e85afeffff     call sym.imp.printf         ;[1]; int printf(const char *format)
|           0x00400896      c745ec000000.  mov dword [rbp - local_14h], 0       #Counter initialized
|       ,=< 0x0040089d      eb2f           jmp 0x4008ce;[2]
|      .--> 0x0040089f      8b45ec         mov eax, dword [rbp - local_14h]
|      ||   0x004008a2      4898           cdqe
|      ||   0x004008a4      0fb680a02060.  movzx eax, byte [rax + 0x6020a0]     #load xor'd flag byte from memory
|      ||   0x004008ab      0fb6d0         movzx edx, al                        #and stick it in rdx
|      ||   0x004008ae      8b45ec         mov eax, dword [rbp - local_14h]
|      ||   0x004008b1      4863c8         movsxd rcx, eax
|      ||   0x004008b4      488b45d0       mov rax, qword [rbp - local_30h]     #Something2 loaded
|      ||   0x004008b8      4801c8         add rax, rcx; '&'                    #Counter offset into Something2
|      ||   0x004008bb      0fb600         movzx eax, byte [rax]                #Get that byte
|      ||   0x004008be      0fbec0         movsx eax, al                        #Really get it
|      ||   0x004008c1      31d0           xor eax, edx                         #xor that byte with something else
|      ||   0x004008c3      89c7           mov edi, eax                         #get the byte into edi to print it out
|      ||   0x004008c5      e8f6fdffff     call sym.imp.putchar        ;[3]; sym.imp.malloc-0x60;  void *malloc(size_t size)
|      ||   0x004008ca      8345ec01       add dword [rbp - local_14h], 1
|      !|      ; JMP XREF from 0x0040089d (sub.printf_876)
|      |`-> 0x004008ce      8b45ec         mov eax, dword [rbp - local_14h]
|      |    0x004008d1      4863d8         movsxd rbx, eax
|      |    0x004008d4      488b45d0       mov rax, qword [rbp - local_30h]
|      |    0x004008d8      4889c7         mov rdi, rax
|      |    0x004008db      e800feffff     call sym.imp.strlen         ;[4]; size_t strlen(const char *s)
|      |    0x004008e0      4839c3         cmp rbx, rax
|      `==< 0x004008e3      72ba           jb 0x40089f;[5]
|           0x004008e5      bf67104000     mov edi, 0x401067
|           0x004008ea      e8e1fdffff     call sym.imp.puts           ;[6]; int puts(const char *s)
|           0x004008ef      90             nop
|           0x004008f0      4883c428       add rsp, 0x28; '('
|           0x004008f4      5b             pop rbx
|           0x004008f5      5d             pop rbp
\           0x004008f6      c3             ret

We can see from the assembly of where the victory string is referenced that the flag is loaded from memory address 0x6020a0 (at instruction 0x004008a4) and un-xor'd one byte at a time from what was passed into the function via the rsi register. Refer to the highlighted lines above and my # added comments to see how that works. At this point I have a sneaking suspicion that the XOR key to decode the flag is some combination of the Username and Password that must be supplied to the program, rather than some hard-coded key.

But we can keep tracing a backward path through the program to determine if this is the case. Back(On)ward!

Back to the Future (Main)

Since I wanna know where this function is called, I can :seek to the first memory address (or use k to go up) until I'm at 0x400876, and use x to show where this function is XREF'd from, and <enter> to jump to where it's called. Once there, scrolling up reveals that we're in main (just below where we patched the NOPs for the second loading screen @ 0x00400f62):

[0x00400f5e 37% 285 catalyst]> pd $r @ loc.00400f5e
|- loc.00400f5e 97
|   loc.00400f5e ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_10h @ rbp-0x10
|           ; var int local_8h @ rbp-0x8
|              ; JMP XREF from 0x00400f24 (loc.00400ea5)
|           0x00400f5e      837df81d       cmp dword [rbp - local_8h], 0x1d ; [0x1d:4]=0x40000000
|           0x00400f62      90             nop
|           0x00400f63      90             nop
|           0x00400f64      bf0a000000     mov edi, 0xa
|           0x00400f69      e852f7ffff     call sym.imp.putchar        ;[1]; sym.imp.malloc-0x60;  void *malloc(size_t size)
|           0x00400f6e      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f72      4889c7         mov rdi, rax
|           0x00400f75      e820fdffff     call fcn.00400c9a           ;[2]
|           0x00400f7a      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f7e      4889c7         mov rdi, rax
|           0x00400f81      e857fdffff     call sub.puts_cdd           ;[3]; int puts(const char *s)
|           0x00400f86      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f8a      4889c7         mov rdi, rax
|           0x00400f8d      e865f9ffff     call sub.puts_8f7           ;[4]; int puts(const char *s)
|           0x00400f92      488b55e8       mov rdx, qword [rbp - local_18h]
|           0x00400f96      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f9a      4889d6         mov rsi, rdx
|           0x00400f9d      4889c7         mov rdi, rax
|           0x00400fa0      e8d2f9ffff     call sub.puts_977           ;[5]; int puts(const char *s)
|           0x00400fa5      488b55e8       mov rdx, qword [rbp - local_18h]
|           0x00400fa9      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400fad      4889d6         mov rsi, rdx
|           0x00400fb0      4889c7         mov rdi, rax
|           0x00400fb3      e8bef8ffff     call sub.printf_876         ;[6]; int printf(const char *format)
|           0x00400fb8      b800000000     mov eax, 0
|           0x00400fbd      c9             leave
\           0x00400fbe      c3             ret
            0x00400fbf      90             nop

Looking at the highlighted assembly above, we see that rsi is set by loading from rbp-0x18, and that this memory address as well as rbp-0x10 are used all throughout. Since I had a feeling that these two variables were going to be pointers to the user input strings (Username and Password), I figured it was time to fire up full-on debug mode.

Full-Frontal Debug

In order to have a controlled environment within which to run radare2 and have its STDIO separated from the debugged binary's STDIO, it's necessary to invoke the usage of rarun2. The steps to get this working are somewhat non-intuitive, however once it is configured, it is well worth the effort.

Rarun2 Config File

In my working directory I created a rarun2 configuration file:

-> % cat catalyst.rr2 
#!/usr/bin/env rarun2
program=catalyst
stdio=/dev/pts/2

This file's path is eventually passed as an argument to r2 with the -R flag. The highlighted line is especially important; it specifies which terminal window I'd like to redirect all STDIO to when debugging the program in radare.

Setting Up My Catalyst IO Terminal

To set up this terminal, I spawned a new terminal session and ran the tty command, which output /dev/pts/2. Then I ran clear; sleep 999999999999999999999; in this window to prepare its use for rarun2.

tobaljackson@binarystudio [06:34:15 PM] [~/AlexCTF/RE3]
-> % tty
/dev/pts/2
tobaljackson@binarystudio [06:38:02 PM] [~/AlexCTF/RE3]
-> % clear; sleep 999999999999999999999999999999999999;

Doing this allows rarun2 access to a "clean" tty for STDIO.

Starting the Debug Session

Next, in another terminal window (not the one that's sleeping), I simply ran r2 again, but specifying rarun2 as the debug target:

-> % r2 -d rarun2 -R ./catalyst.rr2 
Process with PID 32043 started...
= attach 32043 32043
bin.baddr 0x00400000
USING 400000
Assuming filepath /home/tobaljackson/AlexCTF/RE3/catalyst
asm.bits 64
 -- Too old to crash
[0x7f52047a4d70]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
TODO: esil-vm not initialized
[Cannot determine xref search boundariesr references (aar)
[x] Analyze len bytes of instructions for references (aar)
[Oops invalid rangen calls (aac)
[x] Analyze function calls (aac)
[ ] [*] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan))
= attach 32043 32043
[0x7f52047a4d70]> db main
[0x7f52047a4d70]> dc
Selecting and continuing: 32043
hit breakpoint at: 400d93
[0x00400d93]> 

The highlighted lines show the 3 first commands I entered: aaa which analyzes all functions and automatically names them, db main which sets a breakpoint at main, and dc which continues execution until it breaks at main. New users of radare will take note that all commands generally have subcommands which are mnemonics. To see a list of all commands (with descriptions) postfix any command with a ?.

All d- commands are debug commands, for instance.

Getting Visual

Up to this point I've presented some things with radare2's command line interface, and others through the Visual assembly (sans stack/registers) interface. However for debugging nothing beats its Visual mode. Simply enter the V command at the prompt to enter visual mode, and cycle through the display modes with p until you arrive at the debugging mode:

- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffee837a1b8  9162 4204 527f 0000 0000 0400 0000 0000  .bB.R...........
0x7ffee837a1c8  98a2 37e8 fe7f 0000 486c 5604 0100 0000  ..7.....HlV.....
0x7ffee837a1d8  930d 4000 0000 0000 0000 0000 0000 0000  ..@.............
0x7ffee837a1e8  5396 8348 c834 f1d8 8007 4000 0000 0000  S..H.4....@.....
 rax 0x00400d93           rbx 0x00000000           rcx 0x00000000
 rdx 0x7ffee837a2a8        r8 0x00401030            r9 0x7f52047b3900
 r10 0x00000008           r11 0x00000001           r12 0x00400780
 r13 0x7ffee837a290       r14 0x00000000           r15 0x00000000
 rsi 0x7ffee837a298       rdi 0x00000001           rsp 0x7ffee837a1b8
 rbp 0x00400fc0           rip 0x00400d93           rflags 1PZI
orax 0xffffffffffffffff
            ;-- rax:
            ;-- rip:
/ (fcn) main 341
|   main ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_10h @ rbp-0x10
|           ; var int local_4h @ rbp-0x4
|              ; DATA XREF from 0x0040079d (entry0)
|           0x00400d93 b    55             push rbp
|           0x00400d94      4889e5         mov rbp, rsp
|           0x00400d97      4883ec20       sub rsp, 0x20
|           0x00400d9b      bfe8030000     mov edi, 0x3e8              ; 1000
|           0x00400da0      e87bf9ffff     call sym.imp.malloc         ;[1];  void *malloc(size_t size)
|           0x00400da5      488945f0       mov qword [rbp - local_10h], rax
|           0x00400da9      bfe8030000     mov edi, 0x3e8              ; 1000
|           0x00400dae      e86df9ffff     call sym.imp.malloc         ;[1];  void *malloc(size_t size)
|           0x00400db3      488945e8       mov qword [rbp - local_18h], rax
|           0x00400db7      bf00000000     mov edi, 0
|           0x00400dbc      e84ff9ffff     call sym.imp.time           ;[2]; time_t time(time_t *timer)
|           0x00400dc1      89c7           mov edi, eax
|           0x00400dc3      e838f9ffff     call sym.imp.srand          ;[3]; void srand(int seed)
|           0x00400dc8      bf88104000     mov edi, 0x401088

From this mode, you can use vim navigation keys jk to move up and down the current instruction space. S and s allows stepping over and stepping into respectively, while : allows entering of any normal (commandline) radare2 command. c toggles cursor mode, while TAB shifts focus (in cursor mode) between stack, register, and code sections of the interface.

In the output above, the three highlighted lines indicate the boundaries of the three sections I just mentioned. Now that we're in visual mode, we can see what the program loads into the rbp-0x10 and rbp-0x18 qwords on the stack.

Using the j and k keys to look forward and backward at the program's assembly, I scanned down past the two loading loops which we've already NOPd out, until I was at the section we've seen before which calls several more functions (including the winning function sub.printf_876 @ 0x00400fb3).

- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7ffee837a1b8  9162 4204 527f 0000 0000 0400 0000 0000  .bB.R...........
0x7ffee837a1c8  98a2 37e8 fe7f 0000 486c 5604 0100 0000  ..7.....HlV.....
0x7ffee837a1d8  930d 4000 0000 0000 0000 0000 0000 0000  ..@.............
0x7ffee837a1e8  5396 8348 c834 f1d8 8007 4000 0000 0000  S..H.4....@.....
 rax 0x00400d93           rbx 0x00000000           rcx 0x00000000
 rdx 0x7ffee837a2a8        r8 0x00401030            r9 0x7f52047b3900
 r10 0x00000008           r11 0x00000001           r12 0x00400780
 r13 0x7ffee837a290       r14 0x00000000           r15 0x00000000
 rsi 0x7ffee837a298       rdi 0x00000001           rsp 0x7ffee837a1b8
 rbp 0x00400fc0           rip 0x00400d93           rflags 1PZI
orax 0xffffffffffffffff
|           0x00400f62      90             nop
|           0x00400f63      90             nop
|           0x00400f64      bf0a000000     mov edi, 0xa
|           0x00400f69      e852f7ffff     call sym.imp.putchar        ;[1]; sym.imp.malloc-0x60;  void *malloc(size_t size)
|           0x00400f6e      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f72      4889c7         mov rdi, rax
|           0x00400f75      e820fdffff     call fcn.00400c9a           ;[2]
|           0x00400f7a      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f7e      4889c7         mov rdi, rax
|           0x00400f81      e857fdffff     call sub.puts_cdd           ;[3]; int puts(const char *s)
|           0x00400f86      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f8a      4889c7         mov rdi, rax
|           0x00400f8d      e865f9ffff     call sub.puts_8f7           ;[4]; int puts(const char *s)
|           0x00400f92      488b55e8       mov rdx, qword [rbp - local_18h]
|           0x00400f96      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400f9a      4889d6         mov rsi, rdx
|           0x00400f9d      4889c7         mov rdi, rax
|           0x00400fa0      e8d2f9ffff     call sub.puts_977           ;[5]; int puts(const char *s)
|           0x00400fa5      488b55e8       mov rdx, qword [rbp - local_18h]
|           0x00400fa9      488b45f0       mov rax, qword [rbp - local_10h]
|           0x00400fad      4889d6         mov rsi, rdx
|           0x00400fb0      4889c7         mov rdi, rax
|           0x00400fb3      e8bef8ffff     call sub.printf_876         ;[6]; int printf(const char *format)
|           0x00400fb8      b800000000     mov eax, 0
|           0x00400fbd      c9             leave
\           0x00400fbe      c3             ret

Here you can see at the top of the assembly our second "Loading" NOPs that we overwrote the jle instruction with (at 0x00400f63). Also to note is that simply navigating the code view down hasn't advanced the program counter; our instruction pointer is still at the start of main, 0x400d93 shown above on the first highlighted line as rip.

A quick note about memory addresses: you may notice above that sometimes my Stack memory addresses (and potentially instruction addresses) are different than yours, and this has to do with how different operating systems map memory and whether or not something called ASLR (Address Space Layout Randomization) is turned on. If you'd like to turn off ASLR (which can simplify exploit development and debugging/reversing), simply execute the command echo 0 > /proc/sys/kernel/randomize_va_space as root and you'll disable ASLR for all future-spawned processes. Don't worry, the setting will revert upon reboot.

Since I've been dying to know what is put onto the stack that each of these functions appear to rely on, lets put a breakpoint at the first function at 0x00400f75. You can either enter cursor mode and navigate to the first byte of the call instruction and press b to set a breakpoint, or enter the command :bp 0x00400f75.

Another sidenote: the radare2 team has been working on some features which integrate the mouse pointer (for scrolling and some other potential things) a side effect is that clicking within the radare2 window can have some unintended consequences, such as changing settings or modifying data! To disable this, either enter the command e scr.wheel=false or (in visual mode) press e to enter the settings browser, find scr and change the scr.wheel setting to false with <enter>.

Last one: all configuration options set with e can be saved to ~/.radare2rc. Here is mine:

e stack.bytes = false
e scr.wheel = false
e stack.size = 114

Now that we have a breakpoint set here, we can simply :dc to continue execution to our breakpoint.

Detached Head

While it may look like radare2 has frozen, if you switch to the terminal prepared for the STDIO interaction, you should see our familiar catalyst prompt asking for a username and password. Go ahead and type a username and password when prompted, following each with an <enter> keystroke. Once you've entered the password, you'll see the radare2 window update and stop at the breakpoint we've set.

At this point, we can see that the rdi register is loaded with one of the values we were interested in before. To inspect what this value is, use a variation of the print command: ps @ rdi

:> ps @ rdi
HERPDERP

As you can see, the rdi register holds a pointer to our Username string. Since we want to know what the other address points to (the rbp-0x18 one) we can also inspect it with the ps command, however since rbp-0x18 is itself an address, we have to dereference it using the [] characters:

:> ps @ [rbp-0x18]
hurrdurr

Now armed with the knowledge of what was being put into the registers (and where from) I used the s command to step-into the call fcn.00400c9a function to see what it does:

- offset -       0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x7fffffffdf58  7a0f 4000 0000 0000 c00f 4000 0000 0000  z.@.......@.....
0x7fffffffdf68  0034 6000 0000 0000 1030 6000 0000 0000  .4`......0`.....
0x7fffffffdf78  0000 0000 0000 0000 c00f 4000 0000 0000  ..........@.....
0x7fffffffdf88  91c2 a5f7 ff7f 0000 0000 0400 0000 0000  ................
 rax 0x00603010           rbx 0x00000000           rcx 0x7ffff7b17530
 rdx 0x0000000a            r8 0x7ffff7dd6740        r9 0x7ffff7fba440
 r10 0x0000000a           r11 0x00000246           r12 0x00400780
 r13 0x7fffffffe060       r14 0x00000000           r15 0x00000000
 rsi 0x7ffff7dd6740       rdi 0x00603010           rsp 0x7fffffffdf58
 rbp 0x7fffffffdf80       rip 0x00400c9a           rflags 1PZI
orax 0xffffffffffffffff
            ;-- rip:
/ (fcn) fcn.00400c9a 67
|   fcn.00400c9a ();
|           ; var int local_18h @ rbp-0x18
|           ; var int local_4h @ rbp-0x4
|              ; CALL XREF from 0x00400f75 (loc.00400f5e)
|           0x00400c9a      55             push rbp
|           0x00400c9b      4889e5         mov rbp, rsp
|           0x00400c9e      4883ec20       sub rsp, 0x20
|           0x00400ca2      48897de8       mov qword [rbp - local_18h], rdi     #put Username pointer on stack
|           0x00400ca6      c745fc000000.  mov dword [rbp - local_4h], 0        #initialize counter
|       ,=< 0x00400cad      eb18           jmp 0x400cc7;[1]                     #unconditional jump
|       |      ; JMP XREF from 0x00400ccb (fcn.00400c9a)
|      .--> 0x00400caf      8b45fc         mov eax, dword [rbp - local_4h]      
|      ||   0x00400cb2      4863d0         movsxd rdx, eax                      #put counter in rdx
|      ||   0x00400cb5      488b45e8       mov rax, qword [rbp - local_18h]     #get username addr
|      ||   0x00400cb9      4801d0         add rax, rdx                         #offset [counter] bytes into it
|      ||   0x00400cbc      0fb600         movzx eax, byte [rax]                #get that character
|      ||   0x00400cbf      84c0           test al, al                          #see if it's null (\x00)
|     ,===< 0x00400cc1      740c           je 0x400ccf;[2]                      #if it is, we have STRLEN
|     |||   0x00400cc3      8345fc01       add dword [rbp - local_4h], 1        #else, count+=1 and try again
|     |!|      ; JMP XREF from 0x00400cad (fcn.00400c9a)
|     ||`-> 0x00400cc7      837dfc31       cmp dword [rbp - local_4h], 0x31     #max username length == 0x31
|     |`==< 0x00400ccb      7ee2           jle 0x400caf;[3]
|     | ,=< 0x00400ccd      eb01           jmp 0x400cd0;[4]
|     | |      ; JMP XREF from 0x00400cc1 (fcn.00400c9a)
|     `---> 0x00400ccf      90             nop
|       |      ; JMP XREF from 0x00400ccd (fcn.00400c9a)
|       `-> 0x00400cd0      8b45fc         mov eax, dword [rbp - local_4h]      #put STRLEN in rax
|           0x00400cd3      89c7           mov edi, eax                         #and in edi
|           0x00400cd5      e867ffffff     call sub.puts_c41                    #now pass STRLEN to sub-function
|           0x00400cda      90             nop
|           0x00400cdb      c9             leave
\           0x00400cdc      c3             ret

This function is counting the string length of the Username, up to a maximum of 0x31 characters. It does this by initializing a loop counter to 0 (at 0x400ca6) on the stack, as well as placing the username string pointer on the stack (at 0x400ca2). After this occurs, an unconditional jump takes us to 0x400cc7, which checks the loop counter against the value 0x31.

Since the loop counter starts at 0, the jle (jump if less than or equal to) instruction takes rip back up to 0x400caf, which loads the loop counter value into rax (which ends up in rdx) and then doubly-serves as an index into the string at 0x400cb9. Adding the loop counter to the string address allows the instruction at 0x400cbc to load the indexed character of the string into the rax register and use test al,al on it.

A Side Note About Registers: in the three highlighted lines above, rax, eax, and al each refer to the same register, albeit different parts of it. Here is a simple ascii graphic representing the different "parts" of the x86_64 rax register (taken from this stackoverflow answer):

0x1122334455667788
  ================ rax (64 bits)
          ======== eax (32 bits)
              ====  ax (16 bits)
              ==    ah (8 bits)
                ==  al (8 bits)
The same "slicing" scheme is used for the other general-purpose registers, rbx, rcx, rdx

The test instruction will set the ZF (zero flag) of the processor if the value being tested is zero, and the following je (jump if equal) instruction at 0x400cc1 is also referred to as jz or jump if zero; it checks if the zero flag of the processor is set, and if so, follows the jump and unsets the flag.

Despite whether the above explanation for the assembly still seems fuzzy or not (if you're new to RE), I would highly recommend using the s command to single-step through this loop, watching the value of the stack and registers change as you do so. At any point in time (like after the instruction at 0x400cbc) you can use the ps @ rax command to inspect the value of the rax register and see the username characters being loaded and checked. It's rather cool to see and even more rewarding to understand!

Username Validation

After determining that this function was just a custom strlen(), I set a breakpoint on the call sub.puts_c41 with :bp 0x400cd5, and used :dc to continue to it. stepping into the function yielded the following:

|           0x00400c41      55             push rbp
|           0x00400c42      4889e5         mov rbp, rsp
|           0x00400c45      4883ec20       sub rsp, 0x20
|           0x00400c49      897dec         mov dword [rbp - local_14h], edi #put strlen on stack
|           0x00400c4c      8b45ec         mov eax, dword [rbp - local_14h] #put strlen in eax
|           0x00400c4f      c1f802         sar eax, 2                       #divide it by 4
|           0x00400c52      8945fc         mov dword [rbp - local_4h], eax  #store result on stack
|           0x00400c55      8b45fc         mov eax, dword [rbp - local_4h]  
|           0x00400c58      c1e002         shl eax, 2                       #multiply it by 4
|           0x00400c5b      3b45ec         cmp eax, dword [rbp - local_14h] #see if number was evenly divisible by 4 
|       ,=< 0x00400c5e      7523           jne 0x400c83;[1]                 #jump to fail if not
|       |   0x00400c60      8b45fc         mov eax, dword [rbp - local_4h]  #load previously divided value
|       |   0x00400c63      c1f802         sar eax, 2                       #div by 4 again
|       |   0x00400c66      8945f8         mov dword [rbp - local_8h], eax  #store on stack
|       |   0x00400c69      8b45f8         mov eax, dword [rbp - local_8h]  
|       |   0x00400c6c      c1e002         shl eax, 2                       #multiply by 4
|       |   0x00400c6f      3b45fc         cmp eax, dword [rbp - local_4h]  #check if equal
|      ,==< 0x00400c72      740f           je 0x400c83;[1]                  #jump to fail if they are
|      ||   0x00400c74      8b45fc         mov eax, dword [rbp - local_4h]  #load previously divided value
|      ||   0x00400c77      d1f8           sar eax, 1                       #shift right by 1
|      ||   0x00400c79      85c0           test eax, eax                    #check if its zero
|     ,===< 0x00400c7b      7406           je 0x400c83;[1]                  #fail if it is zero
|     |||   0x00400c7d      837df800       cmp dword [rbp - local_8h], 0
|    ,====< 0x00400c81      7414           je 0x400c97;[2]
|    |```-> 0x00400c83      bf69104000     mov edi, str.invalid_username_or_password ; "invalid username or password" @ 0x401069
|    |      0x00400c88      e843faffff     call sym.imp.puts           
|    |      0x00400c8d      bf00000000     mov edi, 0
|    |      0x00400c92      e8b9faffff     call sym.imp.exit           
|    `----> 0x00400c97      90             nop
|           0x00400c98      c9             leave
\           0x00400c99      c3             ret

THIS POST STILL UNDER CONSTRUCTION

The All-In-One Solution

Here is the python script which solves this challenge. Required dependencies are python2 and the angr package.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env python2

import os
import stat
import sys
import angr
import claripy
import logging
from binascii import hexlify
from subprocess import Popen, PIPE

def analyzePaths(start, target, avoid, length, user=None):
    print("Let's do this; beginning analysis")
    fname = 'catalyst2'

    print("Loading the binary")
    b = angr.Project(fname)

    print("Reticulating Splines")
    length += 1

    #setting the addresses that will guide angr
    START = start
    TARGET = target
    AVOID = avoid

    print("\tStart: {:#x}\n\tWant: {:#x}\n\tAvoid: {}".format(START, TARGET, ', '.join([hex(x) for x in AVOID])))

    #create a blank angr state
    state = b.factory.blank_state(addr=START)

    if (user == None):
        #declare symbolic user object, 12 characters in length
        username = claripy.BVS('username', 0x60)

        #constrain what the username bytes can be (printable and non-null '\x00' and non-space)
        for byte in username.chop(8):
            state.add_constraints(byte != '\x00')
            state.add_constraints(byte > ' ')
            state.add_constraints(byte <= '~')
    else:
        print("\tThis might take a while...")

        #set a concrete value using supplied username
        username = claripy.BVV(int(hexlify(user),16), 0x60)

        #this time password is symbolic
        password = claripy.BVS('password', length * 8)

        for bits in range(0, len(password.chop(8))):
            #unlike username, password needs to have it's last byte set to null
            byte = password.chop(8)[bits]
            if (bits == len(password.chop(8)) - 1):
                state.add_constraints(byte = '\x00')

            else:
                state.add_constraints(byte != '\x00')
                state.add_constraints(byte >= ' ')
                state.add_constraints(byte <= '~')

        #store password in memory
        state.memory.store(0x00603400, password)

        #set rsi to password
        state.regs.rsi = 0x00603400

    #store username in memory
    state.memory.store(0x00603010, username)

    #set rdi to username
    state.regs.rdi = 0x00603010

    #initialize path
    path = b.factory.path(state)

    #set up path group
    pg = b.factory.path_group(path, threads=32)

    #Start Exploring!
    ex = pg.explore(find=TARGET, avoid=AVOID)

    print("FOUND A THING!!\n")
    if (user == None):
        #retrieve username from memory
        userStr = ex.found[0].state.se.any_str(username)
        print("username: {}".format(userStr))
        return userStr

    else:
        #retrieve password from memory
        passwordStr = ex.found[0].state.se.any_str(password)
        print("password: {}".format(passwordStr))
        return passwordStr

def patch_binary():
    print("Creating patched binary...")

    #open catalyst
    f = open("catalyst", 'rb')
    fo = list(f.read())
    f.close()
    print("\tskipping loading routines...")

    #write nops over loading loop jumps
    #first: 0x00400ea9
    fo[0xea9] = '\x90'
    fo[0xeaa] = '\x90'

    #second: 0x00400f62
    fo[0xf62] = '\x90'
    fo[0xf63] = '\x90'

    #nop password explosion check
    #seek to 0x40099f
    #write 'jmp 0x400a4c'
    print("\tskipping branch-exploder...")
    fo[0x99f] = '\xe9'
    fo[0x9a0] = '\xa8'

    #write patched binary
    print("\twriting patched binary 'catalyst2'")
    f = open("catalyst2", 'wb')
    f.write(''.join(fo))
    f.close()

    #make it executable
    print("making it executable")
    st = os.stat("catalyst2")
    os.chmod("catalyst2", st.st_mode | stat.S_IEXEC)

if __name__ == "__main__":

    #we need to make the binary usable, removing long sleep()s
    patch_binary()

    #username length of 12 discovered through science
    user = analyzePaths(0x400cdd, 0x400d92, (0x400d7c,), 12)

    #password length of 40 discovered through guessing ;)
    pw = analyzePaths(0x00400977, 0x00400c40, (0x400a1c, 0x400a87, 0x400ab5, 0x400ae3, 0x400b11, 0x400b3f, 0x400b6d, 0x400b9b, 
        0x400bc9, 0x400bf7, 0x400c25), 40, user)

    #after discovering username/pass, feed them into the program
    p = Popen(['./catalyst2'], stdout=PIPE, stdin=PIPE, stderr=PIPE)

    #GET THE FLAG
    flag = p.communicate(input='{}\n{}\n'.format(user, pw))

    #PROFIT
    print(flag[0])

THIS POST STILL UNDER CONSTRUCTION