It is always overwhelming to have a number of ctfs going on at the same time. This week was Volga, boot2root and sunshine. I focused more on sunshine especially for the scripting/Programming challenges. CoDiNg is FUN!..

  • Doing a writeup is also challenging!!!

Scripting/Programming Challenges


Time Warp

  • Given
    A Server that asks you to repeat all the frequency (numbers) in order within 30 seconds. Sounds easy but the important part of the challenge is server expects answer before the question.

  • Challenge

40
  • Approach
    We need to do trial and error to know the next correct number and restart the connection from first executing all the numbers in order. After executing the numbers in order within 30 seconds the flag is thrown out!

  • Parts Required

    • PwnTools Frame/Skeleton Code - to connect to the remote server ( I generally use my GIST )
try:
    from pwn import *
except ImportError:
    print "In order to complete this challenge, please install pwntools"
    print "https://pwntools.readthedocs.io/en/stable/install.html"
    sys.exit(1)

def processResponse(data):
    # I guess we should do something with this data and send it back!
    # return processed_data
    return ""

def talk(address, port, key):
    connection = remote(address, port)
    while 1:
        try:
        response = connection.recvuntil(key)
        except:
        connection.interactive()
        print response
        connection.sendline(processResponse(response))

def main():
    try:
        address = sys.argv[1]
        port = sys.argv[2]
        output_key_to_read_until = sys.argv[3]
    except:
        print "Usage: ./client.py [IP] [Port] [Key]"
        sys.exit(1)
    talk(address, port, output_key_to_read_until)

if __name__ == "__main__":
    main()
  • Python Function to perform trial and error to update the list of correct order numbers.
    • Summary of Function
      • Enter a random number (10000) when the next number is not known in numbers list
      • Update the list from the new number received
      • Reconnect and enter the numbers one by one from the list
def talk(address, port, key):
# Hold the sequence of numbers
numbers = []
response = ""

while "sun" not in response:
    print "Starting with " + str(numbers)
    connection = remote(address, port)
    i = len(numbers)
    error = False
    while 1:
        # Receive Logic
        try:
            response = connection.recvuntil("\n")
            response = response.split("\n")
            if "sun" in response[0]:
                print response
                sys.exit()
        except:
            print "End Connection...."
            if "sun" in response:
                print response
                sys.exit()
            print response
            connection.close() 
            print i
            break
        
        print response

        if "Repeat" in response[0] or "going to give you some numbers" not in response[0] or response[0].isdigit():
            if response[0].isdigit():
                if error:
                    numbers.append(response[0])
            else:
                try:
                    # Send Logic
                    if i < len(numbers):
                        print "Sending "+ numbers[i]
                        connection.sendline(numbers[i])
                        i += 1
                    else:
                        print "Sending 10000"
                        error = True
                        connection.sendline("10000")
                except: 
                    print "sendloop!"
  • The above function did not work…! After the list grew above a certain limit, entering numbers one by one increased the time > 30 seconds.

  • The trick was just to send the updated numbers list with commas in the same string num1, num2, num3 .... which speeds up the process rapidly

  • New function with the following change….

def talk2(address, port, key):
    numbers = []
    response = ""
    while "sun" not in response:
        try:
            print "Starting with " + str(numbers)
            connection = remote(address, port)
            print connection.recvline_regex("Repeat.*")
            print "Sending data " + str(" ".join(numbers))
            if numbers:
                connection.sendline(" ".join(numbers))
                response = connection.recvlines(len(numbers)*2)
                print response
            print "Sending " + "10000"
            connection.sendline("10000")
            response = connection.recvuntil("\n")
            numbers.append(response.strip())
            #print connection.recvuntil("\n")
            connection.close()
        except:
            connection.interactive()
  • Solve for the flag, ( Full log at link )
root@kali:~/Downloads# python timewarp.py tw.sunshinectf.org 4101 ""
Starting with []
[+] Opening connection to tw.sunshinectf.org on port 4101: Done
Repeat them back to me in 30 seconds or less!
Sending data 
Sending 10000
Starting with ['39']
[+] Opening connection to tw.sunshinectf.org on port 4101: Done
Repeat them back to me in 30 seconds or less!
Sending data 39
['39', 'G3tting cl0ser!']
Sending 10000
Starting with ['39', '61']
[+] Opening connection to tw.sunshinectf.org on port 4101: Done
Repeat them back to me in 30 seconds or less!
Sending data 39 61
['39', 'Alm0st there!', '61', 'N1ce 0ne! Do it ag4in!']
Sending 10000
Starting with ['39', '61', '267']
[+] Opening connection to tw.sunshinectf.org on port 4101: Done
Repeat them back to me in 30 seconds or less!
Sending data 39 61 267
['39', 'Alm0st there!', '61', 'N1ce 0ne! Do it ag4in!', '267', 'Am4z1ng!']
Sending 10000
Starting with ['39', '61', '267', '475']
[+] Opening connection to tw.sunshinectf.org on port 4101: Done
Repeat them back to me in 30 seconds or less!
Sending data 39 61 267 475
['39', 'Alm0st there!', '61', 'N1ce 0ne! Do it ag4in!', '267', 'Am4z1ng!', '475', 'This is h0nestly impressive!']
Sending 10000
...
...
...
...
w0rking!", '669', 'Icr3dible!', '962', 'Alm0st there!', '715', "It's w0rking!", '750', 'Am4z1ng!', '773', 'Gre4t j0b!', '326', 'Keep it up! The anoma1y is subsiding!', '370', 'Wow! You did it!', "As reward for fixing the timestream, here's the flag:", 'sun{derotser_enilemit_1001130519}']
Sending 10000
[*] Switching to interactive mode
[*] Got EOF while reading in interactive

Entry Exam

  • Challenge
40


  • I liked this challenge a lot. Given a scantron answer sheet and math questions. We had to solve the math problems, mark answers in the sheet and upload them by 2 seconds.

  • Given Scantron sheet looks like this,

40


  • Googling, I found a number of scanners (reading or correcting) for scantron answer sheets but no direct library to fill them in.

  • Thinking… I divided the problem into 3 parts

    1. Reading the webpage with math questions, solving and select the correct choice
      • Fetching the question web page is easy with python urllib or requests
    2. Marking the answers in the answer sheet
      • I referred to this link and found out that we can past a logo above a image using pillow in python
      • The most tricky part, I decided to choose a check mark png and paste it on the answer sheet
      • Fit the check mark exactly to the size of the answer sheet mark
40


  • Find and record the co-ordinates of all the options in the dictionary. I found no good way to do this. So I used MAC screenshot tool to record the co-ordinates of the image and use that with a number of trial and error to fit the answer sheet.
# * Record the coordinates of all the options prior using a dictionary
# * Process answers and use them to create 
# CoOrdinates of the answer sheet in the png
alphabet_option = {"A": 0, "B": 1, "C":2, "D":3, "E":4}
options = {
    "1": [(300, 400),(365, 400),(435, 400),(510, 400), (580, 400)],
    "2": [(300, 500),(365, 500),(435, 500),(510, 500), (580, 500)],
    "3": [(300, 580),(365, 580),(435, 580),(510, 580), (580, 580)],
    "4": [(300, 680),(365, 680),(435, 680),(510, 680), (580, 680)],
    "5": [(300, 770),(365, 770),(435, 770),(510, 770), (580, 770)],
    "6": [(300, 860),(365, 860),(435, 860),(510, 860), (580, 860)],
    "7": [(300, 950),(365, 950),(435, 950),(510, 950), (580, 950)],
    "8": [(300, 1030),(365, 1030),(435, 1030),(510, 1030), (580, 1030)],
    "9": [(300, 1130),(365, 1130),(435, 1130),(510, 1130), (580, 1130)],
    "10": [(300, 1210),(365, 1210),(435, 1210),(510, 1210), (580, 1210)],
    "11": [(785, 400),(855, 400),(925, 400),(990, 400), (1060, 400)],
    "12": [(785, 500),(855, 500),(925, 500),(990, 500), (1060, 500)],
    "13": [(785, 580),(855, 580),(925, 580),(990, 580), (1060, 580)],
    "14": [(785, 680),(855, 680),(925, 680),(990, 680), (1060, 680)],
    "15": [(785, 770),(855, 770),(925, 770),(990, 770), (1060, 770)],
    "16": [(785, 860),(855, 860),(925, 860),(990, 860), (1060, 860)],
    "17": [(785, 950),(855, 950),(925, 950),(990, 950), (1060, 950)],
    "18": [(785, 1030),(855, 1030),(925, 1030),(990, 1030), (1060, 1030)],
    "19": [(785, 1130),(855, 1130),(925, 1130),(990, 1130), (1060, 1130)],
    "20": [(785, 1210),(855, 1210),(925, 1210),(990, 1210), (1060, 1210)]
}
test_version = [(600, 1290), (695, 1290), (800, 1290), (900, 1290)]
  • and test Voila….
# Check Orientation ==> Orient-test file as an example

image_copy = image.copy()
for j in range(1, 21):
    for i in range(0, 5):
        position = options[str(j)][i]
        image_copy.paste(mark, position, mark)
for ord in test_version:
    image_copy.paste(mark, ord, mark)


40


  • They also had a practice test page to test your full code flow.
40


  • CoDe with all the parts. Full Code is at Program with all the parts, orient and practice sections.
import requests
questions = requests.get(practice_url)
cookie = questions.cookies
questions = questions.text
questions = questions.split("\n")
print cookie
section = 1
flag = ""
print questions

while "sun" not in flag:
    # Answer sheet open
    filename = "answer.png"
    
    image = Image.open('scantron.png')
    # Answer mark
    mark = Image.open('checkmark.png').convert("RGBA")
    
    # Extract questions and options
    final_answers = []
    types = ""
    import re
    for i in range(len(questions)):
        if re.match("<li>[0-9]+\s+[+-\/*]+\s+[0-9]+<\/li>", questions[i]):
            expression = questions[i].strip("<li>").strip("</li>")
            ans = eval(expression)
            # type
            i += 1
            i += 1
            # options
            for j in range(0, 4):
                choice = questions[i].strip("<li>").strip("</li>")
                print expression, ans, choice
                if str(ans) == choice:
                    final_answers.append(j)
                    i += 4 - (j+1)
                    break
                else:
                    i += 1
            i += 1

    # Mark Answers: Find and Mark answers in the answer sheet
    image_copy = image.copy()
    for choice in range(len(final_answers)):
        #print choice+1, final_answers[choice]
        position = options[str(choice+1)][final_answers[choice]]
        image_copy.paste(mark, position, mark)
    image_copy.save(filename, format="png")

    # Upload answer sheet to the server
    headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36"}
    files = {'file': open(filename, 'rb')}
    r = requests.post(practice_url, files=files, cookies=cookie, headers=headers)
    cookie = r.cookies
    questions = r.text
    flag = questions
    if "Wrong" in flag or "slow" in flag:
        print "Exited as wrong or slow...."
        sys.exit()
    if "sun" in flag:
        print flag
        sys.exit()
    questions = questions.split("\n")
    print questions
  • One caveat is to maintain the cookies as we progress so that the server knew you were solving the questions section by section. Initially I used urllib and manually copied cookies as I requested –> This worked for practice but eventually for the real challenge I hit a blocker. The script kept running for 15 minutes and solving all the question correct but the questionnaire stayed on section1 only….
    • This was because whenever a correct solution is given, the server sends a 302 redirecting you to the next section and the 302 traffic cookie was lost with urllib with auto redirect.
    • Using requests and a cookie jar was the solution! It worked.
  • Solve for the flag
pyenv) srimbp:entryExam sri$ 
(pyenv) srimbp:entryExam sri$ python solve.py 
<RequestsCookieJar[<Cookie session=.eJyrVkqtSMyNL05NLsnMz1OyMtRRKs7PKQVxipWsoo11DHWMdYx0DIAQxDYA8xAiEHnjWKCuksSikviSzNxUJatqJYUUJSul4NI8HQVjQwXfxCIFIwNDSwVDCysTYysTSwV33xCl2loAh40hMA.XKEKZQ.OjpjMpXPseeSR43Dsj-tYmzKAHI for ee.sunshinectf.org/>]>
[u'<h1>Exam Section 1</h1><ol type="1">', u'<li>107 - 149</li>', u'<ol type="A">', u'<li>10655</li>', u'<li>6163</li>', u'<li>19980</li>', u'<li>-42</li>', u'</ol>', u'<li>124 / 158</li>', u'<ol type="A">', u'<li>3554</li>', u'<li>0</li>', u'<li>15980</li>', u'<li>16919</li>', u'</ol>', u'<li>186 - 197</li>', u'<ol type="A">', u'<li>6951</li>', u'<li>10512</li>', u'<li>12223</li>', u'<li>-11</li>', u'</ol>', u'<li>10 + 56</li>', u'<ol type="A">', u'<li>9559</li>', u'<li>15383</li>', u'<li>66</li>', u'<li>1020</li>', u'</ol>', u'<li>70 + 57</li>', u'<ol type="A">', u'<li>127</li>', u'<li>2994</li>', u'<li>4568</li>', u'<li>3901</li>', u'</ol>', u'<li>71 * 145</li>', u'<ol type="A">', u'<li>10295</li>', u'<li>1130</li>', u'<li>4619</li>', u'<li>14776</li>', u'</ol>', u'<li>165 * 140</li>', u'<ol type="A">', u'<li>11480</li>', u'<li>5981</li>', u'<li>9006</li>', u'<li>23100</li>', u'</ol>', u'<li>78 * 146</li>', u'<ol type="A">', u'<li>13320</li>', u'<li>11388</li>', u'<li>14004</li>', u'<li>15724</li>', u'</ol>', u'<li>132 - 118</li>', u'<ol type="A">', u'<li>14</li>', u'<li>8175</li>', u'<li>5783</li>', u'<li>19104</li>', u'</ol>', u'<li>36 / 60</li>', u'<ol type="A">', u'<li>18882</li>', u'<li>6155</li>', u'<li>10213</li>', u'<li>0</li>', u'</ol>', u'<li>181 - 175</li>', u'<ol type="A">', u'<li>8078</li>', u'<li>1182</li>', u'<li>6</li>', u'<li>19012</li>', u'</ol>', u'<li>50 * 184</li>', u'<ol type="A">', u'<li>1520</li>', u'<li>3190</li>', u'<li>2496</li>', u'<li>9200</li>', u'</ol>', u'<li>182 / 115</li>', u'<ol type="A">', u'<li>3916</li>', u'<li>15448</li>', u'<li>1</li>', u'<li>11082</li>', u'</ol>', u'<li>135 + 171</li>', u'<ol type="A">', u'<li>306</li>', u'<li>8650</li>', u'<li>19816</li>', u'<li>11088</li>', u'</ol>', u'<li>68 + 47</li>', u'<ol type="A">', u'<li>115</li>', u'<li>4009</li>', u'<li>4931</li>', u'<li>16555</li>', u'</ol>', u'<li>47 / 48</li>', u'<ol type="A">', u'<li>10376</li>', u'<li>15549</li>', u'<li>8852</li>', u'<li>0</li>', u'</ol>', u'<li>136 * 94</li>', u'<ol type="A">', u'<li>3963</li>', u'<li>1943</li>', u'<li>5040</li>', u'<li>12784</li>', u'</ol>', u'<li>91 / 30</li>', u'<ol type="A">', u'<li>12812</li>', u'<li>3</li>', u'<li>17658</li>', u'<li>13479</li>', u'</ol>', u'<li>63 + 135</li>', u'<ol type="A">', u'<li>4777</li>', u'<li>1935</li>', u'<li>4410</li>', u'<li>198</li>', u'</ol>', u'<li>54 - 64</li>', u'<ol type="A">', u'<li>16951</li>', u'<li>14890</li>', u'<li>15695</li>', u'<li>-10</li>', u'</ol>', u'</ol>', u'', u'   <h1>Upload Solution</h1>', u'    <form method=post enctype=multipart/form-data>', u'      <input type=file name=file>', u'      <input type=submit value=Upload>', u'    </form>', u'    ']
107 - 149 -42 10655
107 - 149 -42 6163
107 - 149 -42 19980
107 - 149 -42 -42
124 / 158 0 3554
124 / 158 0 0
186 - 197 -11 6951
186 - 197 -11 10512
186 - 197 -11 12223
186 - 197 -11 -11
10 + 56 66 9559
10 + 56 66 15383
10 + 56 66 66
70 + 57 127 127
71 * 145 10295 10295
165 * 140 23100 11480
165 * 140 23100 5981
165 * 140 23100 9006
165 * 140 23100 23100
78 * 146 11388 13320
78 * 146 11388 11388
132 - 118 14 14
36 / 60 0 18882
36 / 60 0 6155
36 / 60 0 10213
36 / 60 0 0
181 - 175 6 8078
181 - 175 6 1182
181 - 175 6 6
50 * 184 9200 1520
50 * 184 9200 3190
50 * 184 9200 2496
50 * 184 9200 9200
182 / 115 1 3916
182 / 115 1 15448
182 / 115 1 1
135 + 171 306 306
68 + 47 115 115
47 / 48 0 10376
47 / 48 0 15549
47 / 48 0 8852
47 / 48 0 0
136 * 94 12784 3963
136 * 94 12784 1943
136 * 94 12784 5040
136 * 94 12784 12784
91 / 30 3 12812
91 / 30 3 3
63 + 135 198 4777
63 + 135 198 1935
63 + 135 198 4410
63 + 135 198 198
54 - 64 -10 16951
54 - 64 -10 14890
54 - 64 -10 15695
54 - 64 -10 -10
...
...
...
[u'<h1>Exam Section 9</h1><ol type="1">', u'<li>108 + 27</li>', u'<ol type="A">', u'<li>3889</li>', u'<li>4528</li>', u'<li>3205</li>', u'<li>135</li>', u'</ol>', u'<li>190 / 135</li>', u'<ol type="A">', u'<li>8169</li>', u'<li>11767</li>', u'<li>1</li>', u'<li>3929</li>', u'</ol>', u'<li>172 - 81</li>', u'<ol type="A">', u'<li>91</li>', u'<li>5436</li>', u'<li>6273</li>', u'<li>9824</li>', u'</ol>', u'<li>108 / 69</li>', u'<ol type="A">', u'<li>16763</li>', u'<li>6194</li>', u'<li>1</li>', u'<li>3952</li>', u'</ol>', u'<li>180 + 79</li>', u'<ol type="A">', u'<li>7420</li>', u'<li>10758</li>', u'<li>15017</li>', u'<li>259</li>', u'</ol>', u'<li>44 - 24</li>', u'<ol type="A">', u'<li>18672</li>', u'<li>20</li>', u'<li>13581</li>', u'<li>6724</li>', u'</ol>', u'<li>71 + 133</li>', u'<ol type="A">', u'<li>18198</li>', u'<li>18153</li>', u'<li>9016</li>', u'<li>204</li>', u'</ol>', u'<li>183 * 110</li>', u'<ol type="A">', u'<li>18133</li>', u'<li>9896</li>', u'<li>20130</li>', u'<li>11306</li>', u'</ol>', u'<li>4 / 155</li>', u'<ol type="A">', u'<li>4050</li>', u'<li>0</li>', u'<li>3189</li>', u'<li>13122</li>', u'</ol>', u'<li>166 * 39</li>', u'<ol type="A">', u'<li>16922</li>', u'<li>6474</li>', u'<li>11370</li>', u'<li>17554</li>', u'</ol>', u'<li>81 - 93</li>', u'<ol type="A">', u'<li>-12</li>', u'<li>6104</li>', u'<li>4967</li>', u'<li>18335</li>', u'</ol>', u'<li>80 + 17</li>', u'<ol type="A">', u'<li>97</li>', u'<li>15457</li>', u'<li>12846</li>', u'<li>17878</li>', u'</ol>', u'<li>158 / 174</li>', u'<ol type="A">', u'<li>10000</li>', u'<li>17044</li>', u'<li>4596</li>', u'<li>0</li>', u'</ol>', u'<li>169 + 30</li>', u'<ol type="A">', u'<li>199</li>', u'<li>10654</li>', u'<li>16139</li>', u'<li>13182</li>', u'</ol>', u'<li>168 - 93</li>', u'<ol type="A">', u'<li>75</li>', u'<li>7792</li>', u'<li>9714</li>', u'<li>9717</li>', u'</ol>', u'<li>19 - 42</li>', u'<ol type="A">', u'<li>2133</li>', u'<li>15049</li>', u'<li>-23</li>', u'<li>207</li>', u'</ol>', u'<li>70 + 192</li>', u'<ol type="A">', u'<li>9441</li>', u'<li>3435</li>', u'<li>11287</li>', u'<li>262</li>', u'</ol>', u'<li>1 - 34</li>', u'<ol type="A">', u'<li>15624</li>', u'<li>-33</li>', u'<li>17865</li>', u'<li>18702</li>', u'</ol>', u'<li>58 + 54</li>', u'<ol type="A">', u'<li>112</li>', u'<li>5626</li>', u'<li>4288</li>', u'<li>13382</li>', u'</ol>', u'<li>144 - 120</li>', u'<ol type="A">', u'<li>24</li>', u'<li>907</li>', u'<li>6762</li>', u'<li>720</li>', u'</ol>', u'</ol>', u'', u'   <h1>Upload Solution</h1>', u'    <form method=post enctype=multipart/form-data>', u'      <input type=file name=file>', u'      <input type=submit value=Upload>', u'    </form>', u'    ']
108 + 27 135 3889
108 + 27 135 4528
108 + 27 135 3205
108 + 27 135 135
190 / 135 1 8169
190 / 135 1 11767
190 / 135 1 1
172 - 81 91 91
108 / 69 1 16763
108 / 69 1 6194
108 / 69 1 1
180 + 79 259 7420
180 + 79 259 10758
180 + 79 259 15017
180 + 79 259 259
44 - 24 20 18672
44 - 24 20 20
71 + 133 204 18198
71 + 133 204 18153
71 + 133 204 9016
71 + 133 204 204
183 * 110 20130 18133
183 * 110 20130 9896
183 * 110 20130 20130
4 / 155 0 4050
4 / 155 0 0
166 * 39 6474 16922
166 * 39 6474 6474
81 - 93 -12 -12
80 + 17 97 97
158 / 174 0 10000
158 / 174 0 17044
158 / 174 0 4596
158 / 174 0 0
169 + 30 199 199
168 - 93 75 75
19 - 42 -23 2133
19 - 42 -23 15049
19 - 42 -23 -23
70 + 192 262 9441
70 + 192 262 3435
70 + 192 262 11287
70 + 192 262 262
1 - 34 -33 15624
1 - 34 -33 -33
58 + 54 112 112
144 - 120 24 24
sun{7h3_b357_7h3r3_15_7h3_b357_7h3r3_w45_7h3_b357_7h3r3_3v3r_w1ll_b3}

Cryptography

CB1

  • This was a interesting kind of cool challenge.

  • Challenge

40


  • Given: A wav(audio) file that recites a code –> Its NATO Phonetic Alphabet

  • This makes it clear from (link)[https://www.sporcle.com/blog/2017/12/what-is-the-nato-phonetic-alphabet/]

  • Refer the table for Nato Phonetics table and perform the conversion


  • Still we dont get the flag. But when the recitation happens there is a code word at the start. Code word 6 –> Probably Caeser Shift (+6 or -6)?? Yes it is…

  • Code
    ciphertext = "hkcgxkznkojkyulsgxin"
    ciphertext = ciphertext.upper()
    for char in ciphertext:
       limit = ord("A")
       limit2 = ord("Z")+1
       shift = ord(char)-6
       if shift < limit:
          shift = limit-shift
          print chr(limit2-shift),
       else:
          print chr(shift),
    
  • References
* https://cryptii.com/pipes/nato-phonetic-alphabet
* https://www.sporcle.com/blog/2017/12/what-is-the-nato-phonetic-alphabet/
* https://en.wikipedia.org/wiki/NATO_phonetic_alphabet
  • Solve for Flag
(pyenv) srimbp:CB1 sri$ python solve_shift.py 
B E W A R E T H E I D E S O F M A R C H