有趣的Hack-A-Sat黑掉卫星挑战赛——发送遥控指令控制卫星goose
2023-4-15 20:32:55 Author: www.freebuf.com(查看原文) 阅读量:28 收藏

题目介绍

LaunchDotCom has a new satellite, the Carnac 2.0. What can you do with it from its design doc?

LaunchDotCom公司有一颗新的卫星——Carnac 2.0。卫星地面站正在与其进行遥测数据接收、遥控指令发送。本挑战题要求根据卫星的设计文档,思考如何获取flag。

本挑战题给出了一个链接地址,使用netcat连接到题目给的链接后,会给出进一步提示,如图4-14所示,提示遥测服务运行在另外一个地址上,继续使用netcat连接该地址,与4.2节类似,遥测服务正在发送一系列二进制数据,如图4-15所示,显示为乱码,根据题目信息,参赛者需要研究这颗新卫星的设计文档,从中解码出flag值。

图4-14  goose题目的提示信息

图4-15  连接到遥测服务后,接收到的数据

给出的资料有:

(1)cmd_telemetry_defs.zip,其中文件就是telemetry.xtce,它是一个XTCE文件,告诉我们二进制数据包是如何编码的。

(2)LaunchDotCom_Carnac_2.zip,解压后是文件LaunchDotCom_Carnac_2.pdf,该文件是卫星的设计文档,它详细描述这颗新卫星的信息。

题目编译及测试

本挑战题的代码位于goose目录下,查看challenge、solver目录下的Dockerfile,发现其中用到的是python:3.7-slim,为了加快题目的编译进度,在goose目录下新建一个文件sources.list,内容如下:

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free

deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free

将sources.list复制到goose、challenge、solver目录下,修改challenge、solver目录下的Dockerfile,在所有的FROM python:3.7-slim下方添加:

ADD sources.list /etc/apt/sources.list

打开终端,进入goose所在目录,执行命令:

sudo make build

使用make test命令进行测试,会顺利通过,其输出信息如图4-16所示。

图4-16  goose挑战题测试输出

题目解析

题目给出卫星设计文档LaunchDotCom_Carnac_2.pdf和遥测遥控数据格式telemetry.xtce,分别对其分析,找出获取flag的相关途径。

(1)LaunchDotCom_Carnac_2.pdf文档。

文档描述了Carnac2.0卫星系统结构,如图4-17所示。

图4-17  Carnac 2.0卫星系统结构

Carnac 2.0卫星系统由以下6个部分组成。

  • ADCS:Attitude Determination and Control System(姿态控制系统)。
  • Payload Sensors:有效载荷子系统。
  • ECS:增强型通信系统(包含Radio1和Radio2)。
  • EPS:Electrical Power System(电力系统)。
  • Flag Generation:标志生成器(存储敏感信息,也就是参赛者获取的flag)。

卫星设计文档中提到一个重要信息:“The EPS also manages low battery conditions by removing power from non-essential subsystems once a low voltage threshold is reached”表明了如果电压过低,Flag Generation模块可能会被关闭,那么就无法顺利获取flag。因此,我们需要重点关注电源模块EPS,避免因电压过低而无法启动Flag Generation模块。

LaunchDotCom_Carnac_2.pdf中给出与EPS相关的遥测遥控数据中每个参数的含义,如表4-2所示,表中缩放值指的是实际值经过一定比例进行缩放变化后的参数值。

表4-2  与EPS相关的遥测遥控数据中每个参数的含义

参 数 名 称

参    数

描    述

Battery Temp

BATT_TEMP

缩放值为BATT_TEMP / 10

Battery Voltage

BATT_VOLTAGE

缩放值为BATT_VOLT / 100 +9.0

Battery Low Voltage Threshold

LOW_PWR_THRESH

缩放值为LOW_PWR_THRESH/100+9.0

Battery Heater

BATT_HTR

电池状态为on/off

Low Power Mode

LOW_PWR_MODE

低功率模式将关闭非必要的模块

Payload Power

PAYLOAD_PWR

Payload Sensors模块电源开关

Flag Power

FLAG_PWR

Flag Generation模块的电源开关

续表

参 数 名 称

参    数

描    述

ADCS Power

ADCS_PWR

ADCS的电源开关

Radio1 Power

RADIO1_PWR

Radio1的电源开关

Radio2 Power

RADIO2_PWR

Radio2的电源开关

Payload Enable

PAYLOAD_ENABLE

使能Payload

Flag Enable

FLAG_ENABLE

使能Flag Generation

ADCS Enable

ADCS_ENABLE

使能ADCS

Radio1 Enable

RADIO1_ENABLE

使能Radio1

Radio2 Enable

RADIO2_ENABLE

使能Radio2

Command Error Count

CMD_ERR_CNT

收到的无效命令计数

(2)cmd_telemetry_defs.xtce。

cmd_telemetry_defs.xtce描述了卫星的遥测遥控协议。找到EPS相关部分,如下所示:

<xtce:SequenceContainer name="EPS Packet" shortDescription="packet of EPS data">

<xtce:EntryList>

<xtce:ParameterRefEntry parameterRef="BATT_TEMP"/>

<xtce:ParameterRefEntry parameterRef="BATT_VOLTAGE"/>

<xtce:ParameterRefEntry parameterRef="LOW_PWR_THRESH"/>

<xtce:ParameterRefEntry parameterRef="LOW_PWR_MODE"/>

<xtce:ParameterRefEntry parameterRef="BATT_HTR"/>

<xtce:ParameterRefEntry parameterRef="PAYLOAD_PWR"/>

<xtce:ParameterRefEntry parameterRef="FLAG_PWR"/>

<xtce:ParameterRefEntry parameterRef="ADCS_PWR"/>

<xtce:ParameterRefEntry parameterRef="RADIO1_PWR"/>

<xtce:ParameterRefEntry parameterRef="RADIO2_PWR"/>

<xtce:ParameterRefEntry parameterRef="UNUSED1"/>

<xtce:ParameterRefEntry parameterRef="PAYLOAD_ENABLE"/>

<xtce:ParameterRefEntry parameterRef="FLAG_ENABLE"/>

<xtce:ParameterRefEntry parameterRef="ADCS_ENABLE"/>

<xtce:ParameterRefEntry parameterRef="RADIO1_ENABLE"/>

<xtce:ParameterRefEntry parameterRef="RADIO2_ENABLE"/>

<xtce:ParameterRefEntry parameterRef="UNUSED3"/>

<xtce:ParameterRefEntry parameterRef="BAD_CMD_COUNT"/>

</xtce:EntryList>

<xtce:BaseContainer containerRef="AbstractTM Packet Header">

<xtce:RestrictionCriteria>

<xtce:ComparisonList>

<xtce:Comparison parameterRef="CCSDS_VERSION" value="0"/>

<xtce:Comparison parameterRef="CCSDS_TYPE" value="0"/>

<xtce:Comparison parameterRef="CCSDS_SEC_HD" value="0"/>

<xtce:Comparison parameterRef="CCSDS_APID" value="103"/>

</xtce:ComparisonList>

cmd_telemetry_defs.xtce和之前挑战类似。但之前的挑战题只是接收卫星下传的遥测数据,现在多了一个遥控部分,这意味着我们需要将遥控指令发送给卫星。注意到电源模块EPS包定义有许多信息,包括电压参数、低电量阈值等。通过和4.2节类似的解码方式,我们可以获得EPS包的结构如图4-18所示。

图4-18  EPS包结构

参考4.2节的解码方式,可以分为4步:

  • 根据XTCE格式,解码出包头APID的值。
  • 取出APID为103的数据包,就是电源模块EPS包。
  • 针对该数据包,依据图4-18分析出的包结构,提取出感兴趣的各参数值。
  • 打印各个参数,进一步得到更多信息,以便于后续分析。

关键代码如下:

import os

import sys

import socket

import random

import time

import bitstring

from pprint import pprint

FLAG_APID = 102

EPS_APID = 103

PAYLOAD_APID = 105

HOST='127.0.0.1'

class cmdSender:

def __init__(self, APID):

self.packet_version = 0

self.packet_type = 1

self.sec_header_flag = 0

self.APID = APID

self.sequence_flags = 3

self.packet_sequence_count = random.randint(1000, 10000)

self.packet_data_length = 1

if __name__ == '__main__':

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((HOST, 31335))

line = sock.recv(256)

sock.close()

_, Port = line.split(b" ")[-1].split(b":")

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((HOST, int(Port)))

time.sleep(1)

while True:

data = sock.recv(6) # 6字节的包头

s = bitstring.BitArray(data)

version, type, sec_header, apid, sequence_flags, sequence_count, data_length = s.unpack('uint:3, uint:1, uint:1, uint:11, uint:2, uint:14, uint:16')

print("APID: {}\nLength: {}\n".format(apid, data_length))

data = sock.recv(data_length+1)

if apid != EPS_APID:   # 只解析EPS相关遥测信息

print("Ignoring APID we don't care about")

continue

s = bitstring.BitArray(data)

BATT_TEMP, BATT_VOLTAGE, LOW_PWR_THRESH, LOW_PWR_MODE, BATT_HTR, PAYLOAD_PWR, FLAG_PWR, ADCS_PWR, RADIO1_PWR, RADIO2_PWR, UNUSED1, PAYLOAD_ENABLE, FLAG_ENABLE, ADCS_ENABLE, RADIO1_ENABLE, RADIO2_ENABLE= s.unpack('uint:16, uint:16, uint:16, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1, uint:1')

print("LOW_PWR_MODE:",LOW_PWR_MODE)

#259 Scaled value: BATT_VOLT / 100 + 9.0  11.5

print("BATT_VOLTAGE:",BATT_VOLTAGE)

#400  Scaled value: LOW_PWR_THRESH  /100 + 9.0

print("LOW_PWR_THRESH:",LOW_PWR_THRESH)

print("BATT_HTR:",BATT_HTR)

print("PAYLOAD_PWR:",PAYLOAD_PWR)

print("FLAG_PWR:",FLAG_PWR)

print("ADCS_PWR:",ADCS_PWR)

print("RADIO1_PWR",RADIO1_PWR)

print("RADIO2_PWR:",RADIO2_PWR)

print("PAYLOAD_ENABLE:",PAYLOAD_ENABLE)

print("FLAG_ENABLE:",FLAG_ENABLE)

break

运行上述代码后,输出如图4-19所示,成功解析出EPS遥测数据包。

图4-19  解析遥测数据中的EPS包

我们注意到LOW_PWR_MODE=1,如表4-2所示,说明卫星处于低电量模式,可能会关闭一些非核心的系统;FLAG_PWR=0,说明Flag Generation模块的电源是关闭的。因此,首先要做的就是想办法打开Flag Generation模块的供电,进一步从图4-19分析,BATT_VOLTAGE=259,LOW_PWR_THRESH=400,说明低电量模式下电池电压(BATT_VOLTAGE:259)<电池低电压门限(LOW_PWR_THRESH:400)。查看表4-2中BATT_VOLTAGE的描述,259是经过缩放后的值,实际电压根据表中BATT_VOLT / 100 +9.0计算,可得11.59V;同理低电压门限根据表中LOW_PWR_THRESH /100+9.0计算可得,目前的低电压门限设置13V。

根据上述分析,得出解题思路:

  • 打开Flag Generation模块电源。
  • 关闭低电量模式,可以尝试即修改低电压门限,使其小于实际电压。

步骤一:打开Flag Generation模块电源。

通过查看XTCE文档的遥控指令格式可知,可以通过发送EnableFLAG命令设置FLAG_PWR=1,即打开Flag Generation模块电源。XTCE文档中EnableFLAG命令定义如下:

<xtce:MetaCommand name="AbstractCMD Packet Header" shortDescription="CCSDS CMD Packet Header" abstract="true">

<xtce:EntryList>

<xtce:ParameterRefEntry parameterRef="CCSDS_VERSION"/>

<xtce:ParameterRefEntry parameterRef="CCSDS_TYPE"/>

<xtce:ParameterRefEntry parameterRef="CCSDS_SEC_HD"/>

<xtce:ParameterRefEntry parameterRef="CCSDS_APID"/>

<xtce:ParameterRefEntry parameterRef="CCSDS_GP_FLAGS"/>

<xtce:ParameterRefEntry parameterRef="CCSDS_SSC"/>

<xtce:ParameterRefEntry parameterRef="CCSDS_PLENGTH"/>

</xtce:EntryList>

<xtce:RestrictionCriteria>

<xtce:ComparisonList>

<xtce:Comparison parameterRef="CCSDS_VERSION" value="0"/>

<xtce:Comparison parameterRef="CCSDS_TYPE" value="1"/>

<xtce:Comparison parameterRef="CCSDS_SEC_HD" value="0"/>

<xtce:Comparison parameterRef="CCSDS_GP_FLAGS" value="3"/>

</xtce:ComparisonList>

</xtce:RestrictionCriteria>

</xtce:MetaCommand>

<xtce:MetaCommand name="EnableFLAG" abstract="false">

<xtce:BaseMetaCommand metaCommandRef="AbstractCMD Packet Header">

<xtce:RestrictionCriteria>

<xtce:ComparisonList>

<xtce:Comparison parameterRef="CCSDS_APID" value="103"/>

<xtce:Comparison parameterRef="CCSDS_PLENGTH" value="2"/>

</xtce:ComparisonList>

</xtce:RestrictionCriteria>

</xtce:BaseMetaCommand>

<xtce:ArgumentList>

<xtce:Argument argumentTypeRef="EnableState" name="PowerState"/>

</xtce:ArgumentList>

<xtce:CommandContainer name="ADCSenable">

<xtce:EntryList>

<xtce:ParameterRefEntry parameterRef="CMD"/>

<xtce:ParameterRefEntry parameterRef="PARAM"/>

<xtce:ArgumentRefEntry argumentRef="PowerState"/>

</xtce:EntryList>

<xtce:RestrictionCriteria>

<xtce:Comparison parameterRef="CMD" value="0"/>

<xtce:Comparison parameterRef="PARAM" value="2"/>

</xtce:RestrictionCriteria>

由此可知,需要发送的EnableFLAG命令的结构如下,同时,从从上面定义中注意到,限制标准(RestrictionCriteria)部分,CMD=0,PARAM=2。

6字节包头(header)+CMD+PARAM+PowerState

......

<xtce:ParameterSet>

<xtce:Parameter parameterTypeRef="1ByteInteger" name="CMD" initialValue="4" readOnly="true"/>

<xtce:Parameter parameterTypeRef="1ByteInteger" name="PARAM" initialValue="4" readOnly="true"/>

</xtce:ParameterSet>

<xtce:ArgumentList>

<xtce:Argument argumentTypeRef="EnableState" name="PowerState"/>

</xtce:ArgumentList>

......

<xtce:ArgumentList>

<xtce:Argument argumentTypeRef="EnableState"name="PowerState"/>

</xtce:ArgumentList>

</xtce:IntegerArgumentType>

<xtce:EnumeratedArgumentType name="EnableState" initialValue="ENABLE">

<xtce:UnitSet/>

<xtce:IntegerDataEncoding sizeInBits="8" signed="false"/>

<xtce:EnumerationList>

<xtce:Enumeration label="ENABLE" value="1"/>

<xtce:Enumeration label="DISABLE" value="0"/>

</xtce:EnumerationList>

</xtce:EnumeratedArgumentType>

<xtce:StringArgumentType name="Enable">

<xtce:UnitSet/>

......

对于上述参数,寻找对应的参数格式,其中“CMD”和“PARAM”均为1字节。“PowerState”对应的是“EnableState”。“EnableState”为1字节的无符号参数,“PowerState”为“ENABLE”状态对应的值1。

因此需要发送的EnableFLAG命令的结构进一步明确如下:

6字节包头(header)+1字节CMD+1字节PARAM+1字节PowerState

EnableFLAG命令结构如图4-20所示。

图4-20  EnableFLAG命令结构

其中CMD=0,PARAM=2,“PowerState”为“ENABLE”状态对应的值1。最终需要发送的命令如下:

6字节包头结构+ \x00\x02\x01

步骤二:关闭低电量模式。

通过查看XTCE文档可以知道有调整低电量模式阈值的遥控指令LOW_PWR_THRES。当前电压为11.59V,需要找到合理的门限电压值,使得当前的电压11.59V大于门限电压值,从而消除低电量告警。

</xtce:MetaCommand>

<xtce:MetaCommand name="LOW_PWR_THRES" abstract="false">

<xtce:BaseMetaCommand metaCommandRef="AbstractCMD Packet Header">

<xtce:RestrictionCriteria>

<xtce:ComparisonList>

<xtce:Comparison parameterRef="CCSDS_APID" value="103"/>

<xtce:Comparison parameterRef="CCSDS_PLENGTH" value="3"/>

</xtce:ComparisonList>

</xtce:RestrictionCriteria>

</xtce:BaseMetaCommand>

<xtce:ArgumentList>

<xtce:Argument argumentTypeRef="VoltageArgType" name="LW_PWR_THRES"/>

</xtce:ArgumentList>

<xtce:CommandContainer name="ADCSenable">

<xtce:EntryList>

<xtce:ParameterRefEntry parameterRef="CMD"/>

<xtce:ParameterRefEntry parameterRef="PARAM"/>

<xtce:ArgumentRefEntry argumentRef="LW_PWR_THRES"/>

</xtce:EntryList>

<xtce:RestrictionCriteria>

<xtce:Comparison parameterRef="CMD"value="0"/>

<xtce:Comparison parameterRef="PARAM"value="12"/>

</xtce:RestrictionCriteria>

</xtce:CommandContainer>

</xtce:MetaCommand>

其中,“LW_PWR_THRES”对应的参数类型是“VoltageArgType”。因此调整低电量模式的阈值命令的结构为

6字节包头(header)+1字节“CMD+1字节“PARAM+2字节“LW_PWR_THRESVoltageArgType类型)

注意到,限制标准(RestrictionCriteria)部分中,CMD=0,PARAM=12。对于上述参数,在XTCE文档中寻找对应的参数格式,由前文得知,“CMD”和“PARAM”为1字节。提到“LW_PWR_THRES”对应的参数类型是“VoltageArgType”(其定义如下),而“VoltageArgType”为16bit的无符号参数,因此“LW_PWR_THRES”是类型“VoltageArgType”,即16bit的无符号参数。调整低电量模式阈值的包结构如图4-21所示。

<xtce:FloatArgumentType sizeInBits="32" name="VoltageArgType">

<xtce:UnitSet>

<xtce:Unit description="V">:V</xtce:Unit>

</xtce:UnitSet>

<xtce:IntegerDataEncoding sizeInBits="16" encoding="unsigned">

<xtce:DefaultCalibrator>

<xtce:PolynomialCalibrator>

<xtce:Term coefficient="-90.0" exponent="0"/>

<xtce:Term coefficient="100.0" exponent="1"/>

</xtce:PolynomialCalibrator>

图4-21  调整低电量模式阈值的包结构

结合之前观察到的限制标准(RestrictionCriteria)部分中,CMD=0,PARAM=12。进一步明确,需要发送的命令结构如下:

6字节包头(header)+ \x00\x0C+2字节LW_PWR_THRES

其中关键是“LW_PWR_THRES”的选取。已知当前电压为11.59V,需要找到合理的门限电压值,使得当前的电压11.59V大于门限电压值。经过尝试,阈值设置高于11.22V即可获取flag。本节设置阈值为11.22V,通过表4-2计算,11.22V对应的是222(十六进制是0xDE)。最终发送的命令为:

6字节包头(header)+ \x00\x0C\xDE

到此,可以给出解答本挑战题的关键代码如下:

import os

import sys

import socket

import random

import time

sys.path.append('./anaconda3/lib/python3.6')

#print(sys.path)

import bitstring

FLAG_APID = 102

EPS_APID = 103

PAYLOAD_APID = 105

HOST='127.0.0.1'

class cmdSender:

def __init__(self, APID):

self.packet_version = 0

self.packet_type = 1

self.sec_header_flag = 0

self.APID = APID

self.sequence_flags = 3

self.packet_sequence_count = random.randint(1000, 10000)

self.packet_data_length = 1

# 定义一个发送遥控指令的函数

def sendCMD(self, socket, payload):

self.packet_data_length = len(payload) - 1

packet = bitstring.pack('uint:3, uint:1, uint:1, uint:11, uint:2, uint:14, uint:16',

self.packet_version, self.packet_type, self.sec_header_flag, self.APID, self.sequence_flags,

self.packet_sequence_count, self.packet_data_length)

packethdr = packet.tobytes()

wholepacket = packethdr + payload # 包头

self.packet_sequence_count += 1

socket.send(wholepacket)

return True

if __name__ == '__main__':

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((HOST, 31335))

line = sock.recv(256)

sock.close()

_, Port = line.split(b" ")[-1].split(b":")

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((HOST, int(Port)))

# 使能Flag Generator模块

cmd = cmdSender(EPS_APID)

time.sleep(1)

cmd.sendCMD(sock, b'\x00\x02\x01') # 发送使能Flag Generator模块命令

# 选取阈值222(11.22V),发送关闭低电量模式命令

cmd.sendCMD(sock, b'\x00\x0c\x03\xde')

# 参考4.2节中的模式解析遥测数据中的flag值

while True:

data = sock.recv(6) #6字节包头

s = bitstring.BitArray(data)

version, type, sec_header, apid, sequence_flags, sequence_count, data_length = s.unpack('uint:3, uint:1, uint:1, uint:11, uint:2, uint:14, uint:16')# 从头解析包数据

print("APID: {}\nLength: {}\n".format(apid, data_length))

data = sock.recv(data_length+1)

if apid != FLAG_APID:   # 选出flag包

print("Ignoring APID we don't care about")

continue

s = bitstring.ConstBitStream(data)

char = ' '

flag = ''

while char != '}':

char = chr(s.read('uint:7')) # 取出7bit flag片段,每7bit转换为8bit字符

flag += char

print(flag)

break


文章来源: https://www.freebuf.com/articles/neopoints/363726.html
如有侵权请联系:admin#unsafe.sh