2020 Hack-A-Sat DEFCON Space Security Challenge CTF Qualifiers 2020 - Part 3

SpaceDB

This was my favorite challenge of the contest, as it mixed a real-world cubesat framework and technology you're likely to come across as a security engineer, in a convincing manner.

The Problem

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

Connecting to the challenge service spawns a kubOS instance, and a GraphQL service we can connect to:

### Welcome to kubOS ###
Initializing System ...

** Welcome to spaceDB **
-------------------------

req_flag_base  warn: System is critical. Flag not printed.

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: redacted:port/tel/graphiql

update_tel  info: Updating reaction_wheel telemetry.
update_tel  info: Updating gps telemetry.
update_tel  info: Updating eps telemetry.

Before I get started, if you're unfamiliar with GraphQL, let me stop you here and encourage you to take a look at one of Lee Bryon's talks explaining its origins and what it is designed for. I won't go in to explaining the query language here, as he covers it much better than I can. Back when it was introduced I was fairly hopeful we'd see strong adoption and great tooling for it, but it seems to have lost a bit of momentum as of late. Turns out REST APIs are hard to kill.

Back to the challenge, perfoming some poking on the graphql endpoint we can see we've got a lot of telemetry values available:

Based on the console output, we can see that we've entered a critical system state because VIDIODE is too low. Also looks like solar panel voltage is low. Checking... what VIDIODE is at:

Query:

{
  telemetry(subsystem:"eps", parameter:"VIDIODE")
  {
    value,
    timestamp
  }
}
Results:
{
  "data": {
    "telemetry": [
      {
        "value": "6.492487911",
        "timestamp": 1590478238.137734
      },
      {
        "value": "6.485033866",
        "timestamp": 1590478178.091266
      },
      {
        "value": "6.477303053",
        "timestamp": 1590478118.092704
      }
      ...

As its hanging around 6.5v, lets see what happens when we lie and shove it up to 7v:

mutation
{
  insert(subsystem:"eps", parameter:"VIDIODE", value:"7.0"){
   	success, errors 
  }
}
Results:
{
  "data": {
    "insert": {
      "success": true,
      "errors": ""
    }
  }
}
And all of a sudden we get a different output on our terminal connection with the satellite:
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: Solar panel voltage low
critical-tel-check  info: eps subsystem: OK
critical-tel-check  info: Position: GROUNDPOINT
critical-tel-check  warn: System: OK. Resuming normal operations.
critical-tel-check  info: Scheduler service comms started successfully at: redacted:port/sch/graphiql
A new GraphQL Service appears! Poking around the schema, we've now got the ability to look at and modify the satellite's schedule and applications. The output was following sort of what we'd expect based on the documented KubOS Scheduler service:
{
  availableModes{
   name,
   path,
   schedule{
     tasks{
       app{
         name
       },
       description,
      delay,
       time,
       period
     }
     path
   }
   active
 }
}
Results:
{
  "data": {
    "availableModes": [
      {
        "name": "low_power",
        "path": "/challenge/target/release/schedules/low_power",
        "schedule": [
          {
            "tasks": [
              {
                "app": {
                  "name": "low_power"
                },
                "description": "Charge battery until ready for transmission.",
                "delay": "5s",
                "time": null,
                "period": null
              },
              {
                "app": {
                  "name": "activate_transmission_mode"
                },
                "description": "Switch into transmission mode.",
                "delay": null,
                "time": "2020-05-23 09:51:38",
                "period": null
              }
            ],
            "path": "/challenge/target/release/schedules/low_power/nominal-op.json"
          }
        ],
        "active": false
      },
      {
        "name": "safe",
        "path": "/challenge/target/release/schedules/safe",
        "schedule": [],
        "active": false
      },
      {
        "name": "station-keeping",
        "path": "/challenge/target/release/schedules/station-keeping",
        "schedule": [
          {
            "tasks": [
              {
                "app": {
                  "name": "update_tel"
                },
                "description": "Update system telemetry",
                "delay": "35s",
                "time": null,
                "period": "1m"
              },
              {
                "app": {
                  "name": "critical_tel_check"
                },
                "description": "Trigger safemode on critical telemetry values",
                "delay": "5s",
                "time": null,
                "period": "5s"
              },
              {
                "app": {
                  "name": "request_flag_telemetry"
                },
                "description": "Prints flag to log",
                "delay": "0s",
                "time": null,
                "period": null
              }
            ],
            "path": "/challenge/target/release/schedules/station-keeping/nominal-op.json"
          }
        ],
        "active": true
      },
      {
        "name": "transmission",
        "path": "/challenge/target/release/schedules/transmission",
        "schedule": [
          {
            "tasks": [
              {
                "app": {
                  "name": "groundpoint"
                },
                "description": "Orient antenna to ground.",
                "delay": null,
                "time": "2020-05-23 09:51:48",
                "period": null
              },
              {
                "app": {
                  "name": "enable_downlink"
                },
                "description": "Power-up downlink antenna.",
                "delay": null,
                "time": "2020-05-23 09:52:08",
                "period": null
              },
              {
                "app": {
                  "name": "disable_downlink"
                },
                "description": "Power-down downlink antenna.",
                "delay": null,
                "time": "2020-05-23 09:52:13",
                "period": null
              },
              {
                "app": {
                  "name": "sunpoint"
                },
                "description": "Orient solar panels at sun.",
                "delay": null,
                "time": "2020-05-23 09:52:18",
                "period": null
              }
            ],
            "path": "/challenge/target/release/schedules/transmission/nominal-op.json"
          }
        ],
        "active": false
      }
    ]
  }
}

At first, I attempted to force the station into "station-keeping" mode to execute "request_flag_telemetry", however this mode triggers "critical_tel_check", which will re-check the DIDIODE voltage, work out that we fudged it, and kill us. Next attempt was creating a new schedule and triggering it with a new "pwn" mode, using the importRawTaskListMethod:

{
"tasks": [
    {
    "description": "lol",
    "delay": "1s",
    "app": {
        "name": "request_flag_telemetry"
    }
    }
]
}
JSON escaping it, creating the mode, then triggering it:
mutation{
  createMode(name:"pwn")
  {
    success
  }

  importRawTaskList(
    name:"pwn",
    mode:"pwn",
    json: "(the above JSON serialized)"
  )
  {
    success
  }
  activateMode(name:"pwn")
  {
    success
  }
}

Problem is the "request_flag_telemetry" actually "consumes" power, and the satellite dies before it gets the chance to send the telemetry:

transmission
WARN: Could not establish downlink.
ERROR: Downlink: FAILED
WARN: LOW battery.
Shutting down...
Goodbye.

The next thing I tried was to create a schedule that would sleep the satelite for a few minutes, wake it up, and try to send the signal - this failed as well, presumptuously because the low power mode was programmed to only restore enough power for however long you were sleeping.

Helpfully, the low power mode did do a time warp, so creating a schedule with a longer sleep time wasn't a problem. Making modifications to the low power mode (disabling transmission, pointing the satellite towards the sun) and modifying the transmission schedule to include a flag dump did the trick though:

// FAKE TRANSMISSION MODE

    {
      "tasks": [
        {
          "app": {
            "name": "groundpoint"
          },
          "description": "Orient antenna to ground.",
          "delay": null,
          "time": "2020-05-23 11:09:04",
          "period": null
        },
        {
          "app": {
            "name": "enable_downlink"
          },
          "description": "Power-up downlink antenna.",
          "delay": null,
          "time": "2020-05-23 11:09:24",
          "period": null
        },
        {
          "app": {
            "name": "request_flag_telemetry"
          },
          "description": "Prints flag to log",
          "time": "2020-05-23 11:09:25"
        },
        {
          "app": {
            "name": "disable_downlink"
          },
          "description": "Power-down downlink antenna.",
          "delay": null,
          "time": "2020-05-23 11:09:29",
          "period": null
        },
        {
          "app": {
            "name": "sunpoint"
          },
          "description": "Orient solar panels at sun.",
          "delay": null,
          "time": "2020-05-23 11:09:34",
          "period": null
        }
      ]
    }

// END FAKE TRANSMISSION MODE

// FAKE LOW POWER MODE

{
  "tasks": [
    {
      "app": {
        "name": "disable_downlink"
      },
      "description": "Power-down downlink antenna.",
      "delay": "0s"
    },
    {
      "app": {
        "name": "sunpoint"
      },
      "description": "Orient solar panels at sun.",
      "delay": "0s"
    },
    {
      "app": {
        "name": "low_power"
      },
      "description": "Charge battery until ready for transmission.",
      "delay": "5s",
      "time": null,
      "period": null
    },
    {
      "app": {
        "name": "activate_transmission_mode"
      },
      "description": "Switch into transmission mode.",
      "delay": null,
      "time": "2020-05-23 11:08:54",
      "period": null
    }
  ]
}

// END FAKE LOW POWER MODE

Injecting the replaced transmission mode:

mutation{
  removeTaskList(mode:"transmission",name:"nominal-op") {success,errors}
  importRawTaskList(
    name:"nominal-op",
    mode:"transmission",
    json: "(encoded from above)",
  )
  {
    success,
    errors
  }
}

Injecting the replaced low power mode and entering it:

mutation{
  removeTaskList(mode:"low_power", name:"nominal-op") {success,errors}
  importRawTaskList(
    name:"nominal-op",
    mode:"low_power",
    json: "(encoded from above)"
  {
    success,
    errors
  }
  activateMode(name:"low_power") { success }
}

And then we can sit back and watch the logs from the satellite as our schedule goes to work!

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: Solar panel voltage low
critical-tel-check  info: eps subsystem: OK
critical-tel-check  info: Position: GROUNDPOINT
critical-tel-check  warn: System: OK. Resuming normal operations.
critical-tel-check  info: Scheduler service comms started successfully at: redact:port/sch/graphiql

Low_power mode enabled.
Timetraveling.

sunpoint  info: Adjusting to sunpoint...
sunpoint  info: [2020-05-23 10:20:05] Sunpoint panels: SUCCESS

Transmission mode enabled.

Pointing to ground.
Transmitting...

----- Downlinking -----
Recieved flag.
flag{redact}

Downlink disabled.
Adjusting to sunpoint...
Sunpoint: TRUE
Goodbye

Comments

Popular posts from this blog

2020 Hack-A-Sat DEFCON Space Security Challenge CTF Qualifiers 2020 - Part 1

Man-in-the-middling SSL / TLS on Windows