在Linux下使用Hook修改GPS接口数据
郝伟 2021/01/10
GPS定位在系统中是通过硬件芯片完成。操作系统通过驱动程序实现对硬件资源进行管理。当软件需要GPS信息时,只需向系统申请调用相应的API接口即可。在本验证中,我们的目标是在程序调用了GPS的API接口时,将返回的数据进行一定的修改,从而让应用程序无法获得真实的GPS信息。
我们的目标在修改GPS返回的数据,所以需要通过一定的机制,能够修改应用程序调用系统API返回的数据。为了实现这个目标,可以利用Hook的机制,在应用程序与系统接口后再加上一层Hook,从而实现数据的修改目标,这里我们可以利用系统预加载模块。在Linux系统中,ld-linux.so.2 是一个动态库加载器(Dynamic Loader, DL),用于在系统加时自动搜索系统参数并加载相应的共享库。对于GPS模块来说,需要由相应的库函数提供相应的地理信息读写功能。所以系统在启动后,加载这个库,从而实现其他程序对此库的调用,这过程就是系统启动的 预启动(preload) 过程。
为了让系统知道需要将哪些链接库引入到系统预启动过程,我们可以通过可选变量 LD_PRELOAD 进行配置。这个变量实际是一组文件索引,可以包含多个指向共享链接库文件的路径。加载器会先于 C 语言运行库之前载入LD_PRELOAD 指定的共享链接库,从而提前指定链接到哪个.so文件,完成所需要的预加载过程。
基于这个原理,我们在系统加载时,利用此功能编写我们的GPS调用函数,然后进行修改,并返回修改后的数据。从而以Hook原理实现对GPS模块数据的修改。
(注:如果下图显示不正常,请参见这里)
本实验在开发板 fl2440上进行,使用的GPS开发模块是A7版。
#ifndef __GPSInfo_H__ #define __GPSInfo_H__ typedef unsigned int UINT; typedef int BYTE; typedef struct __grpinfo__ { UINT date; /* 日期 */ UINT time; /* 时间 */ char mode; /* 模式 */ char state; /* 状态 */ float longitude; /* 经度 */ float latitude; /* 纬度 */ float speed; /* 速度 */ float direction; /* 方向 */ float declination; /* 倾角 */ }GPSINFO; #endif
GPS默认的数据格式为一串格式化的字符串,而GPS的读取函数接口定义如下所示:
// 创建指针,返回GPSINFO对象指针, // id: 所要读取的GPS模块编号 // gps_info: 返回的数据指针 // 返回值:结果0表示失败,其他值表示成功。 int gps_read(int id, GPSINFO *gps_info);
为了方便演示,定义打印输出函数如下所示:
int print_gps (GPSINFO *gps_info) { int lon_d = (int)(gps_info->latitude / 100); int lon_c = (int)(gps_info->latitude - (lon_d * 100)); int lon_s = (int)((lon_c - lon_c) * 60.0)); int lat_d = (int)(gps_info->longitude / 100); int lat_c = (int)(gps_info->longitude - (lat_d * 100)); int lat_s = (int)(((gps_info->longitude - lat_d * 100) - lat_d * 100) * 60.0); printf("========= GPS定位读取接口 =============\n"); printf("= 纬度: 北纬:%3d度%2d分%2d秒 =\n", lon_d, lon_c, lon_s); printf("= 经度: 东经:%3d度%2d分%2d秒 =\n", lat_d, lat_c, lat_s); printf("======================================\n"); return 0; }
以上是自定义的C库,用于伪造的GPS的调用的 preload_gps.so 文件。
#include <string.h> #include <dlfcn.h> #include "gpsinfo.h" // 定义调用接口函数指针 typedef int(*Gps_read)(int, GPSINFO*); // 自定义的gps_read函数,如果使用了预加载,那么系统调用时时会优先调用此函数而不是默认函数 // 但是在本函数内,由于已经是预加载函数,所以不会再进行一次预加载判断。 int gps_read(const int id, GPSINFO* gps_info) { // 使用RTLD_LAZY进行延迟加载,即对动态库中存在的未定义的变量不执行变量的地址解析 static void* handle = dlopen("gps.so", RTLD_LAZY); // 获得默认接口的地址指针 static Gps_read org_gps = (Gps_read)dlsym(handle, "gps_read"); // 以下内容对应图上第[3] - [5] 步 // 如果解析失败,则返回0,表示未读取到gps数据。 if(!org_gps) return 0; // 调用原始的GPS接口,获得GPS数据。 if(!org_gps(id, &ginfo)) return 0; // 修改GPS数据数据,对应图上的第[5]步,将公司地址修改为地中海岛国塞普鲁斯的位置 ginfo->latitude=25.0114; ginfo->longtitude=35.0542; // 返回1表示数据读取成功 return 1; }
编译代码:
$ gcc -fPIC gps.c -shared -o preload_gps.so -ldl
程序运行以后,结果如下:
# 华云安公司地址:116.2549, 40.0793 ========= GPS定位读取接口 ======= = 纬度: 北纬: 40度 4分24秒 = = 经度: 东经:116度14分56秒 = ================================
# 塞普鲁斯(地中海岛国): 25.0114, 35.0542 ========= GPS定位读取接口 ======= = 纬度: 北纬: 25度 0分41秒 = = 经度: 东经: 35度 3分15秒 = ================================
通过使用预加载,可以对系统的GPS模块的库进行Hook操作,从而在调用后修改GPS数据达到预期的效果。
[1] 经纬度换算,https://www.fcc.gov/media/radio/dms-decimal
[2] GPS定位C语言简介,https://blog.csdn.net/zouleideboke/article/details/73521122
[3] Linux下Hook方式汇总, https://xz.aliyun.com/t/6961
[4] Linux逆向之hook&注入, https://xz.aliyun.com/t/6883#toc-1
[5] Linux预加载ld-linux.so.2, https://www.cnblogs.com/kelamoyujuzhen/p/9823272.html
[6] PlantUML 活动图画法, https://plantuml.com/zh/activity-diagram-beta
[7] PlantUML 流程图画法, https://plantuml.com/zh/sequence-diagram
[8] FL2440开发板的介绍和烧录, https://blog.csdn.net/a4729821/article/details/75570690
[9] 百度经纬度查询,http://api.map.baidu.com/lbsapi/getpoint/?qq-pf-to=pcqq.c2c