高德位置服务浅析

前言

在分析华为 agps 的位置获取具体实现逻辑时,通过抓包发现有一条发送到高德的数据流;
agps 的原理是手机通过移动网络向基站的 agps 服务器请求得到当前位置的卫星星历信息(包括仰角、轨道等),拿到星历信息后再 进行运算出当前设备的伪距信息,最后将伪距信息传送到 agps 服务器并最终得到手机终端的具体 gps 信息返回到设备。
通过抓包得到的是一条 走 wifi 流量的高德数据;是在返回当前位置的时候开始发送的(onLocationChanged 函数返回);

当前的手机是华为手机,在打算分析华为 rom 的时候,拆解 rom,发现有个 高德位置服务.apk ,遂分析该 apk。
包名是: com.amap.android.ams

结论:

高德位置服务会采集当前设备内很多信息。

抓到的数据流

上报的数据是经过 gzip 的编码处理;
可以将抓包数据转成 byte 流之后,再试用 gzip -d filename 命令解码;

POST /APS/r?ver=4.9&q=0&csid=5ff08bb5-bda6-47fc-38ca-30f5b5d2f33b HTTP/1.1
gzipped: 1
Accept-Encoding: gzip
et: 111
Content-Type: application/octet-stream
User-Agent: Dalvik/2.1.0 (Linux; U; Android 8.0.0; EDI-AL10 Build/HUAWEIEDISON-AL10)
Host: aps.amap.com
Connection: Keep-Alive
Content-Length: 895

HTTP/1.1 200 
Date: Thu, 04 Jun 2020 06:18:51 GMT
Content-Type: application/octet-stream;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
retype: -1
rdesc: 
content-encoding: gzip
Server: Tengine/Aserver
EagleEye-TraceId: 0e0fb40115912515318368299efd77
Timing-Allow-Origin: *

gzip 的解码代码

#include <zlib.h>
#include <assert.h>
int inflate_read(char *source,int len,char **dest,int gzip) {

    LOGTEST("[inflate_read][IN]");
    int CHUNK = 2048;
    int  ret;
    unsigned  have;
    z_stream  strm;
    unsigned  char  out[CHUNK];
    int  totalsize  =  0;

    /*  allocate  inflate  state  */
    strm.zalloc  =  Z_NULL;
    strm.zfree  =  Z_NULL;
    strm.opaque  =  Z_NULL;
    strm.avail_in  =  0;
    strm.next_in  =  Z_NULL;
    LOGTEST("[inflate_read][step 0]");
    if (gzip)
    ret = inflateInit2(&strm,  47);
    else
    ret = inflateInit(&strm);

    LOGTEST("[inflate_read][step 1 ret %d ]", ret);
    if (ret !=  Z_OK)
    return  ret;

    strm.avail_in  =  975;
    strm.next_in  =  (Bytef*)source;
    LOGTEST("[inflate_read][step 2]");

    /*  run  inflate()  on  input  until  output  buffer  not  full  */

        strm.avail_out  =  2048;
        strm.next_out  =  out;
        ret  =  inflate(&strm,  Z_NO_FLUSH);
        assert(ret  !=  Z_STREAM_ERROR);    /*  state  not  clobbered  */

        switch  (ret)  {
                //Z_OK
                //Z_STREAM_END    1
                //Z_NEED_DICT     2
                //Z_ERRNO        (-1)
                //Z_STREAM_ERROR (-2)
                //Z_DATA_ERROR   (-3)
                //Z_MEM_ERROR    (-4)
                //Z_BUF_ERROR    (-5)
                //Z_VERSION_ERROR (-6)
            case  Z_NEED_DICT:
            ret  =  Z_DATA_ERROR;          /*  and  fall  through  */
            case  Z_DATA_ERROR:
            case  Z_MEM_ERROR:
                inflateEnd(&strm);
                return  ret;
        }

        have  =  CHUNK  -  strm.avail_out;
        totalsize  +=  have;
        char* resu = (char*)malloc(have);
        memcpy(resu, out, have);


    /*  clean  up  and  return  */
    LOGTEST("[inflate_read][step 4]");
    (void)inflateEnd(&strm);

    LOGTEST("[inflate_read][out]");
    return  ret  ==  Z_STREAM_END  ?  Z_OK  :  Z_DATA_ERROR;
}

int mini_gz_get_data_offset(const unsigned char *buffer, int bufferlength)
{
    int offset;
    int result = 0;

    if (10 < bufferlength)
    {
        //buffer + 0;       // .gz header

        if (buffer[0] == 0x1F && buffer[1] == 0x8B && buffer[2] == 0x08)
        {
            // auxillary header
            offset = 10;

            if (buffer[3] & 0x4)
            {
                //fextra_len = buffer[offset + 1] << 8 | buffer[offset + 0];
                offset += 2;
                //fextra_ptr = &buffer[offset];
            }
            if (buffer[3] & 0x8)
            {
                //fname_ptr = &buffer[offset];
                while (buffer[offset++] != '\0')
                {
                    ;
                }
            }
            if (buffer[3] & 0x10)
            {
                //fcomment_ptr = &buffer[offset];
                while (buffer[offset++] != '\0')
                {
                    ;
                }
            }
            if (buffer[3] & 0x2)
            {
                //fcrc = *(unsigned short *)&buffer[offset];
                offset += 2;
            }

            result = offset;
        }
    }
    return (result);
}
        char* source = "123";
    int len = 123;
    char **dest;
    int gzip  = 1;
    int miniGz = mini_gz_get_data_offset((const unsigned char *)source, len);
    inflate_read(source, len, dest, gzip);

静态分析 apk

他们采集的信息包括:


altitude 
speed
bearing
retype
rdesc
citycode
desc
adcode
country
province
city
district
road
street
number
aoiname
poiname
cens
floor
coord
mcell
offpct
provider
lon
lat
accuracy
type

"type"
"mcc"
"mnc"
"lac"
"cid"
"sid"
"nid"
"bid"
"signalStrength"
"latitude"
"longitude"
"cellAge"
"lastUpdateTimeMills"
"registered"

"updateTime"
"cellType"
"networkOperator"
"mainCell"
"mainWifi"
"updateTime"

"curTime"
"ver", "4.9"
"action", 1
"respctrl"
"src"
"license"
"extrakey"
"srvip"
"model"
"os"
"phoneNum"
"appName"
"imei"
"imsi"
"smac"
"sdkVersion"
"collectionVersion"
"utdid"
"adiu"
"nettype"
"inftype"
"gtype"
v1, this.t
"macsAge"
v1, this.v
"poiid"
"context"

"csid
loc"
"request"

截取代码:

.
                case 1: {
                    v0_1.put("altitude", this.e);
                    v0_1.put("speed", ((double)this.g));
                    v0_1.put("bearing", ((double)this.h));
                    v0_1.put("retype", this.k);
                    v0_1.put("rdesc", this.l);
                    v0_1.put("citycode", this.m);
                    v0_1.put("desc", this.n);
                    v0_1.put("adcode", this.o);
                    v0_1.put("country", this.p);
                    v0_1.put("province", this.q);
                    v0_1.put("city", this.r);
                    v0_1.put("district", this.s);
                    v0_1.put("road", this.t);
                    v0_1.put("street", this.u);
                    v0_1.put("number", this.v);
                    v0_1.put("aoiname", this.w);
                    v0_1.put("poiname", this.x);
                    v0_1.put("cens", this.y);
                    v0_1.put("poiid", this.z);
                    v0_1.put("floor", this.A);
                    v0_1.put("coord", this.C);
                    v0_1.put("mcell", this.D);
                    if(this.E == null) {
                        goto label_82;
                    }

                    if(!v0_1.has("offpct")) {
                        goto label_82;
                    }

                    v0_1.put("offpct", this.E.getString("offpct"));
                    goto label_82;
                }
                case 2: {
                label_82:
                    v0_1.put("time", this.i);
                    goto label_85;
                }
                case 3: {
                label_85:
                    v0_1.put("provider", this.b);
                    v0_1.put("lon", this.c);
                    v0_1.put("lat", this.d);
                    v0_1.put("accuracy", ((double)this.f));
                    v0_1.put("type", this.j);
                    return v0_1;
                }
.


.
        try {
            v1.put("curTime", this.A);
            v1.put("ver", "4.9");
            v1.put("action", 1);
            v1.put("respctrl", this.a);
            v1.put("src", this.b);
            v1.put("license", this.c);
            v1.put("extrakey", this.d);
            v1.put("srvip", this.e);
            v1.put("model", this.f);
            v1.put("os", this.g);
            v1.put("phoneNum", this.h);
            v1.put("appName", this.i);
            v1.put("imei", this.j);
            v1.put("imsi", this.k);
            v1.put("smac", i.a(this.l));
            v1.put("sdkVersion", this.m);
            v1.put("collectionVersion", this.n);
            v1.put("utdid", this.o);
            v1.put("adiu", this.p);
            v1.put("nettype", this.q);
            v1.put("inftype", this.r);
            v1.put("gtype", this.s);
            this.a(v1, this.t);
            v1.put("macsAge", this.u);
            this.a(v1, this.v);
            v1.put("poiid", this.w);
            v1.put("context", this.x);
        }
        catch(Exception v0) {
            a.a("@_19_4_@", "@_19_4_1_@", v0);
.



.
    private JSONObject a(com.amap.location.c.e.b arg5) throws JSONException {
        JSONObject v0;
        if(arg5 == null) {
            v0 = null;
        }
        else {
            v0 = new JSONObject();
            v0.put("type", arg5.a);
            v0.put("mcc", arg5.b);
            v0.put("mnc", arg5.c);
            v0.put("lac", arg5.d);
            v0.put("cid", arg5.e);
            v0.put("sid", arg5.f);
            v0.put("nid", arg5.g);
            v0.put("bid", arg5.h);
            v0.put("signalStrength", arg5.i);
            v0.put("latitude", arg5.j);
            v0.put("longitude", arg5.k);
            v0.put("cellAge", arg5.l);
            v0.put("lastUpdateTimeMills", arg5.m);
            v0.put("registered", arg5.n);
        }

        return v0;
    }
.


.
    private void a(JSONObject arg7, d arg8) throws JSONException {
        JSONArray v4;
        JSONObject v1 = new JSONObject();
        c v2 = arg8.a;
        if(v2 != null) {
            JSONObject v3 = new JSONObject();
            v3.put("updateTime", v2.a);
            v3.put("cellType", v2.b);
            v3.put("networkOperator", v2.c);
            v3.put("mainCell", this.a(v2.d));
            if(v2.e != null && v2.e.size() > 0) {
                v4 = new JSONArray();
                Iterator v5 = v2.e.iterator();
                while(v5.hasNext()) {
                    v4.put(this.a(v5.next()));
                }

                v3.put("neighbors", v4);
            }

            v3.put("mainCell2", this.a(v2.f));
            if(v2.g != null && v2.g.size() > 0) {
                v4 = new JSONArray();
                Iterator v2_1 = v2.g.iterator();
                while(v2_1.hasNext()) {
                    v4.put(this.a(v2_1.next()));
                }

                v3.put("cell2", v4);
            }

            v1.put("cellStatus", v3);
        }

        f v0 = arg8.b;
        if(v0 != null) {
            JSONObject v2_2 = new JSONObject();
            v2_2.put("updateTime", v0.a);
            v2_2.put("mainWifi", this.a(v0.b));
            List v0_1 = v0.b();
            if(v0_1 != null && v0_1.size() > 0) {
                JSONArray v3_1 = new JSONArray();
                Iterator v4_1 = v0_1.iterator();
                while(v4_1.hasNext()) {
                    v3_1.put(this.a(v4_1.next()));
                }

                v2_2.put("wifiList", v3_1);
            }

            v1.put("wifiStatus", v2_2);
        }

        arg7.put("fps", v1);
    }
.
.
    public JSONObject a() {
        JSONObject v1 = new JSONObject();
        try {
            v1.put("curTime", this.A);
            v1.put("ver", "4.9");
            v1.put("action", 1);
            v1.put("respctrl", this.a);
            v1.put("src", this.b);
            v1.put("license", this.c);
            v1.put("extrakey", this.d);
            v1.put("srvip", this.e);
            v1.put("model", this.f);
            v1.put("os", this.g);
            v1.put("phoneNum", this.h);
            v1.put("appName", this.i);
            v1.put("imei", this.j);
            v1.put("imsi", this.k);
            v1.put("smac", i.a(this.l));
            v1.put("sdkVersion", this.m);
            v1.put("collectionVersion", this.n);
            v1.put("utdid", this.o);
            v1.put("adiu", this.p);
            v1.put("nettype", this.q);
            v1.put("inftype", this.r);
            v1.put("gtype", this.s);
            this.a(v1, this.t);
            v1.put("macsAge", this.u);
            this.a(v1, this.v);
            v1.put("poiid", this.w);
            v1.put("context", this.x);
        }
        catch(Exception v0) {
            a.a("@_19_4_@", "@_19_4_1_@", v0);
        }

        return v1;
    }
.

.
            v0 = ((a)v0).getResultData();
            if(g.l(this.a) < 5 && v0 != null && (((com.amap.location.c.e.a)v0).A())) {
                com.amap.location.c.d.a.d("@_16_3_@", "@_16_3_13_@");
                try {
                    com.amap.location.f.a.c v3_1 = com.amap.location.f.a.d.c();
                    if(v3_1 == null) {
                        goto label_291;
                    }

                    JSONObject v4 = new JSONObject();
                    v4.put("csid", v2.i());
                    v4.put("loc", ((com.amap.location.c.e.a)v0).c(1));
                    v4.put("request", v2.h().a());
                    v3_1.logServerParseErrorRequest(Base64.encodeToString(v4.toString().getBytes("UTF-8"), 2));     // 从这里大概可以知道个差不多了
                    g.m(this.a);
                }
.