首页
社区
课程
招聘
[原创]Android netlink&svc 获取 Mac方法深入分析
发表于: 2022-3-2 22:48 37140

[原创]Android netlink&svc 获取 Mac方法深入分析

2022-3-2 22:48
37140

随着市面上获取指纹的方式越来越多,获取的方式也千奇百怪。

比如最开始的system_property_get , system_property_find , system_property_read 直接调用native底层获取。

到后来的进阶包括svc读取boot_id 文件,内存反射mValues 的 map 变量获取android id

(需要过掉反射限制) , 都是很不错的方法指纹获取方法。

今天主要介绍的是通过内核通讯的方式获取设备网卡mac指纹,主要通过netlink的方式和内核通讯去获取mac网卡地址 。

这种方式可以直接绕过android的权限。

在不给app授权的时候也可以直接获取到网卡信息。因为很难进行mock,所以很多大厂app也都是采用这种办法去获取。

我在原有的基础上继续完善了一下逻辑,在接收消息的时候通过内联svc的方式处理接收收到的数据包,大大增加了数据的安全性。

也防止有人通过inlinehook 直接hook recv ,recvform,recvmsg 直接在收到数据包的时候被拦截和替换掉。

理论上这种方式可以过掉99%以上的改机软件。

一般来说用户空间和内核空间的通信方式有三种:

proc

ioctl

Netlink

而前两种都是单向的,但是Netlink可以实现双工通信

Netlink协议基于BSD socket和AF_NETLINK地址簇(address family)。

使用32位的端口号寻址(以前称为PID),每个Netlink协议(或称作总线,man手册中则称之为netlink family),通常与一个或者一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。

1,支持全双工、异步通信

2,用户空间可以使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)

3,在内核空间使用专用的内核API接口

4,支持多播(因此支持“总线”式通信,可实现消息订阅)

5,在内核端可用于进程上下文与中断上下文

不管是ip命令行还是Java的network接口,最终都是调用到ifaddrs.cpp -> getifaddrs

源码摘抄自:

http://aospxref.com/android-10.0.0_r47/xref/bionic/libc/bionic/ifaddrs.cpp#236

NetlinkConnection这个结构体是一个netlink的封装类

重点看一下ReadResponses的实现过程

代码摘抄自:

http://aospxref.com/android-10.0.0_r47/xref/bionic/libc/bionic/bionic_netlink.cpp

通过遍历拿到我们需要的内容,输出即可。

在接受消息的时候android源码是采用recv去接受的消息

通过循环的方式去判断结束位置。

但是recv这种函数很容易被hook,inlinehook recv ,recvfrom ,recvmsg

在方法执行完毕以后直接就可以处理参数二的返回值。

在不直接使用系统提供的recv 以后,有两种方式可以选择。

直接调用syscall函数,通过syscall函数进行切入到recv 。

这种方式可以更好的兼容32和64位,但是可能被直接hook syscall这个函数入口 。

因为和设备指纹相关的函数,是重点函数,侧重安全。所以重点采用方法2

将syscall 汇编代码嵌入到指定方法内部。

方法2:我们直接把recv换成svc内联汇编代码如下

相当于自己实现syscall (代码摘抄自libc syscall)

使用的话也很简单,导入函数头就好。

将代码替换成如下:

很不幸,报错了,安卓8内核上使用了seccomop 过滤掉了svc 直接调用 recv

报错的原因一句话

seccomp prevented call to disallowed arm system call 291

seccomp是Linux的一种安全机制,android 8.1以上使用了seccomp

主要功能是限制直接通过syscall去调用某些系统函数

seccomp的过滤模式有两种(strict&filter)

第一种strict只支持如下四种,如果一旦使用了其他的syscall 则会收到SIGKILL信号

read()

write()

exit()

rt_sigreturn

通过下面方式进行设置。

Seccomp-bpf

bpf是一种过滤模式,只有在linux高版本会存在该功能

当某进程调用了svc 首先会进入我们自己写的bpf规则

通过我们自己的写的规则,进行判断该函数是否被运行调用。

常用的就是ptrace+seccomp去修改svc的参数内容&返回值结果。

回到正文,不过还好,

在android底层 recv的实现是recvfom代码如下

我们将svc调用号切换到recvform

程序完美运行起来,网卡获取成功。

https://github.com/w296488320/getMacForNetlink

参考文章:
https://blog.csdn.net/zhizhengguan/article/details/120448337

文章来源:
课程78-79课件。v296488320
图片描述

 
 
 
 
 
 
 
 
 
 
 
 
 
//传入对应的结构体指针
int getifaddrs(ifaddrs** out) {
  // We construct the result directly into `out`, so terminate the list.
  *out = nullptr;
 
  // Open the netlink socket and ask for all the links and addresses.
  NetlinkConnection nc;
  //判断get addresses 和 get link是否打开成功,返回成功则返回0
  bool okay = nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__getifaddrs_callback, out) &&
              nc.SendRequest(RTM_GETADDR) && nc.ReadResponses(__getifaddrs_callback, out);
  if (!okay) {
    out = nullptr;
    freeifaddrs(*out);
    // Ensure that callers crash if they forget to check for success.
    *out = nullptr;
    return -1;
  }
 
  return 0;
}
//传入对应的结构体指针
int getifaddrs(ifaddrs** out) {
  // We construct the result directly into `out`, so terminate the list.
  *out = nullptr;
 
  // Open the netlink socket and ask for all the links and addresses.
  NetlinkConnection nc;
  //判断get addresses 和 get link是否打开成功,返回成功则返回0
  bool okay = nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__getifaddrs_callback, out) &&
              nc.SendRequest(RTM_GETADDR) && nc.ReadResponses(__getifaddrs_callback, out);
  if (!okay) {
    out = nullptr;
    freeifaddrs(*out);
    // Ensure that callers crash if they forget to check for success.
    *out = nullptr;
    return -1;
  }
 
  return 0;
}
 
 
 
/**
 * @param type  发送参数的类型,具体获取的内容参考
 * @see rtnetlink.h
 * @return
 */
bool NetlinkConnection::SendRequest(int type) {
  // Rather than force all callers to check for the unlikely event of being
  // unable to allocate 8KiB, check here.
  // NetlinkConnection构造方法 的时候生成的8kb的data内存
  if (data_ == nullptr) return false;
 
  // Did we open a netlink socket yet?
  if (fd_ == -1) {
    //尝试建立socket netlink 链接
    fd_ = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
    if (fd_ == -1) return false;
  }
 
  // Construct and send the message.
  // 构造要发送的消息
  struct NetlinkMessage {
    nlmsghdr hdr;
    rtgenmsg msg;
  } request;
 
  memset(&request, 0, sizeof(request));
  request.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
  request.hdr.nlmsg_type = type;
  request.hdr.nlmsg_len = sizeof(request);
  // All families
  request.msg.rtgen_family = AF_UNSPEC;
  //使用socket数据发送
  return (TEMP_FAILURE_RETRY(send(fd_, &request, sizeof(request), 0)) == sizeof(request));
}
/**
 * @param type  发送参数的类型,具体获取的内容参考
 * @see rtnetlink.h
 * @return
 */
bool NetlinkConnection::SendRequest(int type) {
  // Rather than force all callers to check for the unlikely event of being
  // unable to allocate 8KiB, check here.
  // NetlinkConnection构造方法 的时候生成的8kb的data内存
  if (data_ == nullptr) return false;
 
  // Did we open a netlink socket yet?
  if (fd_ == -1) {
    //尝试建立socket netlink 链接
    fd_ = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
    if (fd_ == -1) return false;
  }
 
  // Construct and send the message.
  // 构造要发送的消息
  struct NetlinkMessage {
    nlmsghdr hdr;
    rtgenmsg msg;
  } request;
 
  memset(&request, 0, sizeof(request));
  request.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
  request.hdr.nlmsg_type = type;
  request.hdr.nlmsg_len = sizeof(request);
  // All families
  request.msg.rtgen_family = AF_UNSPEC;
  //使用socket数据发送
  return (TEMP_FAILURE_RETRY(send(fd_, &request, sizeof(request), 0)) == sizeof(request));
}
/*
 * 获取socket的返回结果
 */
bool NetlinkConnection::ReadResponses(void callback(void*, nlmsghdr*), void* context) {
  // Read through all the responses, handing interesting ones to the callback.
  ssize_t bytes_read;
  while ((bytes_read = TEMP_FAILURE_RETRY(recv(fd_, data_, size_, 0))) > 0) {
    //将拿到的data数据进行赋值
    auto* hdr = reinterpret_cast<nlmsghdr*>(data_);
 
    for (; NLMSG_OK(hdr, static_cast<size_t>(bytes_read)); hdr = NLMSG_NEXT(hdr, bytes_read)) {
      //判断是否读取结束,否则读取callback
      if (hdr->nlmsg_type == NLMSG_DONE) return true;
      if (hdr->nlmsg_type == NLMSG_ERROR) {
        auto* err = reinterpret_cast<nlmsgerr*>(NLMSG_DATA(hdr));
        errno = (hdr->nlmsg_len >= NLMSG_LENGTH(sizeof(nlmsgerr))) ? -err->error : EIO;
        return false;
      }
      //处理具体逻辑
      callback(context, hdr);
    }
  }
 
  // We only get here if recv fails before we see a NLMSG_DONE.
  return false;
}
/*
 * 获取socket的返回结果
 */
bool NetlinkConnection::ReadResponses(void callback(void*, nlmsghdr*), void* context) {
  // Read through all the responses, handing interesting ones to the callback.
  ssize_t bytes_read;
  while ((bytes_read = TEMP_FAILURE_RETRY(recv(fd_, data_, size_, 0))) > 0) {
    //将拿到的data数据进行赋值
    auto* hdr = reinterpret_cast<nlmsghdr*>(data_);
 
    for (; NLMSG_OK(hdr, static_cast<size_t>(bytes_read)); hdr = NLMSG_NEXT(hdr, bytes_read)) {
      //判断是否读取结束,否则读取callback
      if (hdr->nlmsg_type == NLMSG_DONE) return true;
      if (hdr->nlmsg_type == NLMSG_ERROR) {
        auto* err = reinterpret_cast<nlmsgerr*>(NLMSG_DATA(hdr));
        errno = (hdr->nlmsg_len >= NLMSG_LENGTH(sizeof(nlmsgerr))) ? -err->error : EIO;
        return false;
      }
      //处理具体逻辑
      callback(context, hdr);
    }
  }
 
  // We only get here if recv fails before we see a NLMSG_DONE.
  return false;
}
int listmacaddrs(void) {
    struct ifaddrs *ifap, *ifaptr;
 
    if (myGetifaddrs(&ifap) == 0) {
        for (ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) {
            char macp[INET6_ADDRSTRLEN];
            if(ifaptr->ifa_addr!= nullptr) {
                if (((ifaptr)->ifa_addr)->sa_family == AF_PACKET) {
                    auto *sockadd = (struct sockaddr_ll *) (ifaptr->ifa_addr);
                    int i;
                    int len = 0;
                    for (i = 0; i < 6; i++) {
                        len += sprintf(macp + len, "%02X%s", sockadd->sll_addr[i],( i < 5 ? ":" : ""));
                    }
                    //LOGE("%s  %s  ",(ifaptr)->ifa_name,macp)
                    if(strcmp(ifaptr->ifa_name,"wlan0")== 0){
                        LOGE("%s  %s  ",(ifaptr)->ifa_name,macp)
                        freeifaddrs(ifap);
                        return 1;
                    }
                }
            }
 
        }
        freeifaddrs(ifap);
        return 0;
    } else {
        return 0;
    }
}
int listmacaddrs(void) {
    struct ifaddrs *ifap, *ifaptr;
 
    if (myGetifaddrs(&ifap) == 0) {
        for (ifaptr = ifap; ifaptr != NULL; ifaptr = (ifaptr)->ifa_next) {
            char macp[INET6_ADDRSTRLEN];
            if(ifaptr->ifa_addr!= nullptr) {
                if (((ifaptr)->ifa_addr)->sa_family == AF_PACKET) {
                    auto *sockadd = (struct sockaddr_ll *) (ifaptr->ifa_addr);
                    int i;
                    int len = 0;
                    for (i = 0; i < 6; i++) {
                        len += sprintf(macp + len, "%02X%s", sockadd->sll_addr[i],( i < 5 ? ":" : ""));
                    }
                    //LOGE("%s  %s  ",(ifaptr)->ifa_name,macp)
                    if(strcmp(ifaptr->ifa_name,"wlan0")== 0){
                        LOGE("%s  %s  ",(ifaptr)->ifa_name,macp)
                        freeifaddrs(ifap);
                        return 1;
                    }
                }
            }
 
        }
        freeifaddrs(ifap);
        return 0;
    } else {
        return 0;
    }
}
 
/*
 * 获取socket的返回结果
 */
bool NetlinkConnection::ReadResponses(void callback(void*, nlmsghdr*), void* out) {
  // Read through all the responses, handing interesting ones to the callback.
  ssize_t bytes_read;
 
//  while ((bytes_read = TEMP_FAILURE_RETRY(recv(fd_, data_, size_, 0))) > 0) {
//  while ((bytes_read = TEMP_FAILURE_RETRY(recvfrom(fd_, data_, size_, 0 ,NULL,0))) > 0) {
 
while ((bytes_read = TEMP_FAILURE_RETRY(raw_syscall(__NR_recvfrom,fd_, data_, size_, 0, NULL,0))) > 0) {
    auto* hdr = reinterpret_cast<nlmsghdr*>(data_);
    for (; NLMSG_OK(hdr, static_cast<size_t>(bytes_read)); hdr = NLMSG_NEXT(hdr, bytes_read)) {
 
      if (hdr->nlmsg_type == NLMSG_DONE) return true;
      if (hdr->nlmsg_type == NLMSG_ERROR) {
        auto* err = reinterpret_cast<nlmsgerr*>(NLMSG_DATA(hdr));
        errno = (hdr->nlmsg_len >= NLMSG_LENGTH(sizeof(nlmsgerr))) ? -err->error : EIO;
        return false;
      }
 
      callback(out, hdr);
    }
  }
 
  // We only get here if recv fails before we see a NLMSG_DONE.
  return false;
}
/*
 * 获取socket的返回结果
 */
bool NetlinkConnection::ReadResponses(void callback(void*, nlmsghdr*), void* out) {
  // Read through all the responses, handing interesting ones to the callback.
  ssize_t bytes_read;
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-8-3 16:59 被珍惜Any编辑 ,原因:
上传的附件:
收藏
免费 14
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/04/06 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (29)
雪    币: 207
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
6
2022-3-2 23:03
0
雪    币: 1867
活跃值: (3958)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
做出来卖钱吧
2022-3-3 08:31
0
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
感谢大佬,以前听说过这个netlink,但是一直不知道如何使用,今天看了大佬的分析终于学到了,太感谢了!!
2022-3-3 09:23
1
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5

大佬 安卓10测试过了可以获取,但是对于mac地址随机化,用你这种方法获取的mac地址会不会发生变化?

最后于 2022-3-3 09:42 被onlythis编辑 ,原因:
2022-3-3 09:42
0
雪    币: 2291
活跃值: (2185)
能力值: (RANK:400 )
在线值:
发帖
回帖
粉丝
6
总结得很到位!
2022-3-3 10:00
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
7
onlythis 大佬&nbsp;安卓10测试过了可以获取,但是对于mac地址随机化,用你这种方法获取的mac地址会不会发生变化?
没测试过 哈哈哈 可以试一下
2022-3-3 10:09
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
8
莫灰灰 [em_62]总结得很到位!
2022-3-3 10:47
0
雪    币: 891
活跃值: (591)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
onlythis 大佬&nbsp;安卓10测试过了可以获取,但是对于mac地址随机化,用你这种方法获取的mac地址会不会发生变化?
肯定随机啦
2022-3-3 11:35
0
雪    币: 4
活跃值: (517)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
2年前还是很有用的(不过时的时候也没人发!!!),可惜随机化了,target 30更直接挂了
2022-3-3 14:41
0
雪    币: 1144
活跃值: (1254)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
1.合规问题海外mac地址相关都不能采集
2.target30以上Android11之后都获取不到
3.很多设备指纹已经开始去mac化了
2022-3-4 10:50
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
12
seandong 1.合规问题海外mac地址相关都不能采集 2.target30以上Android11之后都获取不到 3.很多设备指纹已经开始去mac化了
是的,android 11可以获取,但是12不行 。
2022-3-4 10:55
0
雪    币: 1490
活跃值: (9913)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
13
关注关注。
2022-3-4 16:48
0
雪    币: 6055
活跃值: (6272)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
关注关注。
2022-3-7 17:23
0
雪    币: 252
活跃值: (3213)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
15
现在风控都不看设备指纹了
2022-3-10 10:55
0
雪    币: 4
活跃值: (517)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
gtict 现在风控都不看设备指纹了
设备指纹是风控的一个维度,要是做的不行的确实不需要看了,反正变来变去。也不知道
2022-3-27 17:41
0
雪    币: 267
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
谢谢,大佬分享
2022-4-21 18:24
0
雪    币: 0
活跃值: (270)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
非常感谢大佬对技术的分析,引用牛顿的话,我真是站在巨人的肩膀上
2022-4-22 10:58
0
雪    币: 83
活跃值: (304)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
去年分析过 ks里面有用到
2022-4-22 11:19
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
20
wanglisong 去年分析过 ks里面有用到
他用的是al的
2022-4-23 13:34
0
雪    币: 154
活跃值: (3786)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
21
牛逼
2022-7-29 10:53
0
雪    币: 38
活跃值: (859)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
珍惜Any 他用的是al的
al是什么
2022-9-22 11:18
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
23
ali
2022-9-22 11:23
0
雪    币: 3355
活跃值: (14008)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
24
cnhuaWu al是什么
ali
2022-9-22 11:23
0
雪    币: 12
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
学到了~
2022-10-17 14:11
0
游客
登录 | 注册 方可回帖
返回
//