10
2
Fork 0
has-writeup/payload/spacedb
xenia 0141d1190b finish basic css, tweak html output 2020-06-15 02:26:42 -04:00
..
README.md finish basic css, tweak html output 2020-06-15 02:26:42 -04:00

README.md

SpaceDB

Category: Payload Modules
Points (final): 79 points
Solves: 53

The last over-the-space update seems to have broken the housekeeping on our satellite. Our satellite's battery is low and is running out of battery fast. We have a short flyover window to transmit a patch or it'll be lost forever. The battery level is critical enough that even the task scheduling server has shutdown. Thankfully can be fixed without without any exploit knowledge by using the built in APIs provied[sic] by kubOS. Hopefully we can save this one!

Note: When you're done planning, go to low power mode to wait for the next transmission window

Write-up

by Cameron and haskal

Upon connecting to the provided TCP service via netcat, we see that it spawns a telemetry service accessible via HTTP based off the following console output:

critical-tel-check info: Detected new telemetry values.
critical-tel-check info: Checking recently inserted telemetry values.
critical-tel-check info: Checking gps subsystem
critical-tel-check info: gps subsystem: OK
critical-tel-check info: reaction_wheel telemetry check.
critical-tel-check info: reaction_wheel subsystem: OK.
critical-tel-check info: eps telemetry check.
critical-tel-check warn: VIDIODE battery voltage too low.
critical-tel-check warn: Solar panel voltage low
critical-tel-check warn: System CRITICAL.
critical-tel-check info: Position: GROUNDPOINT
critical-tel-check warn: Debug telemetry database running at: 3.19.141.137:19369/tel/graphiql

Connecting to the provided endpoint yields a graphiql graphql console from which we can run queries against the telemetry database. Using the following query, we can dump the database schema to get an idea of the capabilities of the telemetry interface:

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}
fragment InputValue on __InputValue {
  name
  description
  type {
    ...TypeRef
  }
  defaultValue
}
fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

Dumping the schema reveals two things of interest:

  1. We may query telemetry
  2. We may mutate telemetry via delete and insertBulk

From the schema, we see that telemetry data has the following shape:

{
  "name": "telemetry",
  "description": "Telemetry entries in database",
  "args": [
    {
      "name": "timestampGe",
      "description": null,
      "type": {
        "kind": "SCALAR",
        "name": "Float",
        "ofType": null
      },
      "defaultValue": null
    },
    {
      "name": "timestampLe",
      "description": null,
      "type": {
        "kind": "SCALAR",
        "name": "Float",
        "ofType": null
      },
      "defaultValue": null
    },
    {
      "name": "subsystem",
      "description": null,
      "type": {
        "kind": "SCALAR",
        "name": "String",
        "ofType": null
      },
      "defaultValue": null
    },
    {
      "name": "parameter",
      "description": null,
      "type": {
        "kind": "SCALAR",
        "name": "String",
        "ofType": null
      },
      "defaultValue": null
    },
    {
      "name": "parameters",
      "description": null,
      "type": {
        "kind": "LIST",
        "name": null,
        "ofType": {
          "kind": "NON_NULL",
          "name": null,
          "ofType": {
            "kind": "SCALAR",
            "name": "String",
            "ofType": null
          }
        }
      },
      "defaultValue": null
    },
    {
      "name": "limit",
      "description": null,
      "type": {
        "kind": "SCALAR",
        "name": "Int",
        "ofType": null
      },
      "defaultValue": null
    }
  ],
  "type": {
    "kind": "NON_NULL",
    "name": null,
    "ofType": {
      "kind": "LIST",
      "name": null,
      "ofType": {
        "kind": "NON_NULL",
        "name": null,
        "ofType": {
          "kind": "OBJECT",
          "name": "Entry",
          "ofType": null
        }
      }
    }
  },
  "isDeprecated": false,
  "deprecationReason": null
}

Thus, we can dump the telemetry information via the following command:

query {
	telemetry{timestamp, subsystem, parameter, value}
}

From the console output, we can see that the issue plaguing the system is a low VIDIODE voltage alarm. Thus, in order to fix the alarm, we must spoof the proper VIDIODE voltage and trigger a reset of the alarm system. In order to do this, we run the following mutation:

mutation spoof {
	delete(timestampGe: 1590232427.582683){success, errors}
	insertBulk(timestamp: 1590232427.582683, entries: [
	{subsystem: "eps", parameter: "VIDIODE", value: "8.0"},
	{subsystem: "eps", parameter: "RESETS_MANUAL", value: "1.0"},
	{subsystem: "eps", parameter: "RESETS_BROWNOUT", value: "1.0"},
	{subsystem: "eps", parameter: "RESETS_AUTO_SOFTWARE", value: "1.0"},
	{subsystem: "eps", parameter: "BATTERY_1_RESETS_MANUAL", value: "1.0"},
	{subsystem: "eps", parameter: "BATTERY_1_RESETS_BROWNOUT", value: "1.0"},
	{subsystem: "eps", parameter: "BATTERY_1_RESETS_AUTO_SOFTWARE", value: "1.0"},
	{subsystem: "eps", parameter: "BATTERY_0_RESETS_MANUAL", value: "1.0"},
	{subsystem: "eps", parameter: "BATTERY_0_RESETS_BROWNOUT", value: "1.0"},
	{subsystem: "eps", parameter: "BATTERY_0_RESETS_AUTO_SOFTWARE", value: "1.0"},
	]){success, errors}
}

The correct value for VIDIODE was determined through trial and error. The reset flags were asserted because we didn't know which one we needed, so we just triggered all of them. Were this to be a real satellite, I'm sure nothing bad could possibly happen... We had to run the delete mutation on the most recent telemetry item in order to avoid triggering an alarm for duplicate telemetry data.

After successfully spoofing the telemetry data, we notice in the console output from our session that the scheduler has been activated:

critical-tel-check  info: Scheduler service comms started successfully at: 3.19.61.44:14764/sch
/graphiql

We visit the provided URL and find another graphiql interface. According to the KubOS documentation, we may issue the following query to enter safe mode and stop any subsequent checks which might kill the scheduler and bring us back to where we started:

mutation safe {
	safeMode{success, errors}
}

We also see that we can dump the task lists for all available modes with the following query:

query dump {
	availableModes{name, path, lastRevised, schedule{tasks{description, delay, time, period,
        app{name, args, config}}, path, filename, timeImported}, active},
	activeMode{name}
}

From the information from the dumped task lists, we can see that there are several tasks we may run. First and foremost, we need to fix our power situation by orienting the solar panels towards the sun. We may do this by running the following mutation:

mutation patch {
	createMode(name: "patch"){success, errors}
	importRawTaskList(name: "patch", mode: "patch", json: "{\"tasks\":[{\"description\":\"Orien
t solar panels at sun.\",\"delay\":\"0s\",\"time\":null,\"period\":null,\"app\":{\"name\":\"sun
point\",\"args\":null,\"config\":null}},{\"description\":\"Update system telemetry\",\"delay\":
\"1s\",\"time\":null,\"period\":null,\"app\":{\"name\":\"update_tel\",\"args\":null,\"config\":
null}}]}"){success, errors}
	activateMode(name: "patch"){success, errors}
}

Subsequently, we need to create a mode which will aim the transmission antenna at the ground, activate the antenna, print the flag to the log, transmit the comms buffer, power down the antenna, and reorient the solar panels. We may do this via the following mutation:

mutation {
	importRawTaskList(json:"{\"tasks\":[{\"description\":\"Orient antenna to ground.\",\"delay\
":null,\"time\":\"2020-05-23 16:40:49\",\"period\":null,\"app\":{\"name\":\"groundpoint\",\"arg
s\":null,\"config\":null}},{\"description\":\"Power-up downlink antenna.\",\"delay\":null,\"tim
e\":\"2020-05-23 16:41:09\",\"period\":null,\"app\":{\"name\":\"enable_downlink\",\"args\":null
,\"config\":null}},{\"description\":\"Prints flag to log\",\"delay\":null,\"time\":\"2020-05-23
 16:41:19\",\"period\":null,\"app\":{\"name\":\"request_flag_telemetry\",\"args\":null,\"config
\":null}},{\"description\":\"Power-down downlink antenna.\",\"delay\":null,\"time\":\"2020-05-2
3 16:41:34\",\"period\":null,\"app\":{\"name\":\"disable_downlink\",\"args\":null,\"config\":nu
ll}},{\"description\":\"Orient solar panels at sun.\",\"delay\":null,\"time\":\"2020-05-23 16:4
1:39\",\"period\":null,\"app\":{\"name\":\"sunpoint\",\"args\":null,\"config\":null}}]}",name:"
nominal-op",mode:"transmission"){success,errors}
}

Finally, per the challenge directive, we must have the satellite enter low-power mode:

mutation low_power {
	activateMode(name: "low_power"){success, errors}
}