<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Android netlink&svc 獲取 Mac方法深入分析

    VSole2022-04-03 10:57:57

    前言

    隨著市面上獲取指紋的方式越來越多,獲取的方式也千奇百怪。

    比如最開始的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%以上的改機軟件。

    netlink簡介

    Netlink是linux提供的用于內核和用戶態進程之間的通信方式。

    但是注意雖然Netlink主要用于用戶空間和內核空間的通信,但是也能用于用戶空間的兩個進程通信。

    只是進程間通信有其他很多方式,一般不用Netlink。除非需要用到Netlink的廣播特性時。

    NetLink機制是一種特殊的socket,它是Linux特有的,由于傳送的消息是暫存在socket接收緩存中,并不為接受者立即處理,所以netlink是一種異步通信機制。系統調用和ioctl是同步通信機制。

    一般來說用戶空間和內核空間的通信方式有三種:proc、ioctl、Netlink,而前兩種都是單向的,但是Netlink可以實現雙工通信。

    Netlink協議基于BSD socket和AF_NETLINK地址簇(address family)。

    使用32位的端口號尋址(以前稱為PID),每個Netlink協議(或稱作總線,man手冊中則稱之為netlink family),通常與一個或者一組內核服務/組件相關聯,如NETLINK_ROUTE用于獲取和設置路由與鏈路信息、NETLINK_KOBJECT_UEVENT用于內核向用戶空間的udev進程發送通知等。

    netlink特點

    (1)支持全雙工、異步通信;

    (2)用戶空間可以使用標準的BSD socket接口(但netlink并沒有屏蔽掉協議包的構造與解析過程,推薦使用libnl等第三方庫);

    (3)在內核空間使用專用的內核API接口;

    (4)支持多播(因此支持“總線”式通信,可實現消息訂閱);

    (5)在內核端可用于進程上下文與中斷上下文。

    netlink優點

    (1)netlink使用簡單,只需要在include/linux/netlink.h中增加一個新類型的 netlink 協議定義即可,(如 #define NETLINK_TEST 20 然后,內核和用戶態應用就可以立即通過 socket API 使用該 netlink 協議類型進行數據交換);

    (2)netlink是一種異步通信機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不需要等待接收者收到消息;

    (3)使用 netlink 的內核部分可以采用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴;

    (4)netlink 支持多播,內核模塊或應用可以把消息多播給一個netlink組,屬于該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性;

    (5)內核可以使用 netlink 首先發起會話。

    如何通過netlink獲取網卡信息?

    android 是如何通過netlink獲取網卡地址的?

    不管是ip命令行還是Java的network接口,最終都是調用到ifaddrs.cpp -> getifaddrs

    getifaddrs方法介紹

    源碼摘抄自:http://aospxref.com/android-10.0.0_r47/xref/bionic/libc/bionic/ifaddrs.cpp#236

    //傳入對應的結構體指針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;}
    

    NetlinkConnection這個結構體是一個netlink的封裝類,重點看一下ReadResponses的實現過程。

    代碼摘抄自:http://aospxref.com/android-10.0.0_r47/xref/bionic/libc/bionic/bionic_netlink.cpp

    /** * @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(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(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;    }}
    

    SVC內聯安全封裝:在接受消息的時候android源碼是采用recv去接受的消息

    通過循環的方式去判斷結束位置。

    /* * 獲取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(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(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;}
    

    但是recv這種函數很容易被hook,inlinehook recv ,recvfrom ,recvms,在方法執行完畢以后直接就可以處理參數二的返回值。

    在不直接使用系統提供的recv 以后,有兩種方式可以選擇:

    方法1:直接調用syscall函數,通過syscall函數進行切入到recv,這種方式可以更好的兼容32和64位,但是可能被直接hook syscall這個函數入口 。

    因為和設備指紋相關的函數,是重點函數,側重安全。所以重點采用方法2,將syscall 匯編代碼嵌入到指定方法內部。

    方法2:我們直接把recv換成svc內聯匯編代碼如下,相當于自己實現syscall (代碼摘抄自libc syscall)使用的話也很簡單,導入函數頭就好。

    extern "C" {    __inline__ __attribute__((always_inline))  long raw_syscall(long __number, ...);}
    

    32位:

       .text    .global raw_syscall    .type raw_syscall,%function raw_syscall:        MOV             R12, SP        STMFD           SP!, {R4-R7}        MOV             R7, R0        MOV             R0, R1        MOV             R1, R2        MOV             R2, R3        LDMIA           R12, {R3-R6}        SVC             0        LDMFD           SP!, {R4-R7}        mov             pc, lr
    

    64位:

       .text    .global raw_syscall    .type raw_syscall,@function raw_syscall:        MOV             X8, X0        MOV             X0, X1        MOV             X1, X2        MOV             X2, X3        MOV             X3, X4        MOV             X4, X5        MOV             X5, X6        SVC             0        RET
    

    將代碼替換成如下:

    while ((bytes_read = TEMP_FAILURE_RETRY(raw_syscall(__NR_recv,fd_, data_, size_, 0, NULL,0))) > 0) {    auto* hdr = reinterpret_cast(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(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;}
    

    很不幸,報錯了,安卓8內核上使用了seccomop 過濾掉了svc 直接調用 recv

    2022-03-02 21:47:13.753 5867-5867/? A/DEBUG: Build fingerprint: 'Xiaomi/cmi/cmi:11/RKQ1.200826.002/21.11.3:user/release-keys'2022-03-02 21:47:13.753 5867-5867/? A/DEBUG: Revision: '0'2022-03-02 21:47:13.753 5867-5867/? A/DEBUG: ABI: 'arm'2022-03-02 21:47:13.756 5867-5867/? A/DEBUG: Timestamp: 2022-03-02 21:47:13+08002022-03-02 21:47:13.756 5867-5867/? A/DEBUG: pid: 5773, tid: 5773, name: example.jnihook  >>> com.example.jnihook <<<2022-03-02 21:47:13.756 5867-5867/? A/DEBUG: uid: 100192022-03-02 21:47:13.756 5867-5867/? A/DEBUG: signal 31 (SIGSYS), code 1 (SYS_SECCOMP), fault addr --------2022-03-02 21:47:13.756 5867-5867/? A/DEBUG: Cause: seccomp prevented call to disallowed arm system call 2912022-03-02 21:47:13.756 5867-5867/? A/DEBUG:     r0  0000004c  r1  da3ea000  r2  00002000  r3  000000002022-03-02 21:47:13.756 5867-5867/? A/DEBUG:     r4  00000000  r5  00000000  r6  00000000  r7  000001232022-03-02 21:47:13.756 5867-5867/? A/DEBUG:     r8  00000000  r9  f1ff2e00  r10 ffeedb20  r11 f1ff2e002022-03-02 21:47:13.756 5867-5867/? A/DEBUG:     ip  ffeed9f8  sp  ffeed9e8  lr  c4459b03  pc  c445a3a42022-03-02 21:47:13.756 5867-5867/? A/DEBUG: backtrace:2022-03-02 21:47:13.757 5867-5867/? A/DEBUG:       #00 pc 0000c3a4  /data/app/~~O88Sqqnxjf7EjHid_THMIA==/com.example.jnihook-j2EVKCAjF3Cpu3p_RLym8A==/lib/arm/libhelloword.so (BuildId: 95d05421436486cc260cc32f813488b04b882b78)....
    

    報錯的原因一句話:seccomp prevented call to disallowed arm system call 291

    secomp簡介

    seccomp是Linux的一種安全機制,android 8.1以上使用了seccomp,主要功能是限制直接通過syscall去調用某些系統函數。

    seccomp的過濾模式有兩種(strict&filter),第一種strict只支持如下四種,如果一旦使用了其他的syscall 則會收到SIGKILL信號。

    read()
    write()
    exit()
    rt_sigreturn

    通過下面方式進行設置:

    seccomp(SECCOMP_SET_MODE_STRICT)prctl (PR_SET_SECCOMP, SECCOMP_MODE_STRICT)。
    
    strict
    #include #include #include #include #include #include  int main(int argc, char **argv){int output = open(“output.txt”, O_WRONLY);const char *val = “test”;//通過prctl函數設置seccomp的模式為strictprintf(“Calling prctl() to set seccomp strict mode…”); prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); printf(“Writing to an already open file…”);//嘗試寫入write(output, val, strlen(val)+1);printf(“Trying to open file for reading…”); //設置完畢seccomp以后再次嘗試open (因為設置了secomp的模式是strict,所以這行代碼直接sign -9 信號)int input = open(“output.txt”, O_RDONLY);printf(“You will not see this message — the process will be killed first”);}
    
    filter(BPF)

    Seccomp-bpf

    bpf是一種過濾模式,只有在linux高版本會存在該功能。當某進程調用了svc 首先會進入我們自己寫的bpf規則。

    通過我們自己的寫的規則,進行判斷該函數是否被運行調用。常用的就是ptrace+seccomp去修改svc的參數內容&返回值結果。

    在android底層 recv的實現是recvfom代碼如下:

    __BIONIC_FORTIFY_INLINEssize_t recv(int socket, void* const buf __pass_object_size0, size_t len, int flags)    __overloadable    __clang_error_if(__bos_unevaluated_lt(__bos0(buf), len),                     "'recv' called with size bigger than buffer") {  return recvfrom(socket, buf, len, flags, NULL, 0);}
    

    我們將svc調用號切換到recvform

    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(raw_syscall(__NR_recvfrom,fd_, data_, size_, 0, NULL,0))) > 0) {    auto* hdr = reinterpret_cast(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(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;}
    

    程序完美運行起來,網卡獲取成功。

    2022-03-02 22:05:53.790 11145-11145/com.example.jnihook E/Netlink: wlan0  A4:4B:D5:0B:51:57
    

    git地址:https://github.com/w296488320/getMacForNetlink 

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

    socketnetlink
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    也防止有人通過inlinehook 直接hook recv ,recvform,recvmsg 直接在收到數據包的時候被攔截和替換掉。
    影響版本:Linux v5.10-rc1 ~ v5.14.15。v5.14.16已修補。高危,可導致遠程提權,評分9.8。 默認不加載,需用戶配置。
    陳馳 ,2017年加入美團,目前主要負責IDC服務器的檢測防御產品研發,完善服務器側縱深防御體系建設。 楊一 ,2017年加入美團,目前主要負責HIDS主機安全產品研發工作。 胡鑫博 ,2021年加入美團,目前主要負責HIDS Agent的研發。 前言
    大廠基本為了程序的安全,會使用大量內聯SVC去調用系統函數,以此來保護程序的安全。如何實現SVC指令的IO重定向,成為最大的問題。內核態是當Linux需要處理文件,或者進行中斷IO等操作的時候就會進入內核態。當arm系列cpu發現svc指令的時候,就會陷入中斷,簡稱0x80中斷。
    C++ Socket詳解與研究
    2021-11-09 06:06:32
    數據傳輸是病毒木馬的必備技術之一,而數據回傳也成為了病毒木馬的一個重要特征,我們就嘗試自己寫一個程序來實現數據的傳輸,本文嘗試通過c++來進行套接字(socket)的實現
    Web 前端與后臺的通信短輪詢:通過不斷發送 http 請求達到即時通信的目的長輪詢:訪問無資源并不會立刻返回,保持較長時間的通訊,直到獲得數據或超時返回。
    ss命令用于顯示socket狀態。他可以顯示 PACKET sockets,TCP sockets,UDP sockets,DCCP sockets,RAW sockets,Unix domain sockets等等統計。它比其他工具展示等多tcp和state信息。它是一個非常實用、快速、有效的跟蹤IP連接和sockets的新工具。
    s等命令用于socket狀態。他可以顯示PACKET sockets,TCP sockets,UDP sockets,DCCP sockets,RAW sockets,Unix domain sockets。它比其他工具展示等多tcp和狀態信息。它是一個非常實用、快速、有效的跟蹤 IP 連接和套接字的新工具。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类