bajok
~bajok
Cashmere makes perfect better BTW I USE ARCH

CTF - Space Heroes

Categories: Crypto, Forensics, Web | Tags: CTF


CTF Overview


Name: Space Heroes CTF
Event URL: https://ctftime.org/event/1567
Challenges source: https://github.com/tj-oconnor/spaceheroes_ctf
Flag Format: shctf{flag}


Crypto


Mobile Infantry

nc 0.cloud.chals.io 27602
Author: v10l3nt

The only information provided was the address of the game server. Upon connection, the following prompt was displayed.

$ nc 0.cloud.chals.io 27602
Welcome to Ricos Roughnecks. We use 1-time-pads to keep all our secrets safe from the Arachnids.
Here in the mobile infantry, we also implement some stronger roughneck checks.

Enter pad >

As requested, the sample payload has been sent to the service.

Enter pad > A

[!]- Failed pad strength validation. Please use a stronger pad to keep the mobile infantry safe.

------------------------------------------
def len_check(pad):
    if len(pad) != 38:
        return False
    return True
------------------------------------------

The service responded with an error and indicated that it was expecting a 38-character long payload.

Enter pad > AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

[!]- Failed pad strength validation. Please use a stronger pad to keep the mobile infantry safe.

------------------------------------------
def check2(pad):
    for i in range(int(len(pad)/2)+1, len(pad)):
        if not pad[i].islower():
            return False
    return True
------------------------------------------

Another hint was returned to the output. Positions from 20 to 38 should contain lowercase characters.

Enter pad > AAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaa

[!]- Failed pad strength validation. Please use a stronger pad to keep the mobile infantry safe.

------------------------------------------
def check3(pad):
    for i in range(0, int(len(pad)/2)):
        if ord(pad[i]) != ord(pad[i+1])-1:
            return False
    return True
------------------------------------------

In addition, for each of the first 20 characters the decimal value of character should be equal to the value of the next character reduced by 1.

Enter pad > ABCDEFGHIJKLMNOPQRSTaaaaaaaaaaaaaaaaaa

[!]- Failed pad strength validation. Please use a stronger pad to keep the mobile infantry safe.

------------------------------------------
def check4(pad):
    for i in range(int(len(pad)/2)+1, len(pad)-1):
        if ord(pad[i]) != ord(pad[i+1])+1:
            return False
    return True
------------------------------------------

Moreover, for each of the last 18 characters the decimal value of character should be equal to the value of the next character incremented by 1.

Enter pad > ABCDEFGHIJKLMNOPQRSTzyxwvutsrqponmlkji
[+] Welcome the mobile infantry, keep fighting.

No additional hint was returned, but the payload used was not the only valid one. There were 63 (7 * 9) possible payloads that satisfied the challenge conditions. The following script was used to brute-force the payload.

 1#!/bin/python3
 2from pwn import *
 3
 4for i in range(7):
 5    for j in range(9):
 6        payload = string.ascii_uppercase[i:20] + string.ascii_uppercase[20:20+i] + string.ascii_lowercase[::-1][j:18] + string.ascii_lowercase[::-1][18:18+j]
 7
 8        s = remote('0.cloud.chals.io', 27602, level='error')
 9        s.readuntil(b'Enter pad >')
10        s.writeline(payload)
11
12        line = s.recvline().decode()
13        if "keep fighting" not in line:
14            print("Payload: " + payload + "\n" + line)
15            exit()

Execution of the script returns the flag.

$ ./mobile_infantry.py
Payload: EFGHIJKLMNOPQRSTUVWXxwvutsrqponmlkjihg
 [+] The fight is over, here is your flag: shctf{Th3-On1Y-G00d-BUg-I$-A-deAd-BuG}

Forensics


Netflix and CTF

I don’t like watching anything other than TV shows about Space Heroes.
Author: v10l3nt

A netflix-and-ctf.pcap file was attached to the challenge. After inspecting the file with Wireshark, it could be noted that the intercepted communication mostly consisted of HTTP traffic. An example HTTP request/response is shown below.

POST /keypress/Lit_T HTTP/1.1
Host: 10.10.100.124:8060
User-Agent: python-requests/2.27.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 0

HTTP/1.1 202 Accepted
Server: Roku/10.5.0 UPnP/1.0 Roku/10.5.0
Content-Length: 0

The most striking distinction between the individual requests was that the URI paths differed only in the last letter. The following command was used to extract the letters from the .pcap file.

$ tshark -r netflix-and-ctf.pcap -T fields -e http.request.uri | cut -d'_' -f 2 | tr -d '\n'
/search/browseTreasure+Planet/search/browseDune/search/browseVoltron+in+Space/search/browseTreasure+Planet/search/browseDune/search/browseHighlander/search/browseHighlander/search/browseVoltron+in+Space/search/browseDune/search/browseStar+Trek/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseStar+Wars/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseStar+Trek/search/browseDune/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseBattlestar+Galatica/search/browseStar+Wars/search/browseStar+Trek/search/browseBattlestar+Galatica/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseBattlestar+Galatica/search/browseDune/search/browseDune/search/browseVoltron+in+Space/search/browseHighlander/search/browseTreasure+Planet/search/browseHighlander/search/browseStar+Trek/search/browseStar+Trek/search/browseTreasure+Planet/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseStar+Wars/search/browseBattlestar+Galatica/search/browseStar+Trek/search/browseBattlestar+Galatica/search/browseTreasure+Planet/search/browseshctf%7BT1m3-is-th3-ultimat3-curr3Ncy%7D/search/browseVoltron+in+Space/search/browseVoltron+in+Space/search/browseVoltron+in+Space

The output showed that the letters formed movie titles. In addition, each title that was searched was preceded by the request to the /search/browse location. The string “shctf” could be seen between the lines as well as what looked like URL encoding. Use Following command was used to parse the output and obtain a decoded flag.

$ tshark -r netflix-and-ctf.pcap -T fields -e http.request.uri | cut -d'_' -f 2 | tr -d '\n' | sed 's/\/search\/browse/\n/g' | grep shctf | uniq |  python3 -c "import sys; from urllib.parse import unquote; print(unquote(sys.stdin.read()));"
shctf{T1m3-is-th3-ultimat3-curr3Ncy}

Star Pcap

Author: v10l3nt

A star.pcap file was attached to the challenge. The intercepted communication consisted of dozens of ICMP packets. The only distinction between the individual packets was their ICMP code field. Moreover, all packets were of type 0 - Echo reply, which should only use code 0. Following command was used to extract all ICMP code fields from the .pcap file.

$ tshark -r star.pcap -T fields -e icmp.code | tr '\n' ' '
99 50 104 106 100 71 90 55 84 68 66 110 77 87 77 116 97 83 81 116 100 71 103 122 76 87 74 108 90 50 108 79 84 109 108 117 90 121 48 119 90 105 49 51 97 83 82 107 98 48 49 57

All numbers returned by the command were in range of the ASCII table. Converting the output to ASCII returned what looked like a base64 encoded string.

$ tshark -r star.pcap -T fields -e icmp.code | xargs -I{} sh -c 'printf "\\$(printf %o {})"'
c2hjdGZ7TDBnMWMtaSQtdGgzLWJlZ2lOTmluZy0wZi13aSRkb019

Decoding the string resulted in obtaining the flag.

$ tshark -r star.pcap -T fields -e icmp.code | xargs -I{} sh -c 'printf "\\$(printf %o {})"' | base64 -d
shctf{L0g1c-i$-th3-begiNNing-0f-wi$doM}

Web


Mysterious Broadcast

There used to be 8 Models of humanoid cylon but now there are only 7. We’ve located one of their broadcast nodes but we can’t decode it. Are you able to decipher their technologies? http://173.230.134.127
Author: blakato

The only information provided was the application URL. Upon connection, the HTTP 302 response was returned with the user was redirected to the following location: /seq/a45feca6-f1eb-4b20-a840-b849c7f6b974. The landing page contained only a single ‘~’ character and after reloading the page, that character was being replaced by either ‘1’ or ‘0’ on subsequent refreshes. The entire process is shown below.

$ curl http://173.230.134.127 -LI
HTTP/1.1 302 FOUND
Server: Werkzeug/2.1.1 Python/3.10.4
Date: Wed, 06 Apr 2022 19:20:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 288
Location: /seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a

HTTP/1.1 200 OK
Server: Werkzeug/2.1.1 Python/3.10.4
Date: Wed, 06 Apr 2022 19:20:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1

~

$ curl http://173.230.134.127/seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a
1

$ curl http://173.230.134.127/seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a
0

$ curl http://173.230.134.127/seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a
1

$ curl http://173.230.134.127/seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a
0

$ curl http://173.230.134.127/seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a
1

$ curl http://173.230.134.127/seq/4c1b6e5d-c24e-49ec-866f-7fd6b048b02a
1

Repeating this process resulted in the user being redirected to a different location, but the sequence of characters was the same. At this point it was safe to assume that the ones and zeroes would form a binary encoded message. The following script was used to obtain the payload.

 1#!/bin/python3
 2import requests
 3
 4binary = ''
 5url = "http://173.230.134.127"
 6
 7r = requests.get(url, allow_redirects=False)
 8location = r.headers['Location']
 9requests.get(url + location)
10
11while True:
12    r = requests.get(url + location)
13    if r.content.decode() == '~': break
14    binary += r.content.decode()
15
16ciphertext = [binary[i:i+8] for i in range(0, len(binary), 8)]
17
18flag = ''
19for char in ciphertext:
20    flag += chr(int(char, base = 2))
21
22print(flag)

Execution of the script returned the decoded binary payload, however the output did not seem to be correct.

$ ./mysterious_broadcast.py
¯]ËzaoÄɯlz{çÉFÉ1çvÉbS
                      {\öÅN
                           ~ahwçp³·ÆÉþapŦ
                                          £5g{ksµbÖ[gpµbn|Ú°Ã_¾|ªç³bVÉ1ülµº|¶0Ã2!1Ƕ·2!yÄÊè...

It was helpful to notice that the challenge description included a hint - “There used to be 8 Models of humanoid cylon but now there are only 7.”.
The sentence was referring to the byte length, which is commonly 8. After changing the script to split the payload into bytes of size 7 and re-executing it, the following output was returned.

$ ./mysterious_broadcast.py
WW91ciBob25vciwgdGhlIGNvdXJ0cm9vbSBpcyBhIGNydWNpYmxlOyBpbiBpdCwgd2UgYnVybiBhd2F5IGlycmVsZXZhbmNpZXMgdW50aWwgd2UgYXJlIGxlZnQgd2l0aCBhIHB1cmVyIHByb2R1Y3Q6IHRoZSB0cnV0aCwgZm9yIGFsbCB0aW1lLiBOb3cgc29vbmVyIG9yIGxhdGVyLCB0aGlzIG1hbiDigJMgb3Igb3RoZXJzIGxpa2UgaGltIOKAkyB3aWxsIHN1Y2NlZWQgaW4gcmVwbGljYXRpbmcgQ29tbWFuZGVyIERhdGEuIFRoZSBkZWNpc2lvbiB5b3UgcmVhY2ggaGVyZSB0b2RheSB3aWxsIGRldGVybWluZSBob3cgd2Ugd2lsbCByZWdhcmQgdGhpcyBjcmVhdGlvbiBvZiBvdXIgZ2VuaXVzLiBJdCB3aWxsIHJldmVhbCB0aGUga2luZCBvZiBwZW9wbGUgd2UgYXJlOyB3aGF0IGhlIGlzIGRlc3RpbmVkIHRvIGJlLiBJdCB3aWxsIHJlYWNoIGZhciBiZXlvbmQgdGhpcyBjb3VydHJvb20gYW5kIHRoaXMgb25lIGFuZHJvaWQuIEl0IGNvdWxkIHNpZ25pZmljYW50bHkgcmVkZWZpbmUgdGhlIGJvdW5kYXJpZXMgb2YgcGVyc29uYWwgbGliZXJ0eSBhbmQgZnJlZWRvbTogZXhwYW5kaW5nIHRoZW0gZm9yIHNvbWUsIHNhdmFnZWx5IGN1cnRhaWxpbmcgdGhlbSBmb3Igb3RoZXJzLiBBcmUgeW91IHByZXBhcmVkIHRvIGNvbmRlbW4gaGltIOKAkyBhbmQgYWxsIHdobyB3aWxsIGNvbWUgYWZ0ZXIgaGltIOKAkyB0byBzZXJ2aXR1ZGUgYW5kIHNsYXZlcnk/IFlvdXIgaG9ub3IsIFN0YXJmbGVldCB3YXMgZm91bmRlZCB0byBzZWVrIG91dCBuZXcgbGlmZTogd2VsbCwgdGhlcmUgaXQgc2l0cyEgV2FpdGluZy4gV2l0aCBhIGZsYWchCgoKCnNoY3Rme3dIYTdtQUszNUFNYW59Cgo=

The output resembled a base64 encoded string. Decoding the output resulted in obtaining the flag.

$ ./mysterious_broadcast.py | base64 -d
Your honor, the courtroom is a crucible; in it, we burn away irrelevancies until we are left with a purer product: the truth, for all time. Now sooner or later, this man – or others like him – will succeed in replicating Commander Data. The decision you reach here today will determine how we will regard this creation of our genius. It will reveal the kind of people we are; what he is destined to be. It will reach far beyond this courtroom and this one android. It could significantly redefine the boundaries of personal liberty and freedom: expanding them for some, savagely curtailing them for others. Are you prepared to condemn him – and all who will come after him – to servitude and slavery? Your honor, Starfleet was founded to seek out new life: well, there it sits! Waiting. With a flag!

shctf{wHa7mAK35AMan}

Space Buds

One of the puppies got into the web server. Can you help find out who it was? 45.79.204.27 Author: Lil Tipo

Besides the application URL there was also a “Space Buddies” movie poster attached to the challenge. Upon connection, the server responded with a page that contained a “Login” button, which when pressed made a POST request to the /getcookie location. Shown below is the server’s response to the first request that was made.

$ curl http://45.79.204.27 -I
HTTP/1.1 200 OK
Server: Werkzeug/2.1.1 Python/3.10.4
Date: Sun, 10 Apr 2022 19:49:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 483
Set-Cookie: userID=whoami; Path=/

Out of response headers the “Set-Cookie: userID=whoami” was particularly notable and the cookie name “userID” indicated that it was used for storing user’s identifier. Searching for information about the “Space Buddies” movie poster resulted in obtaining information about the movie’s main characters names which were: Sputnik, Budderball, Buddha, B-dawg, Mudbud and Rosebud.
Following script was used to test if the server would respond differently to any of these names being set as a value of the userID cookie.

 1#!/bin/python3
 2import requests
 3
 4names = ['Sputnik', 'Budderball', 'Buddha', 'B-dawg', 'Mudbud', 'Rosebud']
 5url = "http://45.79.204.27/getcookie"
 6response = requests.post(url).content.decode()
 7
 8for name in names:
 9    cookies = {'userID': name}
10    r = requests.post(url, cookies=cookies).content.decode()
11    if r != response:
12        print(cookies)
13        print(r)

Execution of the script returned the different response for the cookie value set to “Mudbud”.

$ python3 space_buddies.py
{'userID': 'Mudbud'}
<head>
    <title>Winner! Winner! Cookie Dinner!</title>
</head>

<body style="background-color:black">
        <center>
                <img src="../static/images/spacecookie.jpg" style="width:1000px; height:1000px;">
        </center>
</body>

The reponse body contained a spacecookie.jpg image in which the flag was visible.


Space Traveler

Explore space with us. https://spaceheroes-web-explore.chals.io

Upon connection, the server responded with a button, which prompted the user to guess the flag when pressed. In addition, there was a script tag present in the page source which contained the following code:

var _0xb645=["\x47\x75\x65\x73\x73\x20\x54\x68\x65\x20\x46\x6C\x61\x67","\x73\x68\x63\x74\x66\x7B\x66\x6C\x61\x67\x7D","\x59\x6F\x75\x20\x67\x75\x65\x73\x73\x65\x64\x20\x72\x69\x67\x68\x74\x2E","\x73\x68\x63\x74\x66\x7B\x65\x69\x67\x68\x74\x79\x5F\x73\x65\x76\x65\x6E\x5F\x74\x68\x6F\x75\x73\x61\x6E\x64\x5F\x6D\x69\x6C\x6C\x69\x6F\x6E\x5F\x73\x75\x6E\x73\x7D","\x59\x6F\x75\x20\x67\x75\x65\x73\x73\x65\x64\x20\x77\x72\x6F\x6E\x67\x2E","\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C","\x64\x65\x6D\x6F","\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64"];function myFunction(){let _0xb729x2;let _0xb729x3=prompt(_0xb645[0],_0xb645[1]);switch(_0xb729x3){case _0xb645[3]:_0xb729x2= _0xb645[2];break;default:_0xb729x2= _0xb645[4]};document[_0xb645[7]](_0xb645[6])[_0xb645[5]]= _0xb729x2}

The code contained hex-encoded strings. The following script was used to parse the page source.

1#!/bin/python3
2import requests
3from bs4 import BeautifulSoup
4
5r = requests.get("https://spaceheroes-web-explore.chals.io")
6soup = BeautifulSoup(r.content.decode(), 'html.parser')
7strings = soup.find('script').text.split(';')[0].split('=')[1]
8print(eval(strings))

Executing the script returned the flag.

$ python3 space_traveller.py
['Guess The Flag', 'shctf{flag}', 'You guessed right.', 'shctf{eighty_seven_thousand_million_suns}', 'You guessed wrong.', 'innerHTML', 'demo', 'getElementById']

Flag in Space

“The exploration of space will go ahead whether we join in it or not.” - John F. Kennedy http://172.105.154.14/?flag=
Author: v10l3nt

Upon connection, the user was presented with a black grid. The challenge url contained an empty parameter “flag”. Requesting the page again using the flag=shctf parameter resulted in those letters being displayed inside first 5 grid fields. However, when the flag parameter was set to “aaaaf” only the “f” letter was displayed in the fifth grid field. At this point it could have been assummed that the letters were displayed in the grid only if the letter was in correct position. The following script was used to brute-force the flag.

 1#!/bin/python3
 2import requests
 3from bs4 import BeautifulSoup
 4import string
 5
 6flag = ""
 7while True:
 8    for x in range(len(string.printable)):
 9        param = flag + string.printable[x]
10        r = requests.get("http://172.105.154.14/?flag=" + param)
11        soup = BeautifulSoup(r.content.decode(), 'html.parser')
12        if soup.find('div').text.replace('\n','') == param:
13            flag = param
14            if flag[-1:] == '}':
15                print(flag)
16                exit()

Executing the script returned the flag.

$ python3 flag_in_space.py
shctf{2_explor3_fronti3r}