Difference between revisions of "Hack.lu Writeups"

From YobiWiki
Jump to navigation Jump to search
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
  +
* [[Hack.lu 2012 Writeups]]
==2012 CTF by Fluxfingers==
 
  +
* [[Hack.lu 2015 Writeups]]
It was again a great moment of fun to participate to this year's CTF organised by [http://www.fluxfingers.net/ Fluxfingers] @ Hack.lu 2012
 
  +
* [[Hack.lu 2017 Writeups]]
===2012 T-Shirt contest===
 
This one is quite apart from the other challenges for several reasons:
 
* The challenge was actually printed on participants' t-shirts, as of last year
 
* Surprisingly enough, only <strike>2</strike> 3 teams over the 290 who scored at least one challenge managed to solve it!! (none of the top 10, they were teams #50, <strike>#62</strike> #57 and #83)
 
* The challenge was the only one not designed by Fluxfingers team but... by myself, with the help of Alex to design the actual T-Shirt
 
Still I played the ctf fairly, proof is that even my team (woyouyizhixiaomaol=我有一只小毛驴) didn't find the solution ;-)
 
<br>I must admit, the challenge was easier for local people than for remote people, reason is that a list of words was written on the T-Shirt while online a patchwork of unrelated images was visible.
 
<br>On first evening, as no one solved it yet, we added an extra hint in the challenge description.
 
<br>On last morning, as no remote team managed to solve it, we also published a picture of the T-Shirt for remote teams. Apparently it didn't help...
 
<br>Here is it:
 
<source lang=text>
 
28 - T-shirt
 
Same as local T-shirt challenge: https://ctf.fluxfingers.net/challenges/noclue.png
 
Send your answers to (masked email on this wiki as it's too late and we hate spammers)
 
Hint: "It was in use sometime ago" #ctf #infosec
 
Hint: https://ctf.fluxfingers.net/challenges/IMG_4924.JPG
 
</source>
 
Here is noclue.png:
 
<br><br>[[Image:Hacklu2012 Noclue.png]]
 
<br><br>
 
Each element represents one word.
 
<br>Maybe you don't guess the exact right word for each of them, maybe not exactly in the right order, but it doesn't really matter as with a few words, it's enough to find the trick as we will see later.
 
<br>So if we do the exercice we get something like:
 
<br>'''RJ45? / unicode / crypta / assyria? / creatrix / ochra / chimaera / cumulate eucrite / jana antepono / condoleo / remissus / Πελασγοί=pelasgoi / articulo / cimex lectularius'''
 
<br>First two seems to refer to known technologies while all the rest seems mostly latin words
 
<br><br>Second image is the T-Shirt itself and there you get the exact word list:
 
<br><br>[[Image:Hacklu2012 IMG 4924.png]]
 
<br><br>
 
So actual word list is:
 
<br>'''utp cable unicode crypta assyrius creatrix ochra chimaera cumulate antepono condoleo remissus pelasga articulo cimex'''
 
<br><br>I heard many teams trying really many things in all possible directions, sometimes wrong ones but sometimes right:
 
* crypta => cryptography?? and people tried many different things but it's wrong: what you have is not a ciphertext as it's somehow "'''meaningful'''", not garbage as ciphertexts usually are, and moreover the first hint says "'''It was in use sometime ago'''". Which '''really''' means people used this kind of message in the past, it was not some pseudo-crypto I took of my hat.
 
* many saw that '''first three words''' are somehow separated from the rest, kind of header in front of a latin message
 
* someone interpreted it as those words are '''probably there to explain how to decode the latin message'''. And this is perfectly correct!
 
* but we are geeks and when we see utp cable unicode, our brain is too happy to know what they mean today while it seems hard to interpret! That's because thinking of the UTP you know and the Unicode you know is plain wrong :-) Remember the hint said it was in use '''sometime ago'''... not today and long ago they didn't know about Ethernet or UTF-8
 
* still in the funny interpretations, if you type the latin part in Google translate you get: <br>Assyrian vault creative ochra chimera cumulatively prefer empathize ''newly released article bug''<br>and they went googling for newly released vulnerability disclosures :-)
 
* several teams asked me if it could be related to '''telegraph''' and '''Morse code''': could be...
 
 
So to resume the best ideas heard so far and the hint, it's about a real system used in the past to transmit messages, maybe to do with telegraph and Morse code...
 
<br>I don't know for you but when I don't know stuffs I usually ask Google...
 
<br>Now it all depends which words you google for but soon or later you will see an interesting hit:
 
<br><br><br>
 
Googling for '''crypta assyrius creatrix''': first result title is '''Full text of ""Unicode".: The Universal Telegraphic Phrase-book'''
 
<br>Hey! we get something about '''UNICODE'''??? '''U'''niversal '''T'''elegraphic '''P'''hrase-book??? (UTP)
 
<br>Even for people having thought of telegraph it works with a completely different query, not on the message itself:
 
<br>
 
Googling for '''unicode telegraph''': third result title is '''Full text of ""Unicode".: The Universal Telegraphic Phrase-book'''
 
<br>With this link you can see it's actually using a very old phrasebook.
 
<br>See? Not the Unicode your brain is wired to, Not the UTP cable it's wired to, but some telegraphic cable :-)
 
<br>That page is not that easy to use as such because it's kind of pdftotext output and you miss the layout.
 
<br>Click on "See other format" or google again for the book title and you can find at least two versions in pdf, much more confortable to use.
 
<br>E.g. check [https://archive.org/stream/unicodeuniversa00unkngoog#page/n3/mode/2up this one online]
 
<br>It's presented as '''''a code of cypher words for commercial, domestic and familial phrases in ordinary use in inland and foreign telegrams'''''
 
<br>This edition was published in '''1889'''!
 
<br>Actually it's worth reading the introduction if you're curious
 
<br>Why such phrasebook? Because you had to pay per word so sending "''why have you not acknowledged receipt of letter?''" costed 8 times more than sending "''acapnon''"
 
<br>Even ''unicode'' is itself in the phrasebook list, intended to be used as header of the message, with some geeky self-referencing style: ''to decipher this message refer to the UNICODE''
 
<br>
 
<br>And so for the message we had we get:
 
<source lang=text>
 
unicode to decipher this message refer to the UNICODE
 
crypta have been expecting to hear from you
 
assyrius following is strictly confidential
 
creatrix exception cannot be made
 
ochra parcel is waiting remittance
 
chimaera delivery can be made at once
 
cumulate will bear the expense
 
antepono bring home with you...
 
condoleo dispose of it as you please
 
remissus can you obtain good security
 
pelasga be as quick as possible
 
articulo come as soon as you can
 
cimex describe exactly what you want
 
</source>
 
Teams had to submit the decoded message you see in the right column (or slight variations of it if you started from the picture and didn't get the exact words or exact order) to the given email addresses to get the points.
 
<br>Congrats to ChaosMonkeys, Lesboverflow and C3L, the teams who solved this challenge!
 
<br>If you tried and failed, I hope you still had fun and didn't end up totally frustrated ;-)
 
<br><br>Any comments? [[User:PhilippeTeuwen|Contact me]]
 
===2012 zlotpy===
 
<source lang=text>
 
10 - zlotpy
 
Gambling time. Play against the Internet Zlot Machine at ctf.fluxfingers.net tcp/2053
 
This challenge has two stages.
 
1) Medium: Investigate the contents of a saved game.
 
2) Hard: Get 8 (EIGHT) bonus points.
 
Good luck!
 
Hint: We have some sourcecode for you! https://ctf.fluxfingers.net/challenges/zlot.py
 
credits: 500 +3 (1st), +2 (2nd), +1 (3rd)
 
</source>
 
Let's investigate it:
 
<source lang=text>
 
$ nc ctf.fluxfingers.net 2053
 
Welcome to the Internet ZlotMachine. Enter 'T' for the Tutorial.
 
Your current balance is 5 credits and 1 bonus
 
 
T
 
The Internet ZlotMachine works like a traditional slot machine.
 
To get you hooked, we will give you 5 (FIVE) credits every time you visit us.
 
 
Once you have connected, there are several options:
 
[...]
 
6) Save game (Command: S)
 
This allows you to save the current game. You will get a string back
 
that you are supposed to write down somewhere. Using this string later
 
will allow you to resume your game, when you come back.
 
We use our SAFEJSON *hint hint*
 
 
7) Load game (Command: L<SAVESTRING>)
 
Allows you to reload a previously saved game.
 
[...]
 
 
S
 
Your games has been saved! Please write down the following save game code.
 
3SA7TH/9a6E4vgtY0MAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
This game may later be loaded with L<SAVEGAMECODE>
 
 
L 3SA7TH/9a6E4vgtY0MAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
Restored state.
 
Your current balance is 5 credits and 1 bonus
 
</source>
 
Games are saved in a base64 string with some SAFEJSON.
 
<br>Inspection of the source code reveals immediately that it's actually a JSON string containing at least "bonus" and "credits" values, encrypted with AES-CBC and encoded in base64.
 
<br>Probably something like {"credits": 5, "bonus":1}
 
<br>Here is the decoded ciphertext:
 
dd203b4c7ffd6ba138be0b58d0c02eb8
 
c0c229df27bcab88e7f14cc472f4cecc
 
66cdaf066ae760d0d99ef4af7b891722
 
0bb2ed15bac2e5ee68f223ffd4b04fe6
 
fc10705ee797d3b3ac0b685b2db77bf0
 
So 5 blocks of 128 bits.
 
<br>Here it's important to understand the CBC mode, you can check it on [https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 Wikipedia] and especially the decoding: the first block is actually the random IV, to be XORed with the decryption of the next block, actually the first block of ciphertext.
 
<br>So by flipping bits in the IV, we can flip the corresponding bits in the first block of plaintext.
 
<br>This trick can only be made on the first block.
 
<br>Let's hope the value of "bonus" is indeed in that first block.
 
<br>Bonus in the saved game is 1 and we need 8.
 
<br>'1'->'8' == 0x31 -> 0x38 == 0b00110001 -> 0b00111000 => we need to XOR that bonus byte with 0b00001001 == 0x9
 
<br>But we don't know in which byte is located that value.
 
<br>The earliest position it could get is with a JSON string starting with {"bonus":1 so the 10th byte.
 
<br>The furthest we can try is on byte 16, after that we're not in the IV anymore.
 
<br>Let's script it:
 
<source lang=python>
 
#!/usr/bin/env python
 
from base64 import b64decode, b64encode
 
m=b64decode("3SA7TH/9a6E4vgtY0MAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=")
 
for i in range(10,17):
 
print "L", b64encode(m[:i] + chr(ord(m[i])^0x9) + m[i+1:])
 
</source>
 
<source lang=bash>
 
$ ./break-zlot.py
 
L 3SA7TH/9a6E4vgJY0MAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
L 3SA7TH/9a6E4vgtR0MAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
L 3SA7TH/9a6E4vgtY2cAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
L 3SA7TH/9a6E4vgtY0MkuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
L 3SA7TH/9a6E4vgtY0MAnuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
L 3SA7TH/9a6E4vgtY0MAuscDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
L 3SA7TH/9a6E4vgtY0MAuuMnCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
</source>
 
Let's try them:
 
<source lang=text>
 
$ nc ctf.fluxfingers.net 2053
 
Welcome to the Internet ZlotMachine. Enter 'T' for the Tutorial.
 
Your current balance is 5 credits and 1 bonus
 
L 3SA7TH/9a6E4vgJY0MAuuMDCKd8nvKuI5/FMxHL0zsxmza8Gaudg0Nme9K97iRciC7LtFbrC5e5o8iP/1LBP5vwQcF7nl9OzrAtoWy23e/A=
 
Restored state.
 
Your current balance is 5 credits and 8 bonus
 
Nice one. Here's your flag: 9eef8f17d07c4f11febcac1052469ab9
 
</source>
 
Well it seems we succeeded with one single attempt :-)
 
<br><br>Any comments? [[User:PhilippeTeuwen|Contact me]]
 
==2015==
 
===2015 Perl golf===
 
Goal: Write a Perl program that takes a parameter as input and outputs a filtered version with alternating upper/lower case letters of the (english) alphabet. Non-letter characters have to be printed but otherwise ignored.
 
<br>Example:
 
Input Hello World! Hallo Welt!
 
Output HeLlO wOrLd! HaLlO wElT!
 
Rules:
 
* You have 1 second.
 
* You have 45 (ASCII) chars.
 
* Do not flood the server.
 
 
The first thing is to find how the server will use your script.
 
<br>Input isn't read from stdin but is expected as the first argument, so you can try locally with
 
perl -e 'print @ARGV[0]' 'Hello World! Hallo Welt!'
 
Hello World! Hallo Welt!
 
Then come with a strategy to convert letters as asked and squeeze it.
 
<br>Here is our solution: (37 chars)
 
<pre>
 
$_=pop;s/\pL.*?(\pL|$)/\u\L$&/g;print
 
</pre>
 
But here is actually how Fluxfingers designed this challenge: they tried internally, kept the best one-liner they could come with, which was 38 chars long, and extended the limit to 45 to give some room to the participants.
 
<br>The best solution Fluxfingers had in mind: (38 chars)
 
<pre>
 
$_=pop;s/\pL/uc$&^$"^($x^=$")/ge/print
 
</pre>
 
So we were one char shorter \o/
 
 
A little word of explanation of our solution:
 
<pre>
 
$_=pop;s/\pL.*?(\pL|$)/\u\L$&/g;print
 
$_= assign to the Perl "current var", so we don't have to mention it later
 
pop; we need @ARGV[0], pop is shorter
 
s/ / /g; search and replace, g=repeat
 
\pL.* \pL We want one letter followed optionally by some stuff followed by one letter
 
\w captures too much (also [0-9_]) so we rely on that Unicode magic \pL
 
So first captured letter needs to be in uppercase and second captured letter needs to be lowercase
 
.*? Problem with .* is that it captures too much, including letters.
 
One could write [^a-zA-Z]* but that's not golfing anymore
 
Adding the "?" reduces the "greediness" of the expression to capture as few chars as possible
 
(\pL|$) An issue wit \pL only is that it fails if there is an odd number of letters in the string
 
So here we say one letter, or end of string, to capture that corner case.
 
\u\L$& $& is what was captured by the regex, \u means first char uppercase, \L means lowercase for the remaining letters
 
print Should I explain?
 
</pre>
 
 
After the CTF, we got it even shorter, now 36 chars!:
 
<pre>
 
$_=uc pop;s/\pL.*?\pL/\u\L$&/g;print
 
uc Set whole input as uppercase
 
\pL No more dirty (\pL|$) trick here
 
</pre>
 
The trick is to avoid the corner case of odd number of letters by setting the whole input uppercase first
 
 
Alternative solution of 36 chars, optimizing what was discussed [https://github.com/teamavidya/ctf/commit/52f5789e743af94c147d4e5e0fd3796bc162872b#commitcomment-13953040 here]:
 
<pre>
 
$_=lc pop;s/\pL/$&^($u^=$")/ge;print
 
$_= assign to the Perl "current var", so we don't have to mention it later
 
lc Set whole input as lowercase
 
pop; we need @ARGV[0], pop is shorter
 
s/ / /ge search and replace, g=repeat, e=evaluate again the result
 
\pL We want one letter: \w captures too much (also [0-9_]) so we rely on that Unicode magic \pL
 
 
$& $& is what was captured by the regex
 
^ $" xor it with $" which evaluates to " ". The effect is that a letter XORed by 0x20 toggles its case.
 
^($u^=$") Actually that's to be done only half of the time so we xor with a variable that is constantly xored with " "
 
so $u value is successively 0x20, 0x00, 0x20, 0x00 etc
 
print Should I explain?
 
</pre>
 
 
'''UPDATE''': [https://github.com/teamavidya/ctf/commit/52f5789e743af94c147d4e5e0fd3796bc162872b#commitcomment-13964794 someone] managed to get down to 34 chars!:
 
<pre>
 
print pop=~s/\pL/lc$&^($u^=$")/ger
 
r perform non-destructive substitution and return the new value
 
</pre>
 
 
Actually we can go even further for some of those solutions if we tolerate that the script doesn't always work.
 
<br>One can simply reload multiple times the script till the server provides a compatible input to get the flag.
 
<br>E.g. 29 chars and 28 chars:
 
<pre>
 
$_=pop;s/\w.*?\w/\u$&/g;print
 
print pop=~s/\w.*?\w/\u$&/gr
 
</pre>
 
worked after a few reloads on the sentence "body. want Law the help of and, being. a, graduates"
 
 
===2015 PHP Golf===
 
Same as for Perl Golf, but now we've 62 chars max.
 
<br>The lazy solution: (57 chars)
 
<pre>
 
<?=`echo '$argv[1]'|perl -pe 's/\pL.*?(\pL|$)/\u\L$&/g'`;
 
</pre>
 
 
Apparently Fluxfingers solution was:
 
<pre>
 
<?=preg_filter("/\pL/e",'($0|" ")^chr($m^=32)',$argv[1]);
 
</pre>
 
==2017==
 
===2017 Not My Digest===
 
cf https://flatearth.fluxfingers.net/challenges/15 and https://notmydigest.flatearth.fluxfingers.net/
 
 
A webpage allows to sign and verify messages.
 
<br>[https://notmydigest.flatearth.fluxfingers.net/?s2 index.php] and [https://notmydigest.flatearth.fluxfingers.net/?s1 md5.php] are made available.
 
 
Signature:
 
<source lang=php>
 
// $secret = 13 chars a-z0-9
 
function sign($str, $secret) {
 
if(preg_match("/[a-zA-Z]/", $str)) { die( "Alpha characters are evil, thus you are not allowed to sign them!" ); }
 
return htmlspecialchars($str.MD5_custom($str.$secret));
 
}
 
</source>
 
So a signed message is <code>my_message|MD5_custom(my_message|secret)</code> but <code>my_message</code> can't contain alphabetic chars (accentuated chars still work).
 
 
Verification:
 
<source lang=php>
 
function verify($str, $secret)
 
{
 
if(strlen($str) < 32) { return false; }
 
$md5 = substr($str, -32);
 
$content = substr($str, 0, -32);
 
if($md5 === MD5_custom($content.$secret)) { return true; }
 
}
 
[...]
 
//main
 
if($valid) {
 
$result = "success!";
 
$str = substr($_POST["postdata"], 0, -32);
 
if($str === "the earth is not flat!") {
 
$result = "LIAR! GO AWAY, REPTILIAN AND DON'T COME BACK: " . $flag;
 
}
 
} else {
 
$result = "fail!";
 
}
 
</source>
 
So it expects a signed message with at least a hash appended, and it's computing again the custom MD5 and comparing it.
 
<br>It's funny to note that when the hash is incorrect, the function returns nothing...
 
<br>if the signature is correct and if the message is exactly <code>the earth is not flat!</code>, then the flag will be displayed. Note the usage of <code>===</code> so we can't abuse it.
 
 
So the goal is to craft a signature on a given message which contains obviously chars that will be rejected by the <code>sign</code> function.
 
 
Let's look a bit closer to the custom MD5.
 
<br>The obvious customization is highlighted at the beginning:
 
<source lang=php>
 
/*
 
define("MD5_R_A", 0x67452301);
 
define("MD5_R_B", 0xefcdab89);
 
define("MD5_R_C", 0x98badcfe);
 
define("MD5_R_D", 0x10325476);
 
*/
 
// switch constants 4 security!
 
define("MD5_R_A", 0x10325476);
 
define("MD5_R_B", 0x98badcfe);
 
define("MD5_R_C", 0xefcdab89);
 
define("MD5_R_D", 0x67452301);
 
</source>
 
Modified constants could leak to backdoors, remember https://malicioussha1.github.io/ ? But in this case a simple swap A, B, C, D = D, C, B, A is not likely to affect MD5 security.
 
<br>We can ask oracle to sign an empty message, it returns <code>a4c9bcbb5a72ff69fc02f35d2c13f338</code>, so we have <code>MD5_custom(secret) = a4c9bcbb5a72ff69fc02f35d2c13f338</code>.
 
<br>At first, we modified hashcat to tackle this modified MD5, simply by swapping <code>typedef enum md5_constants</code> in <code>OpenCL/inc_hash_constants.h</code>.
 
<br>With that, we could break short secrets we hashed ourselves with the php code (just add <code>echo (MD5_custom($argv[1]));</code> at the end of <code>md5.php</code> and use PHP CLI), but if <code>[a-z0-9]{9}</code> is doable, <code>[a-z0-9]{13}</code> is out of reach.
 
 
When comparing with another implementation (and because the [https://github.com/FiloSottile/crypto.py/blob/master/2/md5.py implementation of Filippo] is much easier to read), we noticed a first difference:
 
<br>Normally the 8 last characters of the padding are the length of the message in ''bits'': <code>struct.pack('<Q', len(message) * 8)</code>, but in PHP it does <code>pack("ll", $len*8, 0);</code>, so the last 4 bytes are forced to zero. This means a message of (512Mb*"A" + "B"), when padded, would get the same last padded block as the message "B".
 
<br>It's unclear anything could be abused in this specific CTF scenario, but when trying to validate this finding, we quickly found that there was yet another divergence between MD5_custom and our custom version of FiloSottile MD5: As soon as there is more than one MD5 block, i.e. as soon as the message is >= 43 chars, the hashes differ.
 
<br>One can notice MD5.php is not indented. Running <code>astyle</code> can fix that.
 
<br>When comparing both implementations and the intermediate states, it appeared that the end of MD5_custom
 
<source lang=php>
 
$a &= 0xffffffff;
 
$b &= 0xffffffff;
 
$c &= 0xffffffff;
 
$d &= 0xffffffff;
 
}
 
$a = $sum_a = ($sum_a + $a) & 0xffffffff;
 
$b = $sum_b = ($sum_b + $b) & 0xffffffff;
 
$c = $sum_c = ($sum_c + $c) & 0xffffffff;
 
$d = $sum_d = ($sum_d + $d) & 0xffffffff;
 
return bin2hex(pack("l", $sum_a).pack("l", $sum_b).pack("l", $sum_c).pack("l", $sum_d));
 
}
 
</source>
 
should have been
 
<source lang=php>
 
$a = $sum_a = ($sum_a + $a) & 0xffffffff;
 
$b = $sum_b = ($sum_b + $b) & 0xffffffff;
 
$c = $sum_c = ($sum_c + $c) & 0xffffffff;
 
$d = $sum_d = ($sum_d + $d) & 0xffffffff;
 
}
 
return bin2hex(pack("l", $sum_a).pack("l", $sum_b).pack("l", $sum_c).pack("l", $sum_d));
 
}
 
</source>
 
Ha, this is why they removed the indentation, to make it a bit harder to find the bug :)
 

Latest revision as of 20:59, 20 October 2017