[RITSEC CTF 21] Un-machined Aerial System

we have an elf file. By disassembling it we have that:

.text:18C2     lea     rdi, format     ; "Fill in the rest of the flag: RS{"
.text:18C9     mov     eax, 0
.text:18CE     call    _printf
.text:18D3     mov     rdx, cs:stdin   ; stream
.text:18DA     lea     rax, [rbp+s]
.text:18DE     mov     esi, 14h        ; n
.text:18E3     mov     rdi, rax        ; s
.text:18E6     call    _fgets
.text:18EB     lea     rax, [rbp+s]
.text:18EF     lea     rsi, reject     ; "\n"
.text:18F6     mov     rdi, rax        ; s
.text:18F9     call    _strcspn
.text:18FE     mov     [rbp+rax+s], 0
.text:1903     lea     rax, [rbp+s]
.text:1907     mov     rsi, rax
.text:190A     lea     rdi, aTheInputtedFla ; "The inputted flag was RS{..}"
.text:1911     mov     eax, 0
.text:1916     call    _printf

we can see that at 0x18e6 the program take as input 0x14 bytes. The bytes that we give as input represent the flag and after we have:

.text:199A     lea     rdi, aYayYouGotTheFl ; "YAY, you got the flag!"
.text:19A1     call    _puts
.text:19A6     jmp     short loc_19B4
.text:19A8 ; ------------------------------------------------------------
.text:19A8
.text:19A8  loc_19A8:                  ; CODE XREF: main+14D↑j
.text:19A8     lea     rdi, aSorryThatSNotT ;"Sorry, that's not the flag..."
.text:19AF     call    _puts

In this month I have been studing angr and in this challenge, and whit angr this challenge seems very easy.

First I need to declare the input value that I give to the binary program as BVS

input_len = 20

flag_chars = [claripy.BVS(f'flag_{i}',8) for i in range(input_len)]
flag = claripy.Concat( *flag_chars + [claripy.BVV(b'\n')] )

and then I add some costraint to this BVSs for lock the possible value as printable character.

for c in flag_chars:
        st.solver.add(c < 0x7f )
        st.solver.add(c > 0x20 )

st was declared here. sm is a Simstate. In angr doc site:

A SimState contains a program’s memory, registers, filesystem data… any “live data” that can be changed by execution has a home in the state.

p = angr.Project('./hard')

# here I define the flag_chars and the flag variables

st = p.factory.full_init_state(
         args=['./hard'],
         add_options=angr.options.unicorn,
         stdin=flag
     )

then I create a simulation sm and running it I stored all the deadend with 'YAY' in the stdout x.posix.dumps(1) inside the ded array:

sm = p.factory.simulation_manager(st)
sm.run()

ded = []
for x in sm.deadended:
        if b'YAY' in x.posix.dumps(1):
                ded.append(x)

valid = ded[0].posix.dumps(0) # dump of the stdin

This is the full angr script:

#!/usr/bin/env python

import angr 
import claripy
import time


def main():

    input_len = 20

    p = angr.Project('./hard')

    flag_chars = [claripy.BVS(f'flag_{i}',8) for i in range(input_len)]
    flag = claripy.Concat( *flag_chars + [claripy.BVV(b'\n')] )

    st = p.factory.full_init_state(
                args=['./hard'],
                add_options=angr.options.unicorn,
                stdin=flag
            )

    for c in flag_chars:
        st.solver.add(c < 0x7f )
        st.solver.add(c > 0x20 )

    sm = p.factory.simulation_manager(st)
    sm.run()

    ded = []
    for x in sm.deadended:
        if b'YAY' in x.posix.dumps(1):
            ded.append(x)

    valid_in  = ded[0].posix.dumps(0)
    valid_out = ded[0].posix.dumps(1)
    print(b'INPUT: '+valid_in)
    print(b'OUTPUT: '+valid_out)
    return


if __name__ == "__main__":
    before = time.time()
    main()
    after = time.time()
    print("Time elapsed: {}".format(after - before))

and this is the output of this script.


┌──(.angr)(eurus㉿warfare)-[~/Documents/ritsec-500]
└─$ ./solver   
WARNING | 2021-04-12 13:25:38,025 | cle.loader | The main binary is a position-independent execuable. It is being loaded with a base address of 0x400000.
WARNING | 2021-04-12 13:25:38,852 | angr.simos.simos | stdin is constrained to 21 bytes (has_end=True). If you are only providing the first 21 bytes instead of the entire stdin, please use stdin=SimFileStream(name='stdin', content=your_first_n_bytes, has_end=False).                                                                        
WARNING | 2021-04-12 13:26:16,011 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.                                                                                                                                              
WARNING | 2021-04-12 13:26:16,011 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:                                                                                                                                          
WARNING | 2021-04-12 13:26:16,011 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2021-04-12 13:26:16,011 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null                                                                                                                                                    
WARNING | 2021-04-12 13:26:16,011 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.                                                                                                                                                        
WARNING | 2021-04-12 13:26:16,011 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefe6e with 1 unconstrained bytes referenced from 0x40126b (PLT.perror+0x19b in hard (0x126b))                                                                                                                                 
b'INPUT: B4bys_1st_VMPr0tect?\n'
b'OUTPUT: Fill in the rest of the flag: RS{The inputted flag was RS{B4bys_1st_VMPr0tect}\n\nYAY, you got the flag!\n'
Time elapsed: 53.326435565948486