首页
社区
课程
招聘
[原创]自己动手从iOS Keychain中恢复保存的Wifi密码
2014-11-12 21:53 27007

[原创]自己动手从iOS Keychain中恢复保存的Wifi密码

2014-11-12 21:53
27007
最近在学用Theos编写插件和小工具,本来打算自己动手写个类似Wifi Passwords的工具,用于查看保存在iOS设备中的Wifi密码的。不过搜了下居然没找到具体的实现方法。

本来以为iOS和Android一样保存在某个plist里(Android的Wifi密码明文保存在/data/misc/wifi/wpa_supplicant.conf),不过Google了才发现iOS虽然在 /private/var/preferences/SystemConfiguration/com.apple.wifi.plist 里保存了Wifi信息,但密码却是存储在Keychain中的,而且网上没有给出具体的读取办法

鉴于对Keychain Services不熟,只好用 Hopper Disassembler 反汇编WiFiPasswords自己看了。代码不多,大部分都是和UITableView相关的内容,排除掉这些很快发现 -[RootViewController refresh] 就是要找的函数:



这里读取了 /private/var/preferences/SystemConfiguration/com.apple.wifi.plist 的List of known networks中的信息(意义不明,实际上直接用SecItemCopyMatching也是能获取到Wifi名的,而且它后面也这样做了)。到机器上用plutil查看该plist文件,会看到类似下面的内容:

    "List of known networks" =     (
                {
            "80211D_IE" =             {
                "IE_KEY_80211D_COUNTRY_CODE" = JP;
            };
            "80211W_ENABLED" = 0;
            AGE = 41;
            "AP_MODE" = 2;
            "ASSOC_FLAGS" = 1;
            "BEACON_INT" = 20;
            BSSID = "00:2e:1f:54:1d:82";
            CAPABILITIES = 1041;
            CHANNEL = 12;
    ...


这个plist里存储的是Wifi的属性等信息,不过BSSID和信道之类的这里用不上。继续往后看就能发现关键数据:



先构造一个NSMutableArray,内容为 [kSecClass, kSecAttrService, kSecReturnAttributes, kSecMatchLimit]



接着构造另一个NSMutableArray,内容为 [kSecClassGenericPassword, @"AirPort", kCFBooleanTrue, kSecMatchLimitAll]
根据接着调用的 SecItemCopyMatching 可以得知,这个数据是参数1 CFDictionaryRef query 的内容,其作用是查询Keychain中kSecClass=kSecClassGenericPassword,且kSecAttrService为AirPort的属性。

Apple的 Keychain Item Class Keys and Values 里有详细介绍这些属性的含义。kSecClass 还可以是这些值:

CFTypeRef kSecClassGenericPassword ;
CFTypeRef kSecClassInternetPassword ;
CFTypeRef kSecClassCertificate ;
CFTypeRef kSecClassKey ;
CFTypeRef kSecClassIdentity;


比如网络密码,证书等等。这里枚举Wifi密码只用到了 kSecClassGenericPassword。

参考StackOverflow中SecItemCopyMatching的用法例子,很容易还原出上面的代码。用Theos的nic.pl创建一个tools项目,输入验证用代码:

#import <Security/Security.h>

int main(int argc, char **argv, char **envp)
{
  NSMutableDictionary *query = [NSMutableDictionary dictionary];

  [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
  [query setObject:(__bridge id)@"AirPort" forKey:(__bridge id)kSecAttrService];
  [query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];

  CFTypeRef result = NULL;
  OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
  if (status != errSecSuccess) {
    printf("[ERROR] SecItemCopyMatching() failed! error = %d\n", (int)status);
    return;
  }

  NSArray *wifi_list = (NSArray *)result;
  for (int i = 0; i < wifi_list.count; i++) {
    NSDictionary *wifi = (NSDictionary*)wifi_list[i];

    NSString *output = [NSString stringWithFormat:@"%@", wifi];
    printf("%s\n", [output cStringUsingEncoding:NSUTF8StringEncoding]);
  }

  if (result != NULL) {
    CFRelease(result);
  }

  return 0;
}


make后传到iOS里运行,然后顺利的失败了 提示-34018:

[ERROR] SecItemCopyMatching() failed! error = -34018


再次请出Google大神,得知SecItemCopyMatching返回-34018(errSecMissingEntitlement)是权限问题。
用 ldid -e WiFiPasswords 查看entitlement,发现它比常规程序多了 keychain-access-groups 权限。于是编辑一个 ent.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>application-identifier</key>
  <string>com.zzz.my-idevice-tools</string>
  <key>get-task-allow</key>
  <true/>
  <key>keychain-access-groups</key>
  <array>
    <string>apple</string>
  </array>
</dict>
</plist>


用 ldid -Sent.xml <app> 签上带keychain-access-groups的签名后运行,成功打印出Wifi信息:

{
    accc = "<SecAccessControlRef: 0x1566a4c0>";
    acct = Magdalene;
    agrp = apple;
    cdat = "2014-11-02 04:56:39 +0000";
    mdat = "2014-11-02 04:56:39 +0000";
    pdmn = ck;
    svce = AirPort;
    sync = 0;
    tomb = 0;
}


acct就是kSecAttrAccount,这里也就是Wifi名 (而accc是指向 strcut SecAccessControl 的指针,只不过网上搜了很久也没找到这个结构体的定义;不过这里用不到,pass)

注意,ldid签名如果失败,一般是codesign_allocate没有用对导致的。在MacOS上/usr/bin/codesign_allocate并非iOS用的版本,需要手工export一下(注意替换为你的Xcode安装目录):

export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate
ldid -Sent.xml obj/wifi_passwords


已经在获取到的 NSDictionary 中找到Wifi的名称(acct),就可以开始查询对应的密码了。获取密码的参数为:



和之前获取Wifi名类似,将kSecReturnAttributes换成了kSecReturnData,另外有kSecAttrAccount(acct)也不需要搜索了;kSecReturnData的返回为NSData,里面就是对应Wifi的密码。

完成后的恢复Wifi密码的函数如下:

void keychain_wifi_passwords()
{
  NSMutableArray *acct_name = [NSMutableArray array];

  // form KeyChain get AirPort.acct (Wifi Name)
  {
    NSMutableDictionary *query = [NSMutableDictionary dictionary];

    [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
    [query setObject:(__bridge id)@KEYCHAIN_SVCE_AIRPORT forKey:(__bridge id)kSecAttrService];
    [query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];

    CFTypeRef result = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
    if (status != errSecSuccess) {
      printf("[ERROR] SecItemCopyMatching() failed! error = %d\n", (int)status);
      return;
    }

    NSArray *wifi_list = (NSArray *)result;
    for (int i = 0; i < wifi_list.count; i++) {
      NSDictionary *wifi = (NSDictionary*)wifi_list[i];
      // get wifi name
      [acct_name addObject:wifi[@KEYCHAIN_ACCT_NAME]];
    }

    if (result != NULL) {
      CFRelease(result);
    }
  }

  // get password for each AirPort.acct
  {
    for (NSString *acct in acct_name) {
      NSMutableDictionary *query = [NSMutableDictionary dictionary];

      [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
      [query setObject:(__bridge id)@KEYCHAIN_SVCE_AIRPORT forKey:(__bridge id)kSecAttrService];
      [query setObject:acct forKey:(__bridge id)kSecAttrAccount];
      [query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];

      CFTypeRef result = NULL;
      OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
      if (status != errSecSuccess) {
        printf("[ERROR] SecItemCopyMatching() failed! error = %d\n", (int)status);
        return;
      }

      NSData *password = (NSData *)result;
      NSString *output = [[NSString alloc] initWithData:password encoding:NSASCIIStringEncoding];
      printf("%s: %s\n", [acct cStringUsingEncoding:NSUTF8StringEncoding], [output cStringUsingEncoding:NSUTF8StringEncoding]);

      if (result != NULL) {
        CFRelease(result);
      }
    }
  }
}


make编译并make ldid签上ent.xml后,传入iOS会输出Keychain中保存的Wifi名和密码:

root# ./wifi_passwords
Magdalene: Retrieve Wifi password.
iPhone: 123456


剩下就是给控制台程序加个界面了。完整Theos工程代码见:https://github.com/upbit/My-iDevice-Tools/blob/master/wifi_passwords.mm

附件中为编译好的测试程序(armv7, arm64)。初次接触Keychain,如果文中有错误之处,欢迎用力拍砖

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (8)
雪    币: 1098
活跃值: (193)
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
zhuliang 5 2014-11-13 15:12
2
0
这个程序也可以实现。
https://github.com/ptoomey3/Keychain-Dumper
雪    币: 302
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
suntiger 2014-11-13 17:54
3
0
还是IDA看的直观:
上传的附件:
雪    币: 296
活跃值: (89)
能力值: ( LV15,RANK:340 )
在线值:
发帖
回帖
粉丝
木桩 8 2014-11-14 10:06
4
0
[QUOTE=zhuliang;1330557]这个程序也可以实现。
https://github.com/ptoomey3/Keychain-Dumper[/QUOTE]

赞!已经star,早点看到就不用去反汇编了
雪    币: 296
活跃值: (89)
能力值: ( LV15,RANK:340 )
在线值:
发帖
回帖
粉丝
木桩 8 2014-11-14 10:11
5
0
[QUOTE=suntiger;1330604]还是IDA看的直观:
[/QUOTE]

确实,IDA这个易懂多了。Hopper Disassembler虽然也有正确的字符串标注,不过
movw r2, #11a8
movt r2, #0xd
add r2, pc

跟进去才知道,实际就是取 @"/private/var/preferences/SystemConfiguration/com.apple.wifi.plist"
雪    币: 136
活跃值: (105)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
夜凉如水 3 2014-11-14 17:11
6
0
不错 分析的很好 很给力啊
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
三胖 2014-11-20 21:12
7
0
虽然看不懂,但是觉得你很牛逼
雪    币: 16
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
MarkYS 2014-11-22 10:46
8
0
很不错!
雪    币: 244
活跃值: (163)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
airbus 2016-8-17 17:17
9
0
原来是在tt的大神
游客
登录 | 注册 方可回帖
返回