Hack-A-Sat-qualifier-2020 复现
又开新坑了,从未接触过的全新领域——卫星安全!
beckley
这道题更像是传统CTF MISC中的OSINT算是初步接触卫星安全领域的开胃小菜吧
环境配置
首先从 yichen115的仓库中拉取题目
git clone https://github.com/yichen115/hackasat-qualifier-2020.git
cd hackasat-qualifier-2020
# 然后构建基础镜像
cd hackasat-qualifier-2020
docker build -t generator-base .
cd ..
由于原来Dockerfile中sgp4库的版本太旧了,需要修改sgp4库的版本
vim challenge/Dockerfile
修改成这样:
FROM ubuntu:16.04
#Install Apache, Python, Curl. Enable CGI. Install PIP, use PIP to install skyfield
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list; \
apt-get update -y; \
apt-get install -y apache2 python curl; \
a2enmod cgi; \
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py; \
python get-pip.py; \
pip install skyfield sgp4==2.15;
COPY errors /etc/apache2/sites-enabled/000-default.conf
COPY custom_50x.html /var/www/html/custom_50x.html
WORKDIR /usr/lib/cgi-bin
# Upload all files to cgi-bin directory
COPY *.py ./
COPY entrypoint.sh ./
COPY deltat.* ./
COPY Leap_Second.dat ./
RUN chmod +x challenge.py
# Suppress error output
RUN echo "ServerName beckley.mydomain.com" >> /etc/apache2/apache2.conf
#ENV FLAG=flag{Hungry_For_Apples}
# RUN
ENTRYPOINT ./entrypoint.sh
然后使用make build生成镜像,也可以用make challenge只生成题目镜像
然后就可以启动题目了
使用以下命令启动题目容器:
socat -v tcp-listen:19020,reuseaddr exec:"docker run --rm -i -e SERVICE_HOST=172.17.0.1 -e SERVICE_PORT=19021 -e SEED=1000 -e FLAG=flag{zulu49225delta\:GG1EnNVMK3-hPvlNKAdEJxcujvp9WK4rEchuEdlDp3yv_Wh_uvB5ehGq-fyRowvwkWpdAMTKbidqhK4JhFsaz1k} -p 19021\:80 beckley\:challenge"
这道题需要启3个终端来交互第一个终端运行上面的命令启动容器
第二个终端需要使用nc访问题目容器
nc 172.17.0.1 19020
第三个终端用来提交答案获取flag,可以先尝试随便传点东西测试题目环境是否搭建成功
curl http://172.17.0.1:19021/cgi-bin/HSCKML.py?CAMERA=0,0,0,0,0 -H 'User-Agent: GoogleEarth/7.3.2.5815(X11;Linux (5.2.0.0);en;kml:2.2;client:Pro;type:default)' -H 'Accept: application/vnd.google-earth.kml+xml, application/vnd.google-earth.kmz, image/*, */*' -H 'Accept-Language: en-US, *' -H 'Connection: keep-alive'
连接成功后会返回xml的响应:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Placemark>
<name>Keep Looking...</name>
<Point>
<coordinates>0.000000,0.000000</coordinates>
</Point>
</Placemark>
</kml>
搭建成功后就像这样:

解题
现在我们就可以正式开始解题了,先看看nc给我们的题目描述

这里的意思是太空中有颗卫星于 2020 年 3 月 26 日 21:53:13 按照特定的视角拍到了华盛顿纪念碑,我们需要伪造一个Google earth Pro的KML文件来获取flag
附件中给了我们KML的文件格式:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Folder>
<name>HackASatCompetition</name>
<visibility>0</visibility>
<open>0</open>
<description>HackASatComp1</description>
<NetworkLink>
<name>View Centered Placemark</name>
<visibility>0</visibility>
<open>0</open>
<description>This is where the satellite was located when we saw it.</description>
<refreshVisibility>0</refreshVisibility>
<flyToView>0</flyToView>
<LookAt id="ID">
<!-- specific to LookAt -->
<longitude>FILL ME IN</longitude> <!-- kml:angle180 -->
<latitude>FILL ME IN TOO</latitude> <!-- kml:angle90 -->
<altitude>FILL ME IN AS WELL</altitude> <!-- double -->
<heading>FILL IN THIS VALUE</heading> <!-- kml:angle360 -->
<tilt>FILL IN THIS VALUE TOO</tilt> <!-- kml:anglepos90 -->
<range>FILL IN THIS VALUE ALSO</range> <!-- double -->
<altitudeMode>clampToGround</altitudeMode>
</LookAt>
<Link>
<href>http://FILL ME IN:FILL ME IN/cgi-bin/HSCKML.py</href>
<refreshInterval>1</refreshInterval>
<viewRefreshMode>onStop</viewRefreshMode>
<viewRefreshTime>1</viewRefreshTime>
<viewFormat>BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth];CAMERA=[lookatLon],[lookatLat],[lookatRange],[lookatTilt],[lookatHeading];VIEW=[horizFov],[vertFov],[horizPixels],[vertPixels],[terrainEnabled]</viewFormat>
</Link>
</NetworkLink>
</Folder>
</kml>
我们需要提交的参数有6个:longitude, latitude, altitude, heading, tilt, range
longitude 是华盛顿纪念碑的经度,latitude 是华盛顿纪念碑的纬度,altitude 是纪念碑的海拔,但由于这里altitudeMode是clampToGround,所以可以填0,华盛顿纪念碑的经纬度我们可以通过谷歌地图得到

我们还需要heading, tilt, 和 range三个参数
heading 是飞行器前进的方向
tilt 是卫星与地球表面法线之间的角度
range 是卫星距离目标的距离
这三个参数我们可以从题目一开始给出的信息中获取:
The image was taken on March 26th, 2020, at 21:53:13
The satellite we used was:
REDACT
1 13337U 98067A 20087.38052801 -.00000452 00000-0 00000+0 0 9995
2 13337 51.6460 33.2488 0005270 61.9928 83.3154 15.48919755219337
题目中给出了卫星的TLE(Two-Line Element set),TLE的格式是这样的:
第 1 行
| 字段 | 值 | 含义 |
|---|---|---|
| 行号 | 1 |
第一行 |
| 卫星编号 | 13337 |
NORAD 目录编号 |
| 分类 | U |
Unclassified(非机密) |
| 国际编号 | 98067A |
1998 年第 67 次发射,A 部分 |
| 历元 | 20087.38052801 |
2020 年第 87 天(3 月 27 日)09:07:57 UTC |
| 平均运动一阶导数 | -.00000452 |
轨道周期变化率 |
| 辐射压项 | 00000-0 |
太阳辐射压影响 |
| BSTAR 阻力 | 00000+0 |
大气阻力项 |
| 星历类型 | 0 |
默认 |
| 元素集号 | 999 |
轨道参数版本 |
| 校验和 | 5 |
校验位 |
第 2 行
| 字段 | 值 | 含义 |
|---|---|---|
| 行号 | 2 |
第二行 |
| 卫星编号 | 13337 |
与第一行一致 |
| 轨道倾角 | 51.6460° |
轨道平面与赤道面的夹角 |
| 升交点赤经 | 33.2488° |
轨道升交点经度 |
| 偏心率 | 0005270 → 0.0005270 |
轨道形状(接近圆形) |
| 近地点幅角 | 61.9928° |
近地点方向 |
| 平近点角 | 83.3154° |
卫星在轨道上的位置 |
| 平均运动 | 15.48919755 |
每天绕地球 15.49 圈 |
| 圈数 | 21933 |
发射以来的圈数 |
| 校验和 | 7 |
校验位 |
更详细的信息可以看:TLE
我们可以用python的skyfield库来解析TLE数据
写脚本求一下:
from skyfield.api import load, EarthSatellite, Topos
tle_lines = [
"1 13337U 98067A 20087.38052801 -.00000452 00000-0 00000+0 0 9995",
"2 13337 51.6460 33.2488 0005270 61.9928 83.3154 15.48919755219337"
]
ts = load.timescale()
satellite = EarthSatellite(tle_lines[0], tle_lines[1], 'Test Satellite', ts)
t = ts.utc(2020, 3, 26, 21, 53, 13)
monument = Topos('38.88964619165539 N', '77.03486031680167 W', elevation_m=0)
difference = satellite - monument
topocentric = difference.at(t)
alt, az, distance = topocentric.altaz()
print('Altitude:', alt)
print('Azimuth:', az)
print('Distance: %d' % distance.m)
tilt = 90 - alt.degrees
heading = (180 + az.degrees) % 360
print('Tilt:', tilt)
print('Heading:', heading)
我们来看看为什么要这么写,首先是卫星的TEL:
tle_lines = [
"1 13337U 98067A 20087.38052801 -.00000452 00000-0 00000+0 0 9995",
"2 13337 51.6460 33.2488 0005270 61.9928 83.3154 15.48919755219337"
]
然后是历元,时间点越精确,得到的卫星数据就越精确
t = ts.utc(2020, 3, 26, 21, 53, 13)
可以看看skyfield的文档 skyfield:
我们从TEL中得到的是位置是地心天球参考系中卫星的 x、y、z 坐标,也就是说是以地心为观察者视角得到的卫星的位置,而解题需要知道的是以华盛顿纪念碑为观察者视角的卫星位置,所以我们需要做减法来得到距离distance
difference = satellite - monument
topocentric = difference.at(t)
alt, az, distance = topocentric.altaz()
然后我们可以得到高度角alt和方位角az
其中,高度角与tilt是互余的

所以我们只需要用90减去高度角就是tilt了
而heading 是卫星前进的方向,我们所得到的方位角是从目标看卫星的方向,而我们需要从卫星看目标的方向,所以需要加180度,然后模360使得范围落在360度内

所以可以得到答案
