aboutsummaryrefslogtreecommitdiffstats
path: root/checker/src/checker.py
blob: 145cc6094dafcd328942b92d2cf692b7b4908d36 (plain) (blame)
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/env python3
from enochecker import BaseChecker, BrokenServiceException, EnoException, run
from enochecker.utils import SimpleSocket, assert_equals, assert_in
import random
import string

#### Checker Tenets
# A checker SHOULD not be easily identified by the examination of network traffic
#    => satisfied, because checker uses regular user interface and picks strings from a wordlist
#       to appear more human (TODO)
# A checker SHOULD use unusual, incorrect or pseudomalicious input to detect network filters
#    => satisfied, send various garbage bytes for model name and file contents (TODO)
####

samplestl = """
solid
	facet normal 1.0 0 0
		outer loop
			vertex 0 1 0
			vertex 0 1 1
			vertex 0 0 1
		endloop
	endfacet
	facet normal 0 0 1.0
		outer loop
			vertex 1 0 0
			vertex 1 1 0
			vertex 0 1 0
		endloop
	endfacet
endsolid
"""

class STLDoctorChecker(BaseChecker):
    """
    Change the methods given here, then simply create the class and .run() it.
    Magic.
    A few convenient methods and helpers are provided in the BaseChecker.
    ensure_bytes and ensure_unicode to make sure strings are always equal.
    As well as methods:
    self.connect() connects to the remote server.
    self.get and self.post request from http.
    self.chain_db is a dict that stores its contents to a mongodb or filesystem.
    conn.readline_expect(): fails if it's not read correctly
    To read the whole docu and find more goodies, run python -m pydoc enochecker
    (Or read the source, Luke)
    """

    ##### EDIT YOUR CHECKER PARAMETERS
    flag_variants = 1
    noise_variants = 1
    havoc_variants = 0
    service_name = "stldoctor"
    port = 9000
    ##### END CHECKER PARAMETERS

    def login_user(self, conn: SimpleSocket, password: str):
        self.debug("Sending command to login.")
        conn.write(f"login\n{password}\n")
        conn.readline_expect(b"logged in!", read_until=b"$", exception_message="Failed to log in")

    def generate_file(self, filetype: str = "ascii", extra: str = ""):
        if filetype == "ascii":
            # TODO handle extra as solidname and gen randomly
            return samplestl
        elif filetype == "bin":
            # TODO handle extra as header
            return samplestl # TODO: this is not a binary STL!
        else:
            raise EnoException("Invalid file type supplied");

    def putfile(self, conn: SimpleSocket, solidname = "TODO", modelname = "TODO"):
        # Generate file contents
        stlfile = samplestl

        # Upload file
        self.debug("Sending command to submit file")
        conn.write(f"submit\n{len(stlfile)}\n{stlfile}{modelname}\n")
        conn.read_until(b"with ID ")

        # Parse ID
        fileid = conn.read_until(b"!")
        if fileid == b"":
            raise BrokenServiceException("Unable to upload file!")
        self.debug(f"Got ID {fileid}")

        conn.read_until(b"$")

        return stlfile, fileid

    def getfile(self, conn: SimpleSocket, modelname: str):
        if modelname != "":
            self.debug(f"Sending command to retrieve file with '{modelname}'")
            conn.write(f"query\n{modelname}\n0\ny\n")
        else:
            self.debug(f"Sending command to retrieve file")
            conn.write(f"query\n0\ny\n")

        resp = conn.read_until(b"$")

        return resp

    def querydb(self, *args):
        self.debug("Querying db contents");
        vals = []
        for arg in args:
            try:
                val: str = self.chain_db[arg]
            except IndexError as ex:
                raise BrokenServiceException("Invalid db contents")
            vals.append(val)
        return vals

    def postdb(self, vdict):
        self.chain_db = vdict

    def openconn(self):
        self.debug("Connecting to service")
        conn = self.connect()
        conn.read_until("$") # ignore welcome
        return conn

    def closeconn(self, conn: SimpleSocket):
        self.debug("Sending exit command")
        conn.write("exit\n")
        conn.close()

    def putflag(self):  # type: () -> None
        """
        This method stores a flag in the service.
        In case multiple flags are provided, self.variant_id gives the appropriate index.
        The flag itself can be retrieved from self.flag.
        On error, raise an Eno Exception.
        :raises EnoException on error
        :return this function can return a result if it wants
                if nothing is returned, the service status is considered okay.
                the preferred way to report errors in the service is by raising an appropriate enoexception
        """
        if self.variant_id == 0:
            conn = self.openconn()
            modelname = self.flag
            stlfile, fileid = self.putfile(conn, modelname = modelname)
            self.closeconn(conn)
            self.chain_db = { "fileid": fileid, "modelname": modelname }
        else:
            raise EnoException("Wrong variant_id provided")

    def getflag(self):  # type: () -> None
        """
        This method retrieves a flag from the service.
        Use self.flag to get the flag that needs to be recovered and self.round to get the round the flag was placed in.
        On error, raise an EnoException.
        :raises EnoException on error
        :return this function can return a result if it wants
                if nothing is returned, the service status is considered okay.
                the preferred way to report errors in the service is by raising an appropriate enoexception
        """
        if self.variant_id == 0:
            fileid, modelname = self.querydb("fileid", "modelname")
            conn = self.openconn()
            resp = self.getfile(conn, modelname)
            assert_in(modelname.encode(), resp, "Resulting flag was found to be incorrect")
            self.closeconn(conn)
        else:
            raise EnoException("Wrong variant_id provided")


    def putnoise(self):  # type: () -> None
        """
        This method stores noise in the service. The noise should later be recoverable.
        The difference between noise and flag is, that noise does not have to remain secret for other teams.
        This method can be called many times per round. Check how often using self.variant_id.
        On error, raise an EnoException.
        :raises EnoException on error
        :return this function can return a result if it wants
                if nothing is returned, the service status is considered okay.
                the preferred way to report errors in the service is by raising an appropriate enoexception
        """
        if self.variant_id == 0:
            conn = self.openconn()
            modelname = "NOISE" # TODO
            contents, fileid = self.putfile(conn, modelname = modelname)
            self.closeconn(conn)
            self.postdb({ "fileid": fileid, "modelname": modelname, "contents": contents })
        else:
            raise EnoException("Wrong variant_id provided")

    def getnoise(self):  # type: () -> None
        """
        This method retrieves noise in the service.
        The noise to be retrieved is inside self.flag
        The difference between noise and flag is, that noise does not have to remain secret for other teams.
        This method can be called many times per round. Check how often using variant_id.
        On error, raise an EnoException.
        :raises EnoException on error
        :return this function can return a result if it wants
                if nothing is returned, the service status is considered okay.
                the preferred way to report errors in the service is by raising an appropriate enoexception
        """
        if self.variant_id == 0:
            fileid, modelname, contents = self.querydb("fileid", "modelname", "contents")
            conn = self.openconn()
            resp = self.getfile(conn, modelname)
            assert_in(contents.encode(), resp, "Noise file content was found to be incorrect")
            self.closeconn(conn)
        else:
            raise EnoException("Wrong variant_id provided")

    def havoc(self):  # type: () -> None
        """
        This method unleashes havoc on the app -> Do whatever you must to prove the service still works. Or not.
        On error, raise an EnoException.
        :raises EnoException on Error
        :return This function can return a result if it wants
                If nothing is returned, the service status is considered okay.
                The preferred way to report Errors in the service is by raising an appropriate EnoException
        """
        return

        # TODO!
        conn = self.openconn()
        self.closeconn(conn)

    def exploit(self):
        """
        This method was added for CI purposes for exploits to be tested.
        Will (hopefully) not be called during actual CTF.
        :raises EnoException on Error
        :return This function can return a result if it wants
                If nothing is returned, the service status is considered okay.
                The preferred way to report Errors in the service is by raising an appropriate EnoException
        """
        # TODO: We still haven't decided if we want to use this function or not. TBA
        pass


app = STLDoctorChecker.service  # This can be used for uswgi.
if __name__ == "__main__":
    run(STLDoctorChecker)