continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
#### MAC地址
##### 介绍
用来定义网络设备的位置。一个主机会有一个 MAC 地址,MAC 地址是网卡决定的,是固定的,为了保护用户隐私**苹果已经禁止读取**这个标识了。
##### 获取方式
###### 正常获取MAC地址
结果是:020000000000
-
(NSString *)getmacaddress2
{int mib[6];
size_t len;
char *buf;
unsigned char *ptr;
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;if ((mib[5] = if_nametoindex(“en0”)) == 0) {
printf(“Error: if_nametoindex error/n”);
return NULL;
}if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf(“Error: sysctl, take 1/n”);
return NULL;
}if ((buf = malloc(len)) == NULL) {
printf(“Could not allocate memory. error!/n”);
return NULL;
}if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
printf(“Error: sysctl, take 2”);
return NULL;
}ifm = (struct if_msghdr *)buf;
sdl = (struct sockaddr_dl *)(ifm + 1);
ptr = (unsigned char *)LLADDR(sdl);
NSString *outstring = [NSString stringWithFormat:@“%02x:%02x:%02x:%02x:%02x:%02x”, *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];// NSString *outstring = [NSString stringWithFormat:@“%02x%02x%02x%02x%02x%02x”, *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
NSLog(@“outString:%@”, outstring);
free(buf);
return [outstring uppercaseString];
}
###### 获取对应Wi-Fi的MAC地址
不同的Wi-Fi的MAC地址不同
-
(nullable NSString *)getMacAddress3 {
res_9_init();
int len;
//get currnet ip address
NSString *ip = [self currentIPAddressOf:IOS_WIFI];
if(ip == nil) {
fprintf(stderr, “could not get current IP address of en0\n”);
return DUMMY_MAC_ADDR;
}//end if//set port and destination
_res.nsaddr_list[0].sin_family = AF_INET;
_res.nsaddr_list[0].sin_port = htons(MDNS_PORT);
_res.nsaddr_list[0].sin_addr.s_addr = [self IPv4Pton:ip];
_res.nscount = 1;unsigned char response[NS_PACKETSZ];
//send mdns query
if((len = res_9_query(QUERY_NAME, ns_c_in, ns_t_ptr, response, sizeof(response))) < 0) {fprintf(stderr, "res_search(): %s\n", hstrerror(h_errno)); return DUMMY_MAC_ADDR;
}//end if
//parse mdns message
ns_msg handle;
if(ns_initparse(response, len, &handle) < 0) {
fprintf(stderr, “ns_initparse(): %s\n”, hstrerror(h_errno));
return DUMMY_MAC_ADDR;
}//end if//get answer length
len = ns_msg_count(handle, ns_s_an);
if(len < 0) {
fprintf(stderr, “ns_msg_count return zero\n”);
return DUMMY_MAC_ADDR;
}//end if//try to get mac address from data
NSString *macAddress = nil;
for(int i = 0 ; i < len ; i++) {
ns_rr rr;
ns_parserr(&handle, ns_s_an, 0, &rr);if(ns_rr_class(rr) == ns_c_in && ns_rr_type(rr) == ns_t_ptr && !strcmp(ns_rr_name(rr), QUERY_NAME)) { char *ptr = (char *)(ns_rr_rdata(rr) + 1); int l = (int)strcspn(ptr, "@"); char *tmp = calloc(l + 1, sizeof(char)); if(!tmp) { perror("calloc()"); continue; }//end if memcpy(tmp, ptr, l); macAddress = [NSString stringWithUTF8String:tmp]; free(tmp); }//end if
}//end for each
macAddress = macAddress ? macAddress : DUMMY_MAC_ADDR;
return macAddress;
}//end -
(nonnull NSString *)currentIPAddressOf: (nonnull NSString *)device {
struct ifaddrs *addrs;
NSString *ipAddress = nil;if(getifaddrs(&addrs) != 0) {
return nil;
}//end if//get ipv4 address
for(struct ifaddrs *addr = addrs ; addr ; addr = addr->ifa_next) {
if(!strcmp(addr->ifa_name, [device UTF8String])) {
if(addr->ifa_addr) {
struct sockaddr_in *in_addr = (struct sockaddr_in *)addr->ifa_addr;
if(in_addr->sin_family == AF_INET) {
ipAddress = [self IPv4Ntop:in_addr->sin_addr.s_addr];
break;
}//end if
}//end if
}//end if
}//end forfreeifaddrs(addrs);
return ipAddress;
}//end currentIPAddressOf: -
(nullable NSString *)IPv4Ntop: (in_addr_t)addr {
char buffer[INET_ADDRSTRLEN] = {0};
return inet_ntop(AF_INET, &addr, buffer, sizeof(buffer)) ?
[NSString stringWithUTF8String:buffer] : nil;
}//end IPv4Ntop: -
(in_addr_t)IPv4Pton: (nonnull NSString *)IPAddr {
in_addr_t network = INADDR_NONE;
return inet_pton(AF_INET, [IPAddr UTF8String], &network) == 1 ?
network : INADDR_NONE;
}//end IPv4Pton
#### 系统版本
##### 获取方式
- (NSString *)getSystemVersion
{
return [[UIDevice currentDevice] systemVersion];
}
#### 设备型号
##### 获取方式
#import <sys/utsname.h>
-
(NSString *)getDeviceModel
{
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];if ([deviceString isEqualToString:@“iPhone3,1”]) return @“iPhone 4”;
if ([deviceString isEqualToString:@“iPhone3,2”]) return @“iPhone 4”;
if ([deviceString isEqualToString:@“iPhone3,3”]) return @“iPhone 4”;
if ([deviceString isEqualToString:@“iPhone4,1”]) return @“iPhone 4S”;
if ([deviceString isEqualToString:@“iPhone5,1”]) return @“iPhone 5”;
if ([deviceString isEqualToString:@“iPhone5,2”]) return @“iPhone 5 (GSM CDMA)”;
if ([deviceString isEqualToString:@“iPhone5,3”]) return @“iPhone 5c (GSM)”;
if ([deviceString isEqualToString:@“iPhone5,4”]) return @“iPhone 5c (GSM CDMA)”;
if ([deviceString isEqualToString:@“iPhone6,1”]) return @“iPhone 5s (GSM)”;
if ([deviceString isEqualToString:@“iPhone6,2”]) return @“iPhone 5s (GSM CDMA)”;
if ([deviceString isEqualToString:@“iPhone7,1”]) return @“iPhone 6 Plus”;
if ([deviceString isEqualToString:@“iPhone7,2”]) return @“iPhone 6”;
if ([deviceString isEqualToString:@“iPhone8,1”]) return @“iPhone 6s”;
if ([deviceString isEqualToString:@“iPhone8,2”]) return @“iPhone 6s Plus”;
if ([deviceString isEqualToString:@“iPhone8,4”]) return @“iPhone SE”;
if ([deviceString isEqualToString:@“iPhone9,1”]) return @“iPhone 7”;
if ([deviceString isEqualToString:@“iPhone9,2”]) return @“iPhone 7 Plus”;
if ([deviceString isEqualToString:@“iPhone9,3”]) return @“iPhone 7”;
if ([deviceString isEqualToString:@“iPhone9,4”]) return @“iPhone 7 Plus”;
if ([deviceString isEqualToString:@“iPhone10,1”]) return @“iPhone 8”;
if ([deviceString isEqualToString:@“iPhone10,4”]) return @“iPhone 8”;
if ([deviceString isEqualToString:@“iPhone10,2”]) return @“iPhone 8 Plus”;
if ([deviceString isEqualToString:@“iPhone10,5”]) return @“iPhone 8 Plus”;
if ([deviceString isEqualToString:@“iPhone10,3”]) return @“iPhone X”;
if ([deviceString isEqualToString:@“iPhone10,6”]) return @“iPhone X”;
if ([deviceString isEqualToString:@“iPhone11,2”]) return @“iPhone XS”;
if ([deviceString isEqualToString:@“iPhone11,4”]) return @“iPhone XS Max”;
if ([deviceString isEqualToString:@“iPhone11,6”]) return @“iPhone XS Max”;
if ([deviceString isEqualToString:@“iPhone11,8”]) return @“iPhone XR”;
if ([deviceString isEqualToString:@“iPhone12,1”]) return @“iPhone 11”;
if ([deviceString isEqualToString:@“iPhone12,3”]) return @“iPhone 11 Pro”;
if ([deviceString isEqualToString:@“iPhone12,5”]) return @“iPhone 11 Pro Max”;
if ([deviceString isEqualToString:@“iPhone12,8”]) return @“iPhone SE (2nd generation)”;
if ([deviceString isEqualToString:@“iPhone13,1”]) return @“iPhone 12 mini”;
if ([deviceString isEqualToString:@“iPhone13,2”]) return @“iPhone 12”;
if ([deviceString isEqualToString:@“iPhone13,3”]) return @“iPhone 12 Pro”;
if ([deviceString isEqualToString:@“iPhone13,4”]) return @“iPhone 12 Pro Max”;
if ([deviceString isEqualToString:@“iPhone14,2”]) return @“iPhone 13 Pro”;
if ([deviceString isEqualToString:@“iPhone14,2”]) return @“iPhone 13 Pro”;
if ([deviceString isEqualToString:@“iPhone14,2”]) return @“iPhone 13 Pro”;
if ([deviceString isEqualToString:@“iPhone14,3”]) return @“iPhone 13 Pro Max”;
if ([deviceString isEqualToString:@“iPhone14,4”]) return @“iPhone 13 mini”;
if ([deviceString isEqualToString:@“iPhone14,5”]) return @“iPhone 13”;return deviceString;
}
#### 设备名称
##### 获取方式
- (NSString *)getDeviceName
{
return [UIDevice currentDevice].name;
}
#### 磁盘大小
##### 获取方式
- (long)getDiskTotalSize
{
NSDictionary *systemAttributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:nil];
NSNumber *diskTotalSize = [systemAttributes objectForKey:NSFileSystemSize];
return (long)(diskTotalSize.floatValue / 1024.f / 1024.f);
}
#### 磁盘剩余空间
##### 获取方式
- (long)getDiskFreeSize
{
NSDictionary *systemAttributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:nil];
NSNumber *diskFreeSize = [systemAttributes objectForKey:NSFileSystemFreeSize];
return (long)(diskFreeSize.floatValue / 1024.f / 1024.f);
}
#### 电量
##### 获取方式
- (CGFloat)getBatteryLevel
{
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
return [[UIDevice currentDevice] batteryLevel];
}
#### 电池状态
##### 获取方式
- (NSString *)getBatteryState
{
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
UIDeviceBatteryState batteryState = [UIDevice currentDevice].batteryState;
switch (batteryState) {
case UIDeviceBatteryStateUnplugged:
return @“未充电”;
case UIDeviceBatteryStateCharging:
return @“充电中”;
case UIDeviceBatteryStateFull:
return @“已充满”;
default:
return @“未知”;
}
}
#### 屏幕尺寸
##### 获取方式
- (CGSize)getScreenSize
{
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat screenScale = [UIScreen mainScreen].scale;
CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
return screenSize;
}
#### 屏幕亮度
##### 获取方式
- (CGFloat)getScreenBrightness
{
return [UIScreen mainScreen].brightness;
}
#### 音量大小
##### 获取方式
#import <AVFoundation/AVFoundation.h>
- (CGFloat)getDeviceVolume
{
return [[AVAudioSession sharedInstance] outputVolume];
}
#### Wifi名称
##### 获取方式
#import <SystemConfiguration/CaptiveNetwork.h>
-
(NSString *)getWifiSSID
{
NSArray *ifs = (__bridge id)CNCopySupportedInterfaces();id info = nil;
for (NSString *ifnam in ifs) {
info = (__bridge id)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
if (info && [info count]) {
break;
}
}
NSDictionary *dctySSID = (NSDictionary *)info;return [dctySSID objectForKey:@“SSID”];
}
#### 网络制式
##### 获取方式
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>
-
(NSString *)getNetCarrier
{
NSString *mobileCarrier;
CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *carrier = networkInfo.subscriberCellularProvider;
NSString *MCC = carrier.mobileCountryCode;
NSString *MNC = carrier.mobileNetworkCode;if (!MCC || !MNC) {
mobileCarrier = @“No Sim”;
} else {
if ([MCC isEqualToString:@“460”]) {
if ([MNC isEqualToString:@“00”] || [MNC isEqualToString:@“02”] || [MNC isEqualToString:@“07”]) {
mobileCarrier = @“China Mobile”;
} else if ([MNC isEqualToString:@“01”] || [MNC isEqualToString:@“06”]) {
mobileCarrier = @“China Unicom”;} else if ([MNC isEqualToString:@"03"] || [MNC isEqualToString:@"05"] || [MNC isEqualToString:@"11"]) { mobileCarrier = @"China Telecom"; } else if ([MNC isEqualToString:@"20"]) { mobileCarrier = @"China Tietong"; } else { mobileCarrier = [NSString stringWithFormat:@"MNC%@", MNC]; } } else { mobileCarrier = @"Foreign Carrier"; }
}
return mobileCarrier;
}
#### 是否越狱
##### 获取方式
#import <sys/stat.h>
#import <dlfcn.h>
//判断是否越狱
-
(BOOL)isJailBreak
{
//以下检测的过程是越往下,越狱越高级
//获取越狱文件路径
NSString *cydiaPath = @“/Applications/Cydia.app”;
NSString *aptPath = @“/private/var/lib/apt/”;
if ([[NSFileManager defaultManager] fileExistsAtPath:cydiaPath]) {
return YES;
}
if ([[NSFileManager defaultManager] fileExistsAtPath:aptPath]) {
return YES;
}//可能存在hook了NSFileManager方法,此处用底层C stat去检测
struct stat stat_info;
if (0 == stat(“/Library/MobileSubstrate/MobileSubstrate.dylib”, &stat_info)) {
return YES;
}
if (0 == stat(“/Applications/Cydia.app”, &stat_info)) {
return YES;
}
if (0 == stat(“/var/lib/cydia/”, &stat_info)) {
return YES;
}
if (0 == stat(“/var/cache/apt”, &stat_info)) {
return YES;
}//可能存在stat也被hook了,可以看stat是不是出自系统库,有没有被攻击者换掉。这种情况出现的可能性很小
int ret;
Dl_info dylib_info;
int (*func_stat)(const char *,struct stat *) = stat;
if ((ret = dladdr(func_stat, &dylib_info))) {
//相等为0,不相等,肯定被攻击
if (strcmp(dylib_info.dli_fname, “/usr/lib/system/libsystem_kernel.dylib”)) {
return YES;
}
}//通常,越狱机的输出结果会包含字符串:Library/MobileSubstrate/MobileSubstrate.dylib。
//攻击者给MobileSubstrate改名,原理都是通过DYLD_INSERT_LIBRARIES注入动态库。那么可以检测当前程序运行的环境变量
char *env = getenv(“DYLD_INSERT_LIBRARIES”);
if (env != NULL) {
return YES;
}return NO;
}
#### 是否插入SIM卡
##### 获取方式
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>
- (BOOL)isSimInserted
{
CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *carrier = [networkInfo subscriberCellularProvider];
if (!carrier.isoCountryCode) {
return NO;
}
return YES;
}
#### 是否允许推送
##### 获取方式
- (BOOL)isPushEnabled
{
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0f) {
UIUserNotificationSettings *setting = [[UIApplication sharedApplication] currentUserNotificationSettings];
if (UIUserNotificationTypeNone == setting.types) {
return NO;
} else {
return YES;
}
} else {
UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if(UIRemoteNotificationTypeNone == type){
return NO;
} else {
return YES;
}
}
}
#### 剪切板内容
##### 获取方式
- (NSString *)getPasteBoardString
{
return [UIPasteboard generalPasteboard].string;
}
#### 是否使用代理
##### 获取方式
- (BOOL)isViaProxy
{
NSDictionary *proxySettings = (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());
NSArray *proxies = (__bridge NSArray *)(CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)([NSURL URLWithString:@“https://www.baidu.com/”]), (__bridge CFDictionaryRef _Nonnull)(proxySettings)));
NSDictionary *settings = proxies[0];
if (![[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@“kCFProxyTypeNone”]){
return YES;
}
return NO;
}
### 附录1—Android主要设备信息

#### DeviceId(设备ID/DID)
##### 介绍
DeviceId是用来标识一台Android物理设备的唯一id
Google提供了`TelephonyManager.getDeviceId`方法来获取Android的DID。该API是获取GSM手机的国际移动设备识别码(IMEI)或者 CDMA手机的移动设备识别码(MEID )。但该API存在一些限制。
##### 补充知识
###### 国际移动设备识别码(IMEI)
\*\*全称“International Mobile Equipment Identity”,\**是通常所说的手机序列号、手机“串号”。用于在移动电话网络中识别每一部独立的手机等移动通信设备,相当于移动电话的身份证,序列号共有15~17位数字,通过在手机拨号键盘中输入*#06#即可查询。
但存在以下限制:
* 1、自API23(Android 6.0)开始,获取IMEI需要用户予"android.permission.READ\_PHONE\_STATE";
* 2、自API29(Android 10.0)开始,您的应用必须是设备或个人资料所有者应用具有特殊运营商权限或具有 READ\_PRIVILEGED\_PHONE\_STATE 特许权限,才能访问这些标识符。
* 3、某些小厂商某型号的手机IMEI可能相同。
###### 移动设备识别码(MEID )
全称“Mobile Equipment Identifier”,是CDMA手机的身份识别码,也是每台CDMA手机或通讯平板唯一的识别码。通过这个识别码,网络端可以对该手机进行跟踪和监管。用于CDMA制式的手机。MEID的数字范围是十六进制的,和IMEI的格式类似。
存在的限制同IMEI的限制。
##### 系统版本迭代带来的影响
* 为了更好保护用户隐私,谷歌对安卓Q系统中所有获取设备识别码的接口都增加了新的权限控制:READ\_PRIVILEGED\_PHONE\_STATE,该权限需要系统签名的应用才能申请。同时,系统默认WiFi Mac地址随机化,当设备连上不同的WiFi网络时随机生成Mac地址;
* 通过READ\_PHONE\_STATE权限获取Device ID的应用以及将设备WiFi Mac地址作为设备唯一标志符的应用将受影响
* 在Android10及以上设备中,对于TargetSdkVersion<Q且没有申请READ\_PHONE\_STATE权限的应用和TargetSdkVersion>=Q的全部应用,获取Device ID会抛异常SecurityException。
* 在Android10及以上设备中,对于 TargetSdkVersion<Q且申请了READ\_PHONE\_STATE权限的应用,通过getDeviceId接口读取的值为Null。
##### 获取方式
public static String getIMEIDeviceId(Context context) {
String deviceId;
//当APK运行在Android10(API>=29)及以上时,获取到的是AndroidID
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
} else {
final TelephonyManager mTelephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//当APK运行在Android6.0(API>=23)及以上时,需要check有无READ_PHONE_STATE权限。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
return “”;
}
}
assert mTelephony != null;
//如果TelephonyManager获取到的DeviceId不为null
if (mTelephony.getDeviceId() != null) {
//获取GSM手机的国际移动设备识别码(IMEI)或者 CDMA手机的移动设备识别码(MEID).
deviceId = mTelephony.getDeviceId();
} else {
//如果DeviceId为null,我们的DID依然是AndroidID。
deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}
}
return deviceId;
}
##### 补充获取方式
以上获取方式有诸多不足之处,也有很多厂商选择自己用硬件拼出来一个UUID
但是这种方式也不是百分百准确。
public static String getUUID()
{
String serial = null;
String m_szDevIDShort = “35” +
Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
Build.CPU\_ABI.length() % 10 + Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
Build.USER.length() % 10; //13 位
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serial = android.os.Build.getSerial();
} else {
serial = Build.SERIAL;
}
//API>=9 使用serial号
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {
//serial需要一个初始化
serial = “serial”; // 随便一个初始化
}
//使用硬件信息拼凑出来的15位号码
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}
有一个比较详细的源码,自取地址:https://github.com/57xiaoyu/DeviceIDUtils
#### ANDROID\_ID
##### 介绍
又称SSAID。设备启动时,随机生成一个 64 位数字(表示为十六进制字符串),对于应用签名密钥、用户和设备的每个组合都是唯一的。 ANDROID\_ID 的值受签名密钥和用户的限制。
但存在如下限制:
* 1、如果在设备上执行恢复出厂设置或 APK 签名密钥更改,则该值可能会更改。
* 2、某些小厂商的Android手机可能为null或相同。
Android 开发者文档中有关于Android 8.0后的隐私性和SSAID的变化说明:

从图中不难看出,在 Android 8.0 以后,签名不同的 App 所获取的 Android ID(SSAID)是不一样的,但同一个开发者可以根据自己的数字签名,将所开发的不同 App 进行关联。
##### 获取方式
private String android_id = Secure.getString(getContext().getContentResolver(),Secure.ANDROID_ID);
#### AAID
AAID 与 IDFA 作用相同——IDFA 是 iOS 平台内的广告跟踪 ID,AAID 则用于 Android 平台。
它们都是一种非永久、可重置的标识符,专门提供给 App 以进行广告行为,用户随时可以重置该类 ID,或通过系统设置关闭个性化广告跟踪。但 AAID 依托于 Google 服务框架,因此如果手机没有内置该框架、或框架不完整、或无法连接到相关服务,这些情况都有可能导致 AAID 不可用。
#### OAID(匿名设备标识符)
是指华为、小米、OPPO、VIVO等安卓系统设备提供一串非永久性设备标识符,示例:1fe9a970-efbb-29e0-0bdd-f5dbbf751ab5。
Android 10 之后的替代方案
OAID 的本质其实是一种在国行系统内使用的、应对 Android 10 限制读取 IMEI 的、「拯救」国内移动广告的广告跟踪标识符,其背后是 移动安全联盟(Mobile Security Alliance,简称 MSA)。
### 附录2—Android 系统名字、版本、API level的对应关系
| 代号 | 版本 | API 级别/NDK 版本 |
| --- | --- | --- |
| Android12L | 12 | API 级别 32 |
| Android12 | 12 | API 级别 31 |
| Android11 | 11 | API 级别 30 |
| Android10 | 10 | API 级别 29 |
| Pie | 9 | API 级别 28 |
| Oreo | 8.1.0 | API 级别 27 |
| Oreo | 8.0.0 | API 级别 26 |
| Nougat | 7.1 | API 级别 25 |
| Nougat | 7 | API 级别 24 |
| Marshmallow | 6 | API 级别 23 |
| Lollipop | 5.1 | API 级别 22 |
| Lollipop | 5 | API 级别 21 |
| KitKat | 4.4 - 4.4.4 | API 级别 19 |
| Jelly Bean | 4.3.x | API 级别 18 |
| Jelly Bean | 4.2.x | API 级别 17 |
| Jelly Bean | 4.1.x | API 级别 16 |
| Ice Cream Sandwich | 4.0.3 - 4.0.4 | API 级别 15,NDK 8 |
| Ice Cream Sandwich | 4.0.1 - 4.0.2 | API 级别 14,NDK 7 |
| Honeycomb | 3.2.x | API 级别 13 |
| Honeycomb | 3.1 | API 级别 12,NDK 6 |
## 最后
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**





**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)
**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**
PI 级别 17 |
| Jelly Bean | 4.1.x | API 级别 16 |
| Ice Cream Sandwich | 4.0.3 - 4.0.4 | API 级别 15,NDK 8 |
| Ice Cream Sandwich | 4.0.1 - 4.0.2 | API 级别 14,NDK 7 |
| Honeycomb | 3.2.x | API 级别 13 |
| Honeycomb | 3.1 | API 级别 12,NDK 6 |
## 最后
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**
[外链图片转存中...(img-iXBnBJlT-1715490579900)]
[外链图片转存中...(img-iPwSNBZJ-1715490579901)]
[外链图片转存中...(img-zyJ0zJaW-1715490579901)]
[外链图片转存中...(img-VV30ZOw1-1715490579901)]
[外链图片转存中...(img-jzpCnJSa-1715490579901)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)
**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**