close

本文為看雪論壇優秀文章看雪論壇作者ID:珍惜Any

1


前言


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

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

2


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進程發送通知等。

3


netlink特點

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

(2)用戶空間可以使用標準的BSD socket接口(但netlink並沒有屏蔽掉協議包的構造與解析過程,推薦使用libnl等第三方庫);
(3)在內核空間使用專用的內核API接口;
(4)支持多播(因此支持「總線」式通信,可實現消息訂閱);
(5)在內核端可用於進程上下文與中斷上下文。

4


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 首先發起會話。

5


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

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

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

6


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

但是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<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(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

7


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 <fcntl.h>#include <stdio.h>#include <unistd.h>#include <string.h>#include <linux/seccomp.h>#include <sys/prctl.h> 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…\n」); prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); printf(「Writing to an already open file…\n」);//嘗試寫入write(output, val, strlen(val)+1);printf(「Trying to open file for reading…\n」); //設置完畢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\n」);}
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<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;}

程序完美運行起來,網卡獲取成功。
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


看雪ID:珍惜Any

https://bbs.pediy.com/user-home-819934.htm

*本文由看雪論壇 珍惜Any原創,轉載請註明來自看雪社區


#往期推薦

1.一個BLE智能手環的分析

2.VT虛擬化技術筆記

3.通過DWARF Expression將代碼隱藏在棧展開過程中

4.x86-頁式管理

5.HG532e漏洞復現(cve-2017-17215)

6.逆向某平台分析過程指導




球分享

球點讚

球在看

點擊「閱讀原文」,了解更多!

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()