The medium levels brought the first reverse enginnering challenges, the first web hacking challenges, some image manipulation, and of course, some obfuscated Perl.
Day 8
Challenge
HV19.08 SmileNcryptor 4.0
Categories:
CRYPTO REVERSE ENGINEERING
Level:
medium
Author:
otaku
You hacked into the system of very-secure-shopping.com and you found a SQL-Dump with $$-creditcards numbers. As a good hacker you inform the company from which you got the dump. The managers tell you that they don’t worry, because the data is encrypted.
I’m also given a Goal:
Analyze the “Encryption”-method and try to decrypt the flag.
And some Hints:
CC-Numbers are real/valid ones.
Cyber-Managers often doesn’t know the difference between encoding and encryption.
The .sql file has a handful of sections. There are two interesting ones.
Decode
The first is for the table creditcards:
---- Dumping data for table `creditcards`--LOCKTABLES`creditcards`WRITE;/*!40000 ALTER TABLE `creditcards` DISABLE KEYS */;INSERTINTO`creditcards`VALUES(1,'Sirius Black',':)QVXSZUVY\ZYYZ[a','12/2020'),(2,'Hermione Granger',':)QOUW[VT^VY]bZ_','04/2021'),(3,'Draco Malfoy',':)SPPVSSYVV\YY_\\]','05/2020'),(4,'Severus Snape',':)RPQRSTUVWXYZ[\]^','10/2020'),(5,'Ron Weasley',':)QTVWRSVUXW[_Z`\b','11/2020');/*!40000 ALTER TABLE `creditcards` ENABLE KEYS */;UNLOCKTABLES;
Just above this, it defines the structure of the table. The important thing to know is that the third column is the cc_number. Clearly, like the prompt says, it’s encrypted.
I read these values into a Python console to play with:
I spent a long time confused here. There are 20 different encrypted characters, but only 10 possible digits. How can this work? It can’t be a one to one translation. But I still do think that these card lengths of 14-16 look right.
There’s a lot of rules as to what goes into a valid credit card number. There are also sites that will take a number and report back if it is valid. I scanned through this table and took at look at what the possible first digit could be for a number of length 14, 15, and 16:
Length
Possible First Digits
14
3, 5, 6
15
2, 3, 5, 6
16
2, 3, 4, 5, 6, 8, 9
If I think that at least the translation is the same for each position in the encrypted string, then I have a Q at first position in lengths of 14, 15, and 16.
Q must be 3, 5, 6 in pos 0. I started with 3. If Q = 3, then the next digt must be 4 or 7 in the 15 character string. To get Q to 3, I found I could subtract 30. Unsurprisingly, that did not give a valid card number:
But I noticed that the second digit was 8, and 7 was one of the valid numbers. What if it subtracted one more for each position. I wrote a quick decode function:
>>>defdecode(enc):... out = ''
... for i,c in enumerate(enc):
... out += chr(ord(c)-30-i)
... return out
...
Now I tried the first card:
>>>decode(cards[0])'378282246310005'
The checker reported it valid. I decoded the rest:
---- Dumping data for table `flags`--LOCKTABLES`flags`WRITE;/*!40000 ALTER TABLE `flags` DISABLE KEYS */;INSERTINTO`flags`VALUES(1,'HV19{',':)SlQRUPXWVo\Vuv_n_\ajjce','}');/*!40000 ALTER TABLE `flags` ENABLE KEYS */;
It looks like a join of three strings:
HV19{
:)SlQRUPXWVo\Vuv_n_\ajjce
}
The middle string matches the encryption format. I’ll remove the :) and decode it:
Visiting the following railway station has left lasting memories.
</picture>
Santas brand new gifts distribution system is heavily inspired by it. Here is your personal gift, can you extract the destination path of it?
</picture>
Solution
The railway station is Cambridge North. I found it by uploading the image to Google Image Search, and that lead me to the Wikipedia Page. The picture is in the section on Facilities, and that section ends with:
In fact, when I did the Google Image Search, I had to skip through a handful of articles on Rule 30 to find Cambridge North Station. Rule 30 is a method to algorithmicly create this pattern that seems chaotic. If you start with a single black pixel at the top, it’ll generate this image:
The solution was something I thought of very quickly, but spent a long time trying to implement. The QRcode seems good in the top right and top left, but bad on the bottom. It happens that pyramid shape from Rule30 would fit nicely over the broken parts of the QRcode. So my thinking was that I needed to combine them. Just by looking at the two images, the only operator I can think of is to xor the two pixels. Other operations won’t work, like or or and because I need both white to flip to black and black to flip to white in the QRcode.
I took the image above and used a paint program to crop it so that it only had the first 33 rows:
I’ll also notice that the blocks are 1x1 pixel in that image.
Next I looked at the QRcode. I’ll see that the blocks in this image are 5x5 pixels. That’ll be useful in a minute. I loaded that image into Gimp, a free Photoshop alternative. I load the QR first because it’s larger, and thus sets the background canvas big enough, but I really the order doesn’t matter. Then Gimp, I went to File -> Open as Layers…, and selected the Rule30 image. It looks like this:
On the bottom right, you can see the layers. With the bottom layer selected, I’ll go to Layer -> Scale Layer…, and change Width and Height to 20% (which should take five pixels to one). When I hit Scale, The QR has now disappeared behind the Rule30:
In the Layers sub windows, I’ll select the top layer, and change the Mode from Normal to Exclude. This is what I think of as XOR. The result is kinda QR-ish again, but still not a QRcode, and now inverted.
Colors -> Invert fixes the inversion, but it still isn’t right:
I lost about 30 minutes thinking this was a deadend before I decided to try moving the two laters in space. Since I had already cut the Rule30 image to match vertically, I started with horizontal, clicking on the layer, and then using my arrow keys. Going right didn’t give anything useful, but coming back to the left did:
Hints were later added to the challenge that pointed to this:
it starts with a single pixel
centering is hard
The resulting QRcode gives the flag.
Flag: HV19{Cha0tic_yet-0rdered}
Day 10
Challenge
HV19.10 Guess what
Categories:
FUN
Level:
medium
Author:
inik
The flag is right, of course
There’s a zip file (I’ll show the third one from here on):
Time for full points will be extended for additional 24 hours
No asm needed
run it on linux
I picked a good day to get a late start on this one, as the originally released binary apparently was unsolvable. I spent a little time with the second binary, before learning that it didn’t work either.
$ file guess3
guess3: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5e1e9f74990e4f8f96d380d2b5264a3567a9d046, stripped
When I run the binary, it prompts for input:
# ./guess3
Your input: 0xdf
nooooh. try harder!
It says no RE required. I tried to open it quickly in gdb, but it seems there’s anti-debug in place as well. Typically for a challenge like this if they want you to solve it without RE (which they do based on both the hint and the fact that this is category FUN and not REVERSE ENGINEERING), they either make it really easy so that it’s just faster to solve without RE, or make the RE really hard. I suspect they’ve done the latter.
So I do the next best thing, and run strace and ltrace, which will show me the system calls made and what library calls are made respectively. For both of these, there’s a ton of output, especially stuff setting up the binary which isn’t necessarily interesting (though it can be), so I’ll snip the interesting parts.
strace didn’t show anything that interesting (it had guess2):
ltrace showed more. I like to run it as echo test | ltrace ./guess3 2>&1 | less, so that I can jump around the output in less. In doing that, I was expecting to see certain comparisons, so I checked for strcmp, and found this:
I think this may have been an anti-debug technique that went astray.
Day 11
Challenge
HV19.11 Frolicsome Santa Jokes API
Categories:
FUN
Level:
medium
Author:
inik
The elves created an API where you get random jokes about santa.
Go and try it here: http://whale.hacking-lab.com:10101
Solution
API Functionality
The page gives an overview of the API:
I can register a user. If my password is 8 characters, it complains:
$ curl -s-X POST -H'Content-Type: application/json' http://whale.hacking-lab.com:10101/fsja/register --data'{"username":"0xdf", "password": "0xdf0xdf"}'{"errorMessage":"Password empty or too short","errorCode":400}
$ curl -X GET "http://whale.hacking-lab.com:10101/fsja/random?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoiMHhkZiIsInBsYXRpbnVtIjpmYWxzZX0sImV4cCI6MTU3NjAzMTI0NS4zMzcwMDAwMDB9.loytdJB-DCIeY14urvC5M___0ZF9vvMlHTF8QVE1GXU"{"joke":"You know you’re getting old, when Santa starts looking younger.","author":"Bart Simpson in The Simpsons","platinum":false}
Get Platinum
The token is a JWT, which is a token with encoded information, and that is carries a signature. Without knowing the password used to sign the token, I can’t change the information in the token, as it will be invalid. But I can decode it and read the information. One way to do that is to drop it into jwt.io, which decode this one for me:
I can see it carries my username, and a field, platinum, which is set to false. If I knoew the secret, I could change information, like setting platinum to true. But I don’t. I gave it a quick attempt to crack the secret using john, but no luck.
I went back to the registration, where the API tells me to send a username and password. I tried registering with an additional field, and it returned success:
If I toss that token into the debugger at jwt.io, I see that platinum is set to true for this token:
Now if I request a quote, I get the flag:
$ curl -s-X GET "http://whale.hacking-lab.com:10101/fsja/random?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoiMHhkZmRmIiwicGxhdGludW0iOnRydWV9LCJleHAiOjE1NzYwMzI0NjQuODExMDAwMDAwfQ.3ZJH-BjN9-EGv2_YGgLeF_qGaFDwgzaMPzKgurOoW_s"{"joke":"Congratulation! Sometimes bugs are rather stupid. But that's how it happens, sometimes. Doing all the crypto stuff right and forgetting the trivial stuff like input validation, Hohoho! Here's your flag: HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}","author":"Santa","platinum":true}
Day 11 came with a new server, whale.hacking-lab.com. The challenge runs on port 10101. Running nmap on the server shows another port, open, 17 (ignoring SSH on 22, since that’s likely for legitimate administration of the host):
# nmap -p---min-rate 1000 whale.hacking-lab.com
Starting Nmap 7.80 ( https://nmap.org ) at 2019-12-11 19:45 EST
Nmap scan report for whale.hacking-lab.com (80.74.140.188)
Host is up (0.099s latency).
rDNS record for 80.74.140.188: urb80-74-140-188.ch-meta.net
Not shown: 65527 filtered ports
PORT STATE SERVICE
17/tcp open qotd
22/tcp open ssh
80/tcp closed http
443/tcp closed https
2222/tcp closed EtherNetIP-1
4444/tcp closed krb524
5555/tcp closed freeciv
10101/tcp open ezmeeting-2
Nmap done: 1 IP address (1 host up) scanned in 130.60 seconds
Each time I talked to the port, it returned a single character. It was typically the same character, but it seemed to change periodically. One time I checked and got a }, and that was a good clue that it was a flag.
I wrote a quick python script to check once a minute (later changed that to 10 minutes since it seems to update on the hour each hour), and print the character if it’s different:
$ file BackToBasic.exe
BackToBasic.exe: PE32 executable (GUI) Intel 80386, for MS Windows
I fired up a Windows VM. The program has an oldschool icon:
When I ran it, it pops a message box to check your flag:
I’ll open this in x32dbg to try to walk through it. It’s written in VB, so I’m going to see a lot of functions from msvbvm60.dll. I ran the program til it brakes at the entry point. Then right click, Search For -> Current Region -> String References. I get back four strings:
I’ll jump to that part of the code. That function starts at 0x401F80, which I’ll go to, and hit g to graph it. After a bunch of setup, there’s a branch:
There’s two checks that if I fail reference the “Status: wrong message”. I did a lot of tinkering, and found where the code references HV19. In Ghidra, it looks like this:
I set a break point at 0x4022BF, just at the top of the block to the bottom right. I found that if I entered HV19{ followed by enough characters to get the string length to 33 it would hit the break point. So those first two checks must be looking at starting with HV19 and the right length.
Next comes a loop, followed by a check. If that check is correct, it prints correct:
The string at 0x401B40 is references right near that check. In Ghidra, I can see it as:
The full string is (in a Python console):
>>>key="6klzic<=bPBtdvff'y\x7fFI~on//N"
I hypothesize that this is the key. My input is going to be transformed (likely with at least an xor based on the function calls in the loop), and then compared to this.
The VB calling conventions are confusing, and there’s no documentation on the web. I did notice a couple things:
The call to __vbaStrValVal at 0x402368 returns a pointer to EAX to the current character in my input string as it’s looped over.
Two instructions later, rtcAnsiValueBStr puts that character into EAX.
I don’t totally understand what comes back from the __vbaVarXor call at 0x402391, but there’s a call edi two instructions later, and after that, there’s a different single character in EAX. When my input was a, the output there was g. When it was b, the output was d.
I did a test in a Python console:
>>>ord('a')^ord('g')6
>>>ord('b')^ord('d')6
That confirmed for me that the first byte of my input (after HV19{) is xored by 6.
The next byte, my input was b, and the output was d:
>>>ord('b')^ord('e')7
Then the next byte I found an xor by 8. I formed a theory and tested it:
Switzerland’s national security is at risk. As you try to infiltrate a secret spy facility to save the nation you stumble upon an interesting looking login portal.
Can you break it and retrieve the critical information?
I’m given a url, http://whale.hacking-lab.com:8888/trieme/, and a zip:
Looking at the site, it’s just a single text field and a login button:
When I click the login button, I get a warning:
At first there’s a temptation to think I can change the textbox and have it repeated, but no matter what I enter, I get the same message about intrusions being reported.
I’ll turn to the code:
packagecom.jwt.jsf.bean;importorg.apache.commons.collections4.trie.PatriciaTrie;importjava.io.IOException;importjava.io.InputStream;importjava.io.Serializable;importjava.io.StringWriter;importjavax.faces.bean.ManagedBean;importjavax.faces.bean.SessionScoped;importstaticorg.apache.commons.lang3.StringEscapeUtils.unescapeJava;importorg.apache.commons.io.IOUtils;!@ManagedBean(name="notesBean")@SessionScopedpublicclassNotesBeanimplementsSerializable{/**
*
*/privatePatriciaTrie<Integer>trie=init();privatestaticfinallongserialVersionUID=1L;privatestaticfinalStringsecuritytoken="auth_token_4835989";publicNotesBean(){super();init();}publicStringgetTrie()throwsIOException{if(isAdmin(trie)){InputStreamin=getStreamFromResourcesFolder("data/flag.txt");StringWriterwriter=newStringWriter();IOUtils.copy(in,writer,"UTF-8");Stringflag=writer.toString();returnflag;}return"INTRUSION WILL BE REPORTED!";}publicvoidsetTrie(Stringnote){trie.put(unescapeJava(note),0);}privatestaticPatriciaTrie<Integer>init(){PatriciaTrie<Integer>trie=newPatriciaTrie<Integer>();trie.put(securitytoken,0);returntrie;}privatestaticbooleanisAdmin(PatriciaTrie<Integer>trie){return!trie.containsKey(securitytoken);}privatestaticInputStreamgetStreamFromResourcesFolder(StringfilePath){returnThread.currentThread().getContextClassLoader().getResourceAsStream(filePath);}}
Right away I’m drawn to the getTrie() function. It calls isAdmin(trie), and then if the answer is no, it prints “INTRUSION WILL BE REPORTED!”, which is familiar.
isAdmin checks if the trie contains the token securitytoken, which is the static string “auth_token_4835989”. If that token is NOT there, it returns admin.
Time to figure out what a Particia Tree is. It’s a specific radix tree with radix of two. So it’s a data structure that stores things in a tree sorted by common prefix.
It also has a vulnerability in it’s implementation in CommonCollections from April, where it ignores trailing unicode null. That means that it thinks x and x\u0000 are the same thing. But Java will differentiate between them, which can lead to weirdness.
I took a look at my POST request in Burp:
First, I tried adding the null characters to the ViewState by sending this request over to Burp Repeater. It crashed the page, with a title of “Error - viewId:/index.xhtml - View /index.xhtml could not be restored.”. I tried with it the other parameters, and on j_idt14:name, I actually made a mistake and appended \u000 (only three 0s, not 4). In HTTP 500 that came back, it had the following error:
javax.faces.component.UpdateModelException: javax.el.ELException: /index.xhtml @19,52 value="#{notesBean.trie}": Error writing [trie] on type [com.jwt.jsf.bean.NotesBean]
It failed writing to trie! That means what I POST gets written to trie. And, in the callstack, there’s this:
So it’s happening in the setTrie function. So now I can theorize that that parameter is being passed to setTrie().
I’ve got all the pieces now to make this attack. The trie object is initialized with a key of securitytoken (or auth_token_4835989) with value 0. Then I post something, and the name field is passed to setTrie, which tries to write a second key with my input and value 0. Then getTrie is called, which calls isAdmin. To be admin, the key auth_token_4835989 has to not be there.
If I post with the name of auth_token_4835989\u0000, due to the bug in Common Collections PatriciaTrie, it will overwrite the original key with mine. Then when Java checks to see if the key auth_token_4835989 is present, it won’t be, because only auth_token_4835989\u0000 remains.
In practice, I POST with j_idt14%3Aname=auth_token_4835989%5cu0000 (rest of the parameters the same), and I get back:
HTTP/1.1 200
X-Powered-By: JSF/2.0
Content-Type: text/html;charset=UTF-8
Content-Length: 560
Date: Fri, 13 Dec 2019 02:31:03 GMT
Connection: close
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><head><title>SpyPortal</title><linktype="text/css"rel="stylesheet"href="/trieme/faces/javax.faces.resource/style.css?ln=css"/></head><body><h3>Secret Spy Portal</h3><h4>STATUS:
We will steal all the national chocolate supplies at christmas, 3pm: Here's the building codes: HV19{get_th3_chocolateZ}
!</h4></body></html>
Flag: HV19{get_th3_chocolateZ}
Day 14
Challenge
HV19.14 Achtung das Flag
Categories:
FUN PROGRAMMING
Level:
medium
Author:
M.
Let’s play another little game this year. Once again, I promise it is hardly obfuscated.
I’m given this code:
useTk;useMIME::Base64;chomp(($a,$a,$b,$c,$f,$u,$z,$y,$r,$r,$u)=<DATA>);sub M{$M=shift;##@m=keys%::;(grep{(unpack("%32W*",$_).length($_))eq$M}@m)[0]};$zvYPxUpXMSsw=0x1337C0DE;###/_help_me_/;$PMMtQJOcHm8eFQfdsdNAS20=sub{$zvYPxUpXMSsw=($zvYPxUpXMSsw*16807)&0xFFFFFFFF;};($a1Ivn0ECw49I5I0oE0='07&3-"11*/(')=~y$!-=$`-~$;($Sk61A7pO='K&:P3&44')=~y$!-=$`-~$;m/Mm/g;($sk6i47pO='K&:R&-&"4&')=~y$!-=$`-~$;;;;$d28Vt03MEbdY0=sub{pack('n',$fff[$S9cXJIGB0BWce++]^($PMMtQJOcHm8eFQfdsdNAS20->()&0xDEAD));};'42';($vgOjwRk4wIo7_=MainWindow->new)->title($r);($vMnyQdAkfgIIik=$vgOjwRk4wIo7_->Canvas("-$a"=>640,"-$b"=>480,"-$u"=>$f))->pack;@p=(42,42);$cqI=$vMnyQdAkfgIIik->createLine(@p,@p,"-$y"=>$c,"-$a"=>3);;;$S9cXJIGB0BWce=0;$_2kY10=0;$_8NZQooI5K4b=0;$Sk6lA7p0=0;$MMM__;$_=M(120812).'/'.M(191323).M(133418).M(98813).M(121913).M(134214).M(101213).'/'.M(97312).M(6328).M(2853).'+'.M(4386);s|_||gi;@fff=map{unpack('n',$::{M(122413)}->($_))}m:...:g;($T=sub{$vMnyQdAkfgIIik->delete($t);$t=$vMnyQdAkfgIIik->#FOOcreateText($PMMtQJOcHm8eFQfdsdNAS20->()%600+20,$PMMtQJOcHm8eFQfdsdNAS20->()%440+20,#Perl!!"-text"=>$d28Vt03MEbdY0->(),"-$y"=>$z);})->();$HACK;$i=$vMnyQdAkfgIIik->repeat(25,sub{$_=($_8NZQooI5K4b+=0.1*$Sk6lA7p0);;$p[0]+=3.0*cos;$p[1]-=3*sin;;($p[0]>1&&$p[1]>1&&$p[0]<639&&$p[1]<479)||$i->cancel();00;$q=($vMnyQdAkfgIIik->find($a1Ivn0ECw49I5I0oE0,$p[0]-1,$p[1]-1,$p[0]+1,$p[1]+1)||[])->[0];$q==$t&&$T->();$vMnyQdAkfgIIik->insert($cqI,'end',\@p);($q==###$cqI||$S9cXJIGB0BWce>44)&&$i->cancel();});$KE=5;$vgOjwRk4wIo7_->bind("<$Sk61A7pO-n>"=>sub{$Sk6lA7p0=1;});$vgOjwRk4wIo7_->bind("<$Sk61A7pO-m>"=>sub{$Sk6lA7p0=-1;});$vgOjwRk4wIo7_#%"->bind("<$sk6i47pO-n>"=>sub{$Sk6lA7p0=0if$Sk6lA7p0>0;});$vgOjwRk4wIo7_->bind("<$sk6i47pO"."-m>"=>sub{$Sk6lA7p0=0if$Sk6lA7p0<0;});$::{M(7998)}->();$M_decrypt=sub{'HACKVENT2019'};__DATA__
The cake is a lie!
width
height
orange
black
green
cyan
fill
Only perl can parse Perl!
Achtung das Flag! --> Use N and M
background
M'); DROP TABLE flags; --
Run me in Perl!
__DATA__
Solution
Play It
I’ll run the game, and it’s a game of snake, where I have to collect the flag, two characters at a time:
I can play manually, but the flag is long, and it’s nearly impossible not to run into yourself and lose.
useTk;useMIME::Base64;chomp(($a,$a,$b,$c,$f,$u,$z,$y,$r,$r,$u)=<DATA>);sub M{$M=shift;##@m=keys%::;(grep{(unpack("%32W*",$_).length($_))eq$M}@m)[0]};$zvYPxUpXMSsw=0x1337C0DE;###/_help_me_/;$PMMtQJOcHm8eFQfdsdNAS20=sub{$zvYPxUpXMSsw=($zvYPxUpXMSsw*16807)&0xFFFFFFFF;};($a1Ivn0ECw49I5I0oE0='07&3-"11*/(')=~y$!-=$`-~$;($Sk61A7pO='K&:P3&44')=~y$!-=$`-~$;m/Mm/g;($sk6i47pO='K&:R&-&"4&')=~y$!-=$`-~$;;;;$d28Vt03MEbdY0=sub{pack('n',$fff[$S9cXJIGB0BWce++]^($PMMtQJOcHm8eFQfdsdNAS20->()&0xDEAD));};'42';($vgOjwRk4wIo7_=MainWindow->new)->title($r);($vMnyQdAkfgIIik=$vgOjwRk4wIo7_->Canvas("-$a"=>640,"-$b"=>480,"-$u"=>$f))->pack;@p=(42,42);$cqI=$vMnyQdAkfgIIik->createLine(@p,@p,"-$y"=>$c,"-$a"=>3);;;$S9cXJIGB0BWce=0;$_2kY10=0;$_8NZQooI5K4b=0;$Sk6lA7p0=0;$MMM__;$_=M(120812).'/'.M(191323).M(133418).M(98813).M(121913).M(134214).M(101213).'/'.M(97312).M(6328).M(2853).'+'.M(4386);s|_||gi;@fff=map{unpack('n',$::{M(122413)}->($_))}m:...:g;($T=sub{$vMnyQdAkfgIIik->delete($t);$t=$vMnyQdAkfgIIik->#FOOcreateText($PMMtQJOcHm8eFQfdsdNAS20->()%600+20,$PMMtQJOcHm8eFQfdsdNAS20->()%440+20,#Perl!!"-text"=>$d28Vt03MEbdY0->(),"-$y"=>$z);})->();$HACK;$i=$vMnyQdAkfgIIik->repeat(25,sub{$_=($_8NZQooI5K4b+=0.1*$Sk6lA7p0);;$p[0]+=3.0*cos;$p[1]-=3*sin;;($p[0]>1&&$p[1]>1&&$p[0]<639&&$p[1]<479)||$i->cancel();00;$q=($vMnyQdAkfgIIik->find($a1Ivn0ECw49I5I0oE0,$p[0]-1,$p[1]-1,$p[0]+1,$p[1]+1)||[])->[0];$q==$t&&$T->();$vMnyQdAkfgIIik->insert($cqI,'end',\@p);($q==$cqI||$S9cXJIGB0BWce>44)&&$i->cancel();});$KE=5;$vgOjwRk4wIo7_->bind("<$Sk61A7pO-n>"=>sub{$Sk6lA7p0=1;});$vgOjwRk4wIo7_->bind("<$Sk61A7pO-m>"=>sub{$Sk6lA7p0=-1;});$vgOjwRk4wIo7_->bind("<$sk6i47pO-n>"=>sub{$Sk6lA7p0=0if$Sk6lA7p0>0;});$vgOjwRk4wIo7_->bind("<$sk6i47pO-m>"=>sub{$Sk6lA7p0=0if$Sk6lA7p0<0;});$::{M(7998)}->();$M_decrypt=sub{'HACKVENT2019'};__DATA__
The cake is a lie!
width
height
orange
black
green
cyan
fill
Only perl can parse Perl!
Achtung das Flag! --> Use N and M
background
M'); DROP TABLE flags; --
Run me in Perl!
__DATA__
The game is using TK, a framework for writing GUI applications. Right away I can identify the main loop:
I can’t say for certain what everything here does, but it looks like $p has to do with my position. I think this first line calculates the current angle of velocity, where 0 is due left, and so is 2\(\pi\). Then there is math to update the x (p[1]) and y (p[0]) positions using the sin and cos based on the current angle.
Then there’s a check to see if the position falls within the screen, and to call $i->cancel(); if not.
Then there’s a line that sets $q to something based on the current position. The next line checks if $q==$t, and if so, it calls $T. This is the check to see if the snake is at the place where the current flag piece is. I can check out $T:
It calls the canvas’ delete method on the current text object, and then creates a new one, using $PMMtQJOcHm8eFQfdsdNAS20 to generate random looking (though the same every time) values, and mod to keep it within the canvas. The text of the text object is created by calling $d28Vt03MEbdY0. I can check that out:
The last two lines of the look seem to add the current position into $cqI, which I guess is a list of occupied positions, and then checks if $q (some measure of position) is equal to $cqI (must return true if it’s in the list at all), or if $S9cXJIGB0BWce is greater than 44, and exits if either. $S9cXJIGB0BWce appears to be a counter referenced both here and in the function generating the text values.
Just looking at that, I’m expecting 45 blocks of two characters, or 90 characters in the flag.
Just after the loop, there’s a section that updates based on the key presses:
Now, I can play and not die when I hit myself. I started playing, but writing down the flag while playing was too much. So I went into the function that creates the text. Perl has this implicit return, so as is, it just returns the output of the one line:
Now, as the letters are created, they are also printed to the terminal. I’ll play for a bit, and eventually collect all the flags, and find the full flag printed out on my console:
But, that still took a bit of effort. I can chop out more, and make it go fast. With my modification to make it print still in there, I’ll remove the need for the snake:
Something I didn’t know about, but discovered after solving day 14 was the Deparse module for Perl. It compiles the code, and the decompiles it, formatting it nicely, removing a lot of the obfuscation.
$ perl -MO=Deparse prog.pl > prog-deparse.py
prog.pl syntax OK
Now I’ve got cleaner code:
sub Tk::Toplevel::FG_Destroy;sub Tk::Toplevel::FG_BindOut;sub Tk::Toplevel::FG_BindIn;sub Tk::Toplevel::FG_In;sub Tk::Toplevel::FG_Create;sub Tk::Toplevel::FG_Out;sub Tk::Frame::labelVariable;sub Tk::Frame::freeze_on_map;sub Tk::Frame::AddScrollbars;sub Tk::Frame::queuePack;sub Tk::Frame::scrollbars;sub Tk::Frame::sbset;sub Tk::Frame::label;sub Tk::Frame::packscrollbars;sub Tk::Frame::labelPack;sub Tk::Frame::FindMenu;useTk;useMIME::Base64;chomp(($a,$a,$b,$c,$f,$u,$z,$y,$r,$r,$u)=readlineDATA);sub M{$M=shift();@m=keys%main::;(grep{unpack('%32W*',$_).length($_)eq$M;}@m)[0];}$zvYPxUpXMSsw=322420958;/_help_me_/;$PMMtQJOcHm8eFQfdsdNAS20=sub {$zvYPxUpXMSsw=$zvYPxUpXMSsw*16807&4294967295;};($a1Ivn0ECw49I5I0oE0='07&3-"11*/(')=~tr/!-=/`-|/;($Sk61A7pO='K&:P3&44')=~tr/!-=/`-|/;/Mm/g;($sk6i47pO='K&:R&-&"4&')=~tr/!-=/`-|/;$d28Vt03MEbdY0=sub {pack'n',$fff[$S9cXJIGB0BWce++]^&$PMMtQJOcHm8eFQfdsdNAS20()&57005;};'???';($vgOjwRk4wIo7_='MainWindow'->new)->title($r);($vMnyQdAkfgIIik=$vgOjwRk4wIo7_->Canvas("-$a",640,"-$b",480,"-$u",$f))->pack;@p=(42,42);$cqI=$vMnyQdAkfgIIik->createLine(@p,@p,"-$y",$c,"-$a",3);$S9cXJIGB0BWce=0;$_2kY10=0;$_8NZQooI5K4b=0;$Sk6lA7p0=0;$MMM__;$_=M(120812).'/'.M(191323).M(133418).M(98813).M(121913).M(134214).M(101213).'/'.M(97312).M(6328).M(2853).'+'.M(4386);s/_//gi;@fff=map({unpack'n',$main::{M122413}($_);}/.../g);($T=sub {$vMnyQdAkfgIIik->delete($t);$t=$vMnyQdAkfgIIik->createText(&$PMMtQJOcHm8eFQfdsdNAS20()%600+20,&$PMMtQJOcHm8eFQfdsdNAS20()%440+20,'-text',&$d28Vt03MEbdY0(),"-$y",$z);})->();$HACK;$i=$vMnyQdAkfgIIik->repeat(25,sub {$_=$_8NZQooI5K4b+=0.1*$Sk6lA7p0;$p[0]+=3*cos($_);$p[1]-=3*sin($_);$i->cancelunless$p[0]>1and$p[1]>1and$p[0]<639and$p[1]<479;'???';$q=+($vMnyQdAkfgIIik->find($a1Ivn0ECw49I5I0oE0,$p[0]-1,$p[1]-1,$p[0]+1,$p[1]+1)||[])->[0];&$T()if$q==$t;$vMnyQdAkfgIIik->insert($cqI,'end',\@p);$i->cancelif$q==$cqIor$S9cXJIGB0BWce>44;});$KE=5;$vgOjwRk4wIo7_->bind("<$Sk61A7pO-n>",sub {$Sk6lA7p0=1;});$vgOjwRk4wIo7_->bind("<$Sk61A7pO-m>",sub {$Sk6lA7p0=-1;});$vgOjwRk4wIo7_->bind("<$sk6i47pO-n>",sub {$Sk6lA7p0=0if$Sk6lA7p0>0;});$vgOjwRk4wIo7_->bind("<$sk6i47pO".'-m>',sub {$Sk6lA7p0=0if$Sk6lA7p0<0;});$main::{M7998}();$M_decrypt=sub {'HACKVENT2019';};__DATA__
The cake is a lie!
width
height
orange
black
green
cyan
fill
Only perl can parse Perl!
Achtung das Flag! --> Use N and M
background
M'); DROP TABLE flags; --
Run me in Perl!
__DATA__
Hidden 4
Challenge
HV19.H4 Hidden Four
Categories:
FUN PROGRAMMING
Level:
novice
Author:
M.
There’s no description.
Solution
The day 14 challenge was all about obfuscated Perl, and the flag was weird, s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20\n^&&s[(.)(..)][\2\1]g;s%4(...)%"p$1t"%ee. That kind of looked like Perl to me. So I saved it in a file, flag.pl, and ran it: