在Linux下使用Hook修改GPS接口数据 郝伟 2021/01/10 [TOC]

1. 1 目的

GPS定位在系统中是通过硬件芯片完成。操作系统通过驱动程序实现对硬件资源进行管理。当软件需要GPS信息时,只需向系统申请调用相应的API接口即可。在本验证中,我们的目标是在程序调用了GPS的API接口时,将返回的数据进行一定的修改,从而让应用程序无法获得真实的GPS信息。

2. 2 实现原理

我们的目标在修改GPS返回的数据,所以需要通过一定的机制,能够修改应用程序调用系统API返回的数据。为了实现这个目标,可以利用Hook的机制,在应用程序与系统接口后再加上一层Hook,从而实现数据的修改目标,这里我们可以利用系统预加载模块。在Linux系统中,ld-linux.so.2 是一个动态库加载器(Dynamic Loader, DL),用于在系统加时自动搜索系统参数并加载相应的共享库。对于GPS模块来说,需要由相应的库函数提供相应的地理信息读写功能。所以系统在启动后,加载这个库,从而实现其他程序对此库的调用,这过程就是系统启动的 预启动(preload) 过程。

为了让系统知道需要将哪些链接库引入到系统预启动过程,我们可以通过可选变量 LD_PRELOAD 进行配置。这个变量实际是一组文件索引,可以包含多个指向共享链接库文件的路径。加载器会先于 C 语言运行库之前载入LD_PRELOAD 指定的共享链接库,从而提前指定链接到哪个.so文件,完成所需要的预加载过程。

基于这个原理,我们在系统加载时,利用此功能编写我们的GPS调用函数,然后进行修改,并返回修改后的数据。从而以Hook原理实现对GPS模块数据的修改。

(注:如果下图显示不正常,请参见这里

  • 正常的GPS数据读取过程 在正常的没有加入Hook之前,GPS的调用系统接口的过程如下所示:

    @startuml
    autonumber 1 1 "<b>[0]"
    应用程序 -> 操作系统: 调用GPS接口
    操作系统 --> GPS库: 直接在动态库中找到原GPS库的并调用相应接口
    操作系统 <- GPS库: 返回真实的GPS数据
    应用程序 <- 操作系统: 返回真实的GPS数据
    @enduml
    
  • 通过Hook机制修改GPS数据读取过程 在加入了Hook机制以后,多了一层自定义库的Hook层,从而实现数据的修改,过程如下所示:

    @startuml
    autonumber 1 1 "<b>[0]"
    应用程序 -> 操作系统: 调用 GPS 接口
    操作系统 -> 自定义库进行Hook: 发现同签名预加载库调用自定义的GPS接口
    自定义库进行Hook -[#0000FF]> GPS库: 调用真实的GPS接口
    GPS库 -[#0000FF]> 自定义库进行Hook: 返回真实的GPS数据
    自定义库进行Hook -[#red]> 自定义库进行Hook: 修改数据
    note right of 自定义库进行Hook: 第[5]步是重要步骤,数据在此完成修改
    操作系统 <- 自定义库进行Hook: 返回修改后的数据
    应用程序 <- 操作系统: 返回修改后的GPS数据
    @enduml
    

3. 3 验证过程

3.1. 3.1 硬件设备

本实验在开发板 fl2440上进行,使用的GPS开发模块是A7版。

3.2. 3.2 GPS调用接口

3.2.1. 3.2.1 GPS信息结构体

#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

3.2.2. 3.2.2 GPS读取接口

GPS默认的数据格式为一串格式化的字符串,而GPS的读取函数接口定义如下所示:

// 创建指针,返回GPSINFO对象指针,
// id: 所要读取的GPS模块编号
// gps_info: 返回的数据指针
// 返回值:结果0表示失败,其他值表示成功。
int gps_read(int id, GPSINFO *gps_info);

3.2.3. 3.2.3 输出函数

为了方便演示,定义打印输出函数如下所示:

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;
}

3.3. 3.3 自定义.so库

以上是自定义的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

4. 4 验证结果

程序运行以后,结果如下:

  • 正常调用的情况

    # 华云安公司地址:116.2549, 40.0793
    ========= GPS定位读取接口 =======
    =    纬度: 北纬: 40度 4分24秒    =
    =    经度: 东经:116度14分56秒    =
    ================================
    
  • 使用Hook加载的情况

    # 塞普鲁斯(地中海岛国): 25.0114, 35.0542
    ========= GPS定位读取接口 =======
    =    纬度: 北纬: 25度 0分41秒    =
    =    经度: 东经: 35度 3分15秒    =
    ================================
    

    5. 5 结论

    通过使用预加载,可以对系统的GPS模块的库进行Hook操作,从而在调用后修改GPS数据达到预期的效果。

6. 参考资料

[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

results matching ""

    No results matching ""