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 801

802

To find this flag I had to check cookies that the site created. 802

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 805

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 806 page

After seeing that i looked at the source code of the page 806 source

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: 806

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: 807 functions

The “easy_flag” function was just a decoy, didn’t really contain anything interesting 807 easy flag

The “another_flag” contained some interesting array stuff 807 another flag After cleaning it up a bit in ghidra (retyping the return value, changing the variable name) I saw the individual characters clearly 807 another cleaned

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. 808 source

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 809 git log 809 git changes

And here’s our flag 809 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. 80a git log

I found it as a “test secret” in base.html in “Added icons” 80a flag

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. 80d main

After looking through the resources i found a JS file with a suspicious looking fragment of code 80d 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 80d cyberchef

80e

For this flag there were two ways of getting it. I could either:

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 80e sstv

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. 810 html

I decided to throw it into cyberchef and it returned with a flag 810 cyberchef

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 813 css

After downloading the file, opening it up in stegsolve and browsing the various color planes I found it hiding on the gray bits plane. 813 flag

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 final10 desk

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 final10 door

The cat icon was a button to activate a chat window. After clicking it I was greeted by “The Devil Himself” final10 chat start

After interacting with the bot a bit i noticed that I can set my name with “my name is " and it'd remember it. My teammates also figured out that the bot reacted to the keyword "debug" with a specific message

final10 debug error The bot responded that the command is only accessible by his master. So I decided to ask him what is his masters name final10 chat name With this I could set my name to match his masters name and try to access debug again final10 debug password 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 final10 debug unlocked

And we’re in.

I tried experimenting with the commands and found out that:

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 final10 stack fun

There was a reoccuring suspicious string that popped up from time to time called “SECRET FLAG PATTERN” final10 secret trigger

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 final10 secret final

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