Where is Twardowski? CTF by SecuRing Twardowski, one of the greatest hackers in Cracow, has been missing for 3 days now. He left some notes, but nobody can access them. Can you help us?
~taken from the rules page
Link to the CTF: https://twardowski.securing.pl/
Quick description
This CTF was organized by SecuRing as a week-long CTF with small challenges sprinkled around a website and final flags for getting to a hidden directory and getting all flags.
I was solving challenges from this CTF with few members of AlphaPwners
20 normal flags
801
After checking the source code of the page I noticed there were around 1000 lines in the documents, most of them empty. I was able to find the flag around line 500
802
To find this flag I had to check cookies that the site created.
803
To find this one I had to check the headers server sends when getting the index page
1
2
3
4
5
6
7
8
9
10
11
12
$ curl --head https://twardowski.securing.pl/
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 11 Sep 2020 17:39:49 GMT
Content-Type: text/html
Content-Length: 5652
Last-Modified: Tue, 01 Sep 2020 13:17:54 GMT
Connection: keep-alive
ETag: "5f4e4a02-1614"
[...Cookies...]
X-Totaly-Legit-Header: FLAG{803d8adce61c4b1efb974f5eff93f5cb54afd}
Accept-Ranges: bytes
804
I found this flag by looking at robots.txt
1
2
3
4
User-agent: pentest
Disallow: /*
Disallow: /FLAG{804764d8bae198e68fcc037066c386978a187}
#TODO: remove .DS_Store file from statics ~ntrojanowska
I also got a .DS_Store hint here
.DS_Store
.DS_Store is a file created by OSX systems to save metadata about directories in Finder. With this file I was able to extract the list of file entries in “/static/”
If it’s just catted out then there’s just a lot of garbage data so I decided to make it easier to read with a quick sed filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
$ cat .DS_Store | sed 's/Ilocblob/\n/g'
Bud1
�
3
DSDB@�@ @ @3▒android-icon-144x144.png
��������▒android-icon-192x192.png
��������android-icon-36x36.png
��������android-icon-48x48.png
��������android-icon-72x72.png
��������android-icon-96x96.png
��������apple-icon-114x114.png
��������apple-icon-114x114.png_original
��������apple-icon-120x120.png
��������apple-icon-120x120.png_original
��������apple-icon-144x144.png
��������apple-icon-144x144.png_original
��������apple-icon-152x152.png
��������apple-icon-152x152.png_original
��������apple-icon-180x180.png
��������apple-icon-180x180.png_original
��������apple-icon-57x57.png
��������apple-icon-57x57.png_original
��������apple-icon-60x60.png
��������apple-icon-60x60.png_original
��������apple-icon-72x72.png
��������apple-icon-72x72.png_original
��������apple-icon-76x76.png
��������apple-icon-76x76.png_original
��������▒apple-icon-precomposed.png
��������#apple-icon-precomposed.png_original
��������apple-icon.png
��������apple-icon.png_original
��������background.png
��������background_lowreso.png
��������browserconfig.xml
��������favicon-16x16.png
��������favicon-32x32.png
��������favicon-96x96.png
�������� flag.html
��������+FLAG{80bfd64a059a831b5b2a3460899ef22fdfdff}
�������� game.html
��������
hello_world
�������� index.php
manifest.json
moonlight.mp3
��������ms-icon-144x144.png
��������ms-icon-150x150.png
��������ms-icon-310x310.png
��������ms-icon-70x70.png
��������rb500_webupgrade.bin
��������secret_message_inside.zip
��������secrets.min.js
sha256.min.js
�������� style.css
��������zRZYMappka.apk
��������
805
I found this flag when I stumbled upon the 404 page. The text is somehow readable after flipping it
806
I found this flag in flag.html (mentioned in .DS_Store)
When I opened the file in my browser it was all grey but after highlighting part of the page i saw characters resembling a QR code
After seeing that i looked at the source code of the page
After copying the text straight from the page to a text document (with the spaces preserved) I prepared a python script to convert it into a qr code image:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from PIL import Image
lines = [l.replace('\n', '') for l in open('./flag_txt', 'r').readlines()]
dimensions = (len(lines[0]), len(lines) * 2) # Width, Height
i = Image.new('RGB', dimensions, color = 'white')
pixels = i.load()
for y, row in enumerate(lines):
for x, p in enumerate(row):
if p == '@':
pixels[x, y*2] = (0, 0, 0)
pixels[x, (y*2)+1] = (0, 0, 0)
else:
pixels[x, y*2] = (0xff, 0xff, 0xff)
pixels[x, (y*2)+1] = (0xff, 0xff, 0xff)
i.save('./806.png')
NOTE: Height is doubled because without it the image is squished and unreadable by most readers i tried
Final result:
807
I found flag 807 in the “hello_world” file mentioned in DS_Store. After downloading it I checked it with file
1
2
$ file hello_world
hello_world: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, not stripped
As it’s an ELF file I decided to open it up in ghidra
After analysis finished I noticed two suspicious functions in the function listing:
The “easy_flag” function was just a decoy, didn’t really contain anything interesting
The “another_flag” contained some interesting array stuff After cleaning it up a bit in ghidra (retyping the return value, changing the variable name) I saw the individual characters clearly
I copied the chars manually and with some vscode multi-cursor magic got them to a single string.
808
This flag was hidden in index.html in a commented-out JS snippet.
The script was obfuscated with a common technique that uses an array for strings and mixes them up. I cleaned it up and this is the resulting code:
1
2
3
4
5
6
7
8
9
10
11
12
function printSecretFlag() {
var flag = "FLAG{8086a91e45bc076403e7a2e8928386c233ae7}"
var almost = "Almost.."
console.log(almost);
}
function printNotFlag() {
var where = "Where is flag?";
console.log(where);
}
printNotFlag();
809
For this flag I had to make a quick directory scan of the site (i used quickhits.txt from SecLists) and I found .git/
With .git discovered i used GitTools to extract most of the files and was left with a git repo (although a bit broken)
1
2
3
4
5
$ ~/bin/GitTools/Dumper/gitdumper.sh https://twardowski.securing.pl/.git/ siteRepo
[Bunch of random filenames]
$ cd siteRepo
$ git status
fatal: this operation must be run in a work tree
To fix that I used a random command i found on stackoverflow git command that converts the repo from a bare one to a normal repo with a worktree
1
2
3
$ git config --unset core.bare
$ git status
[Bunch of files deleted]
To un-delete the files i used “git reset –hard HEAD” and I was left with a nice repo with some history
The first entry in the repo log is suspicious so let’s check that
And here’s our flag
80a
80a is located in the same repo as 809, just in a different place and at a “different time”.
I found it by browsing all files in different commits.
I found it as a “test secret” in base.html in “Added icons”
80b
I already found this flag when filtering entries in .DS_Store
1
2
3
4
$ sed 's/Ilocblob/\n/g' .DS_Store
[FILES]
��������+FLAG{80bfd64a059a831b5b2a3460899ef22fdfdff}
[FILES]
80c
This flag was located in “static/secret_message_inside.zip” on the webserver (found by .DS_Store)
The zip file contained another zip and a “password.txt” file. It’s a zip in a zip in a zip (…) in a zip, we have to unzip all of them to get to the flag.
For this one one of my teammates (kolokokop) shared a bash script with me to unzip all archives
1
for i in {500..0}; do cat password.txt >> password_list.txt; 7z x -y flag$i.zip -p"$(cat password.txt)"; done
It unzips all of them and saves the passwords into a password list for later inspection
After the script unzips all of the files I saw this in flag.txt
1
2
Nice! You made it!
But where is flag?
It wasn’t here so it probably was one of the passwords
1
2
$ grep -o "FLAG{.*}" password_list.txt
FLAG{80cdd0386ff8a8cf7c6fe8df8326e747965c2}
80d
80d was located in a .apk file called zRZYMappka
After downloading it and opening it up in jadx-gui I saw that it’s a cordova app meaning it’s written as a webapp rather than a Java app.
After looking through the resources i found a JS file with a suspicious looking fragment of code
Looking at the decipher function I saw that it uses XOR with a single key for all characters. I was able to easily bruteforce that in cyberchef with the starting string hardcoded
80e
For this flag there were two ways of getting it. I could either:
- browse the “rb500_webupgrade.bin” file from “static” and find a gif file with the flag encoded
- get the moonlight.mp3 file and analyze it
rb500_webupgrade.bin
From the filename I presumed this is a firmware update for a embedded device. Binwalk successfully extracted two squashfs file trees that look identical at the first glance but it’s easy to check that with diff
1
2
3
4
5
6
7
$ diff -r squashfs-root squashfs-root-0 2>/dev/null
Only in squashfs-root/etc: motd
Only in squashfs-root-0/etc: motd.gz
diff --color -r squashfs-root/etc/preinit squashfs-root-0/etc/preinit
62a63
> gzip -d /etc/motd.gz
Binary files squashfs-root/www/images/sputnik.gif and squashfs-root-0/www/images/sputnik.gif differ
There were two files that were different between the directories.
After extracting strings and doing a diff on them I got our flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ strings squashfs-root/www/images/sputnik.gif > sputnik1
$ strings squashfs-root-0/www/images/sputnik.gif > sputnik2
$ diff sputnik1 sputnik2
2c2
< RPhotoshop 3.0
---
> Photoshop 3.0
66,69c66
< hackme
< securing
< flag
< %80ef71927a6ce99778c56f54d5faa72ae0294
---
> NOTFLAG
moonlight.mp3
This file is an audio file with a piano playing and two distinct audio tracks layered onto it. On one channel I heard random beeps and sounds which sound like SSTV, on the other one I was able to hear a TTS program saying random words.
After extracting SSTV audio and decoding it I got images with TONGALF (FLAG-NOT) so that’s not the way
When I slowed the track down and listened closely I was able to make out some english words said in a weird way
1
??????? ??????? INDIA YANKEE XRAY
This gave me an idea that this might be NATO phonetic alphabet. After finding a translation chart with pronounciations listed I was able to decode the entire message (took me almost half an hour to decode the whole thing)
1
2
3
4
5
6
7
8
9
10
11
12
UJIYX 043ZYH OPDBZ 18MAV? 4DBZW SMH921 1114LIG MG90123S
4DBZW SMH921 1114IG MG90123S 0432YH OPDBZ 18MAV? D5FDBZW
4DBZW 114LIG
TONGALF (this was said 10+ times)
043ZYH OPDBZ 18MAV? D5FDBZW 4DBZW 1114LIG SMH921 4DBZW
SMG921 1114LIG MG09123S AA72ABZW ALF43ZYH
FLAGFLAGFLAG
80ef7192 7a6ce997 78c56f54 d5faa72a e0294
TONGALF
So this was a big waste of time
80f
This flag was also hidden in the rb500 firmware update. It was the second file that was different, “motd.gz”
After gunzipping it and reading the file I got this:
1
2
3
4
5
6
Żył kiedyś w Krakowie szlachcic – wielki czarnoksiężnik Twardowski. Bardzo
pragnął stać się mądrzejszy oraz sprytniejszy od innych i dlatego postanowił
wzbogacić swoją wiedzę i wynaleźć sposób na nieśmiertelność. Czytał stare
księgi, przeglądał pożółkłe dokumenty, aż w końcu w jednej z nich wyczytał, jak
można przywołać diabła.
80f6239dd090a821fa72d3aa459dc52947d77
810
This flag was pretty easy but easy to overlook. In the source code of the page I noticed that the CSRF token had b64 padding which threw me off a bit.
I decided to throw it into cyberchef and it returned with a flag
811
This one was hiding in favicon.ico
1
2
$ strings favicon.ico
FLAG{81166d6f581a84672a339514bff537a84d73b}
812
This flag was also hiding in icons, this time in the apple ones
1
2
3
4
5
6
7
8
9
10
11
$ wget https://twardowski.securing.pl/static/apple-icon-180x180.png
$ exiftool apple-icon-180x180.png
ExifTool Version Number : 12.00
File Name : apple-icon-180x180.png
Directory : .
File Size : 60 kB
[Random data]
Make : SecuRing
Model : FLAG{8129815924cf4138152755532d5306ebed675}
Image Size : 180x180
Megapixels : 0.032
813
813 was hiding in the background but not the copy that is on the site.
The style.css linked on the page was hinting that something is up with the background
After downloading the file, opening it up in stegsolve and browsing the various color planes I found it hiding on the gray bits plane.
814
This one was a real pain to get to. The only file I had left was a index.php file from both “static/” and the git repository.
From the text inside I recognized that it is a php file obfuscated with zendguard
1
2
3
4
5
<?php @Zend;
3074;
/* �!This is not a text file!�� */
print <<<EOM
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML><HEAD></HEAD><BODY LANG="en-US" DIR="LTR"><H2 ALIGN=CENTER>Zend Guard Run-time support missing!</H2>
Most of the tools I tried didn’t work but my teammate sent a very promising blog post about decrypting those. Blog post
After following the steps and running the command to decrypt the php file I received an error
1
2
3
4
root@258b6881f005:/# php index.php /src/index_git.php
PHP Fatal error: This file has expired. in /src/index_git.php on line 0
error compling /src/index_git.php
PHP Warning: array_key_exists() expects parameter 2 to be array, boolean given in /Decompiler4.class.php on line 3321
Apparently zend obfuscated files have an expiry date. This required me to change my system time back to 2008. With that I was able to decrypt the file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function flag()
{
$flag = '814c3aaa6f90c3bf3fd42417ea27b8f16bd8a';
return $flag;
}
function fake_flag()
{
$flag = '814c3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
return $flag;
}
if (isset($_GET['debug']) && (0 < $_GET['debug'])) {
echo flag();
}
else {
echo fake_flag();
}
?>
finalflag10
The site said that if you submit 10 flags you get access to Twardowski’s personal notes. After submitting them I got redirected to this
The note said:
To whom it may concern,
I have always been a security researcher. Recently I felt that I have lost purpose in life - I had no one I could learn from anymore. Curious and bored, I tried to use my skill to cause some chaos, but it didn’t fill the void that I felt in my heart. I contacted some of the best hackers in the world, but it only brought me more loneliness - there was nothing I could learn from these script kiddies. Hopeless, I decided to visit my great grandfather and ask for his advice. I knew I could trust him with my problems. After we talked for many hours, he went to the basement for some time and returned with a small notebook. “This diary belonged to Jan Twardowski, your ancestor. It has been in our family since the ⅩⅥ century. Be careful, though”, he said. “After Jan got what he wanted, he disappeared and nobody heard anything about him ever again”.
After many sleepless nights, I finally knew what the notebook owner was up to. He tried to trick the devil, but failed miserably. However, the most interesting part for me was the beginning - a few pages titled “How to summon the devil”. I realized that there might be a small flaw in the design of the first circle of Inferno, which Jan Twardowski missed. Yesterday my exploit finally worked. I found a backdoor to hell.
By solving this part of my HackMe, you proved your worth to me, but it is not enough yet. If you would like to follow my path, the path of skill and darkness, here is another task:
The picture of the desk was actually a video.
It contained an image of a laptop and two displays, a router with flashing lights and multiple crosswords with Ropsten, twarde krypto (hard crypto) and some 4 bit sequences written on it.
The lights on the router seemed random at first but after seeing the note with 4 bit sequences and that only 4 LEDs changed on the router and that the WiFi diode was lighting up in the same pattern.
To me that meant that it was the “clock” signal and that I should record the state only when it was turned on. The resulting pattern was (in hexadecimal)
1
000ba55f2315338b495652bbec9784af81e1cde39d80000
It wasn’t obvious to me but my teammates noticed that after stripping the zeroes from the start&end this was a valid contract address on ropsten. The contract’s code had an interesting string coded into it
1
2
3
4
5
6
7
8
9
constructor(address next) public {
bytes memory b = "I wanted to keep the message on blockchain so that it's never removed or deleted. However this is public contract so I have to hide the secret message. It's hidden on different contract: 0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
bytes memory hexaddr = toAsciiString(next);
for (uint i = 0; i < 40; i++) {
b[189+i] = hexaddr[i];
}
message = string(b);
}
The message hinted that there might be another contract it created and after checking the transaction history we noticed that there was another contract created.
This one had a smaller but more interesting codebase attached to it
1
2
3
4
5
6
7
8
9
constructor(bytes memory hm) public {
bytes memory b = new bytes(hm.length);
for (uint i = 0; i < hm.length; i++) {
b[i] = byte(hm[i]) ^ byte(0x61);
}
message = string(b);
}
This meant that any transaction sent would have it’s message XORed with 0x61
We can check for transaction input data in their details
1
0x60806040526040518060400160405280601981526020017f486572652077696c6c20626520746865206d65737361676521000000000000008152506000908051906020019061004f929190610206565b5034801561005c57600080fd5b506040516102f73803806102f78339818101604052602081101561007f57600080fd5b810190808051604051939291908464010000000082111561009f57600080fd5b838201915060208201858111156100b557600080fd5b82518660018202830111640100000000821117156100d257600080fd5b8083526020830192505050908051906020019080838360005b838110156101065780820151818401526020810190506100eb565b50505050905090810190601f1680156101335780820380516001836020036101000a031916815260200191505b50604052505050606081516040519080825280601f01601f1916602001820160405280156101705781602001600182028038833980820191505090505b50905060008090505b82518110156101e757606160f81b83828151811061019357fe5b602001015160f81c60f81b188282815181106101ab57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508080600101915050610179565b5080600090805190602001906101fe929190610206565b5050506102ab565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b90565b603e806102b96000396000f3fe6080604052600080fdfea265627a7a7231582037c6770ad8688b28f8b6660a6907761aab109d4dfb64a1a0b6ccb5b7e5dcc8b864736f6c634300051100320000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004e37081208154109040d0d41170800410c18410300020a050e0e135b414e53545751550358035103525557035254040758005859525303075203585057034e0355220a255151334c15514c09520d0d000000000000000000000000000000000000
As the code said, after XORing it with 0x61 I got some random data and a message appended after it:
1
<random data>/Visit hell via my backdoor: /25604b9b0b346b35ef9a9832bf3b916b/b4CkD00R-t0-h3llaaaaaaaaaaaaaaaaaa
After visiting the site I was greeted with a “door to hell” and a cat icon in the corner
The cat icon was a button to activate a chat window. After clicking it I was greeted by “The Devil Himself”
After interacting with the bot a bit i noticed that I can set my name with “my name is
The bot responded that the command is only accessible by his master. So I decided to ask him what is his masters name With this I could set my name to match his masters name and try to access debug again Now it wants a password. I asked the bot for the password and it responded to me that admin1 is definitely not the password. So i tried it
And we’re in.
I tried experimenting with the commands and found out that:
- SHOW STACK shows us the stack
- PUSH pushes a variable onto the stack
- POP grabs the top value from the stack
- GET PREDICATES returns a list of all variables with additional info about them (eg. “name is TWARDOWSKI”)
- TEST PREDICATES returns a list of all questions that would set predicates when answered
- POPOM OM pops a random string that triggers a specific response (“HELL”, “TWARDOWSKI”, “BACKDOOR”)
When playing around with the stack I noticed that when the stack was empty but I still popped values from it random triggers would be put onto it
There was a reoccuring suspicious string that popped up from time to time called “SECRET FLAG PATTERN”
After I sent it to the bot I got a message that I got the flag and gave me a “mailto” link with the flag
finalflag20
To get this flag I had to submit all 20 flags on the main site. After that it redirected me to a page with a “mailto” link to send the flag
Big thanks again to AlphaPwners (kolokokop especially) for playing this CTF with me