IOS 越狱插件-虚拟定位、WIFI修改

前言

  • 阅读本文需要有一定逆向相关基础,否则可能看不懂,哈。
  • 虚拟定位、WIFI修改原理是HOOK系统函数,所以并不针对某个APP。
  • 本文所讲的是越狱插件,最好有越狱手机,当然你也可以重签名APP来实现HOOK函数。
  • 本文纯属逆向知识学习及探讨,请勿用作学习之外的任何用途。

开始

准备

  • XCode安装MonkeyDev插件
  • 创建LogosTweak项目


    image.png
  • 如果选择要导入PreferenceLoader,要在手机Cydia安装PreferenceLoader插件(雷锋源http://apt.abcydia.com/有)
    image.png
  • 部分配置解释


    image.png

虚拟定位

  • hook函数 - CLLocation的coordinate
// See http://iphonedevwiki.net/index.php/Logos

#if TARGET_OS_SIMULATOR
#error Do not support the simulator, please use the real iPhone Device.
#endif

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

%hook CLLocation
-(CLLocationCoordinate2D) coordinate
{
    CLLocationCoordinate2D location;
    //纬度
    location.latitude = 26.012345;
    //经度
    location.longitude = 106.49328;
    return location;
}
%end
  • 只要修改为你想去的地方的经纬度即可,也可以参考我另一篇文章IOS非越狱虚拟定位(保持定位)查看经纬度怎么写。
  • 可以看到,其实就只需要几行代码而已。但是每次都要研究经纬度是多少,再重新安装一次插件,太麻烦。所以我提供一个简易版(就是简单在地图上直接选位置)的选择位置信息控制器ViewController(目标APP需使用的是高德地图)。
  • SelectLocationVct.h
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <NetworkExtension/NetworkExtension.h>
NS_ASSUME_NONNULL_BEGIN
/**
 选择位置
 */
@interface SelectLocationVct : UIViewController
//获取保存的定位
+(CLLocationCoordinate2D) getSaveLocation;

@end

NS_ASSUME_NONNULL_END
  • SelectLocationVct.m
#import "SelectLocationVct.h"
#import "AMapFoundationKit.h"
#import "MAMapKit.h"
#import "AMapSearchAPI.h"
#import "MAPointAnnotation.h"

#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height


@interface SelectLocationVct ()<MAMapViewDelegate,AMapSearchDelegate,UISearchBarDelegate,UIActionSheetDelegate>
@property (nonatomic,assign) BOOL isFirstLocation;
@property (nonatomic,copy) NSString *firstLocationCity;
@property (nonatomic,strong) MAMapView *mapView;
@property (nonatomic,strong) MAPointAnnotation *pointAnn;
@property (nonatomic,strong) AMapSearchAPI *search;
@property (nonatomic,strong) UISearchBar * searchBar;
@property (nonatomic,strong) NSMutableArray *searchContentArray;
@end

@implementation SelectLocationVct

+(CLLocationCoordinate2D) getSaveLocation
{
   NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
   double latitude = [userDefault doubleForKey:@"save_latitude"];
   double longitude =  [userDefault doubleForKey:@"save_longitude"];
    if (latitude == 0 && longitude == 0) {
        CLLocationCoordinate2D location;
        location.latitude = 33.63235;
        location.longitude = 113.255743;
        return location;
    }
    return CLLocationCoordinate2DMake(latitude, longitude);
   
}

-(void) saveLocationWithLatitude:(double)latitude longitude:(double)longitude
{
    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    [userDefault setDouble:latitude forKey:@"save_latitude"];
    [userDefault setDouble:longitude forKey:@"save_longitude"];
    [userDefault synchronize];
    self.mapView.centerCoordinate = CLLocationCoordinate2DMake(latitude, longitude);
}

/**
 修改位置并保存

 @param title 标题
 @param latitude 纬度
 @param longitude 经度
 */
-(void) changeLocationWithTitle:(NSString *)title latitude:(double)latitude longitude:(double)longitude
{
    self.pointAnn.coordinate = CLLocationCoordinate2DMake(latitude, longitude);
    self.pointAnn.title = title;
    [self.mapView selectAnnotation:self.pointAnn animated:YES];
    [self saveLocationWithLatitude:latitude longitude:longitude];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = [SelectLocationVct selectBtnName];
    _pointAnn = [NSClassFromString(@"MAPointAnnotation") new];
    _search = [NSClassFromString(@"AMapSearchAPI") new];
    _search.delegate = self;
    _searchContentArray = [[NSMutableArray alloc] init];
}

-(void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    [self.view addSubview:self.searchBar];
    [self.view addSubview:self.mapView];
}

-(UISearchBar *) searchBar
{
    CGFloat navHeight = self.navigationController.navigationBar.bounds.size.height + [[UIApplication sharedApplication] statusBarFrame].size.height;
    _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, navHeight, ScreenWidth , 40)];
    _searchBar.placeholder = @"请输入搜索地名";
    _searchBar.delegate = self;
    return _searchBar;
}

- (MAMapView *) mapView
{
    if (_mapView == nil) {
//        _mapView = [[MAMapView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth,ScreenHeight - 200 - 64)];
        _mapView = [NSClassFromString(@"MAMapView") new];
        CGFloat navHeight = self.navigationController.navigationBar.bounds.size.height + [[UIApplication sharedApplication] statusBarFrame].size.height;
        _mapView.frame = CGRectMake(0, navHeight + 40, ScreenWidth,ScreenHeight - navHeight - _searchBar.frame.size.height);
        _mapView.delegate = self;
        /// 打开定位
        _mapView.showsUserLocation = YES;
        /*
         MAUserTrackingModeNone              = 0,    ///< 不追踪用户的location更新
         MAUserTrackingModeFollow            = 1,    ///< 追踪用户的location更新
         MAUserTrackingModeFollowWithHeading = 2     ///< 追踪用户的location与heading更新
         */
        _mapView.userTrackingMode = MAUserTrackingModeFollow;
        ///设定定位精度。默认为kCLLocationAccuracyBest
        _mapView.desiredAccuracy = kCLLocationAccuracyBest;
        /// 是否显示指南针
        _mapView.showsCompass = NO;
        /// 设定定位的最小更新距离。默认为kCLDistanceFilterNone,会提示任何移动

        _mapView.distanceFilter = 15.0f;
        /// 是否显示比例尺,默认为YES
        _mapView.showsScale = NO;
        /// 是否支持缩放,默认为YES

        _mapView.zoomEnabled = YES;
        /// 是否支持平移,默认为YES
        _mapView.scrollEnabled = YES;
        /// 缩放级别, [3, 20]
        _mapView.zoomLevel = 15;
        /// 去掉高德地图logo
        for (UIView *view in _mapView.subviews) {
            if ([view isKindOfClass:[UIImageView class]]) {
                [view removeFromSuperview];
            }
        }

    }
    return _mapView;

}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/



#pragma mark -- MAMapViewDelegate
/**
 * @brief 位置或者设备方向更新后调用此接口
 * @param mapView 地图View
 * @param userLocation 用户定位信息(包括位置与设备方向等数据)
 * @param updatingLocation 标示是否是location数据更新, YES:location数据更新 NO:heading数据更新
 */
- (void)mapView:(MAMapView *)mapView didUpdateUserLocation:(MAUserLocation *)userLocation updatingLocation:(BOOL)updatingLocation
{
    NSLog(@"定位更新");
    NSLog(@"经度-->%f 纬度-->%f",userLocation.coordinate.longitude,userLocation.coordinate.latitude);
    if (!_isFirstLocation) {
        _isFirstLocation = true;
        self.pointAnn.coordinate = userLocation.coordinate;
        self.pointAnn.title = userLocation.title;
        [self.mapView addAnnotation:self.pointAnn];
        [self.mapView selectAnnotation:self.pointAnn animated:YES];
        [self saveLocationWithLatitude:userLocation.coordinate.latitude longitude:userLocation.coordinate.longitude];
        [self getLocationByCoordinate:userLocation.coordinate successAction:^(NSDictionary *addressDic) {
            NSString *city=[addressDic objectForKey:@"City"];
            self.firstLocationCity = city;
        } failAction:^{
            
        }];
    }
}

/**
 * @brief 在地图View将要启动定位时调用此接口
 * @param mapView 地图View
 */
- (void)mapViewWillStartLocatingUser:(MAMapView *)mapView
{
    NSLog(@"开始定位");
}

/**
 * @brief 在地图View停止定位后调用此接口
 * @param mapView 地图View
 */
- (void)mapViewDidStopLocatingUser:(MAMapView *)mapView
{
    NSLog(@"停止定位");
}

/**
 * @brief 单击地图底图调用此接口
 * @param mapView    地图View
 * @param coordinate 点击位置经纬度
 */
- (void)mapView:(MAMapView *)mapView didSingleTappedAtCoordinate:(CLLocationCoordinate2D)coordinate
{
    NSLog(@"单击地图");
    NSLog(@"经度-->%f 纬度-->%f",coordinate.longitude,coordinate.latitude);
    
    [self getLocationByCoordinate:coordinate successAction:^(NSDictionary *addressDic) {
        NSString *state=[addressDic objectForKey:@"State"];
        NSString *city=[addressDic objectForKey:@"City"];
        NSString *subLocality=[addressDic objectForKey:@"SubLocality"];
        NSString *street=[addressDic objectForKey:@"Street"];
        //NSLog(@"%@,%@,%@,%@",state,city,subLocality,street);
        NSString *strLocation;
        if (street.length == 0 || street == NULL || [street isEqualToString:@"(null)"]) {
            strLocation= [NSString stringWithFormat:@"%@%@%@",state,city,subLocality];
        }else{
            strLocation= [NSString stringWithFormat:@"%@%@%@%@",state,city,subLocality,street];
        }
        
        [self changeLocationWithTitle:strLocation latitude:coordinate.latitude longitude:coordinate.longitude];
    } failAction:^{
        
    }];
}
//通过坐标获取位置信息
-(void) getLocationByCoordinate:(CLLocationCoordinate2D)coordinate successAction:(void(^)(NSDictionary *info))success failAction:(void(^)())fail
{
    //反编码 经纬度---->位置信息
    CLLocation *location=[[CLLocation alloc] initWithLatitude:coordinate.latitude longitude:coordinate.longitude];
    CLGeocoder *geocoder=[[CLGeocoder alloc] init];
    [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            NSLog(@"反编码失败:%@",error);
            if (fail) {
                fail();
            }
        }else{
            //NSLog(@"反编码成功:%@",placemarks);
            CLPlacemark *placemark=[placemarks lastObject];
            //NSLog(@"%@",placemark.addressDictionary[@"FormattedAddressLines"]);
            NSDictionary *addressDic=placemark.addressDictionary;
            if (success) {
                success(addressDic);
            }
        }
    }];
}


#pragma mark -- AMapSearchDelegate
/* POI 搜索回调. */
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response
{
    if (response.pois.count == 0)
    {
        NSLog(@"没有搜索到内容");
        return;
    }
    NSLog(@"搜索到了");
    //解析response获取POI信息
    [self.searchContentArray removeAllObjects];
    UIActionSheet *action = [[UIActionSheet alloc] initWithTitle:@"选择位置" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles: nil];
    for (int i = 0 ; i < response.pois.count; i++) {
        AMapPOI *p = [response.pois objectAtIndex:i];
        [action addButtonWithTitle:[NSString stringWithFormat:@"%@-%@",p.name,p.address]];
        [self.searchContentArray addObject:p];
    }
    [action showInView:self.view];
}


#pragma mark -- UISearchBarDelegate
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    [searchBar resignFirstResponder];
    AMapPOIKeywordsSearchRequest *request = [NSClassFromString(@"AMapPOIKeywordsSearchRequest") new];

    request.keywords            = searchBar.text;
    request.city                = self.firstLocationCity ? self.firstLocationCity : @"广州";
//    request.types               = @"";
    request.requireExtension    = YES;
    /* 按照距离排序. */
    request.sortrule            = 0;
    /*  搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
    request.cityLimit           = YES;
    request.requireSubPOIs      = YES;
    [self.search AMapPOIKeywordsSearch:request];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    NSLog(@"textDidChange-->%@",searchText);
    if ([searchText isEqualToString:@""]) {
        CLLocationCoordinate2D coordinate = self.mapView.userLocation.coordinate;
        [self changeLocationWithTitle:self.mapView.userLocation.title latitude:coordinate.latitude longitude:coordinate.longitude];
    }
}

#pragma mark -- UIActionSheetDelegate
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex NS_DEPRECATED_IOS(2_0, 8_3) __TVOS_PROHIBITED
{
 
    if(actionSheet.cancelButtonIndex != buttonIndex){
        AMapPOI *p = [self.searchContentArray objectAtIndex:buttonIndex-1];
        NSLog(@"%@*%@*%@",p.description,p.name,p.address);
        CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(p.location.latitude, p.location.longitude);
        [self changeLocationWithTitle:p.name latitude:coordinate.latitude longitude:coordinate.longitude];
    }
}
@end

  • 当然你还是要导入高德地图API的头文件(随便开个项目,pod高德地图的API再拷贝过来)

  • 最后修改一下xm文件

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "SelectLocationVct.h"
%hook CLLocation
-(CLLocationCoordinate2D) coordinate
{
    return [%c(SelectLocationVct) getSaveLocation];
}
%end
  • 来张效果图吧(支持直接点击地图,搜索来确定位置)


    image.png

PS:在哪里跳到这个控制器,就个人喜欢了,比如你可以hook某个按钮,或者hook某个控制器然后加个按钮,按钮点击时就跳到这个控制器就行了。

WIFI信息修改

  • 使用fishhook钩住系统函数

1、CNCopySupportedInterfaces:获取wifi列表(实际测试只返回当前wifi)
2、CNCopyCurrentNetworkInfo:获取wifi信息

  • 相关类
#import <Foundation/Foundation.h>
@interface DataManage : NSObject

+(void) saveDataForObject:(id)value AndKey:(NSString *)key;

+(id) getObjectFromKey:(NSString *)key;
@end


#import "DataManage.h"

@implementation DataManage

#pragma mark - 保存数据和获取数据
+(void) saveDataForObject:(id)value AndKey:(NSString *)key
{
    NSUserDefaults *defaluts = [NSUserDefaults standardUserDefaults];
    [defaluts setObject:value forKey:key];
    [defaluts synchronize];
}

+(id) getObjectFromKey:(NSString *)key
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    return [defaults objectForKey:key];
}

@end
  • 创建一个实体来保存wifi信息
@interface DVWifiModel : NSObject

// BSSID:路由器的Mac地址
@property (nonatomic, copy) NSString *BSSID;

// SSID:路由器的广播名称
@property (nonatomic, copy) NSString *SSID;

// SSIDDATA:SSID的十六进制
@property (nonatomic, strong) NSData *SSIDDATA;

//CNCopySupportedInterfaces返回的数组里的对象
@property (nonatomic, copy) NSString *ifnam;

+(instancetype) getSaveWifiInfo;

@end

@implementation DVWifiModel
+(instancetype) getSaveWifiInfo
{
    NSDictionary *dic = [DataManage getObjectFromKey:@"Wifi_info"];
    DVWifiModel *model = [DVWifiModel new];
    model.ifnam = dic[@"ifnam"];
    model.BSSID = dic[@"BSSID"];
    model.SSID = dic[@"SSID"];
    model.SSIDDATA = [model.SSID dataUsingEncoding:NSUTF8StringEncoding];
    return model;
}

@end
  • 主角 DVWifiHook -- 钩住系统方法的具体实现
#import <UIKit/UIkit.h>
@interface DVWifiHook : NSObject
+(NSDictionary *) getCurrentSSIDInfo;

+(void) saveCurrentWifi;

@end


#import "DVWifiHook.h"
#import "fishhook.h"
#import "DataManage.h"
#import "DVWifiModel.h"
@implementation DVWifiHook
+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        struct rebinding rebind1 = {"CNCopySupportedInterfaces", dv_CNCopySupportedInterfaces, (void *)&orig_CNCopySupportedInterfaces};
        struct rebinding rebind2 = {"CNCopyCurrentNetworkInfo", dv_CNCopyCurrentNetworkInfo, (void *)&orig_CNCopyCurrentNetworkInfo};
        //定义数组
        struct rebinding rebinds[] = {rebind1,rebind2};
        /*
         参数一 : 存放rebinding结构体的数组
         参数二 : 数组的长度
         */
        rebind_symbols(rebinds, 2);

    });
}



// CFArrayRef CNCopySupportedInterfaces        (void)
static CFArrayRef (*orig_CNCopySupportedInterfaces)();

static CFArrayRef dv_CNCopySupportedInterfaces() {
    CFArrayRef cfArray = NULL;
    DVWifiModel *wifi = [DVWifiModel getSaveWifiInfo];
    if(wifi && wifi.ifnam) {
        NSArray *array = [NSArray arrayWithObject:wifi.ifnam];
        cfArray = CFRetain((__bridge CFArrayRef)(array));
    }
    if(!cfArray) {
        cfArray = orig_CNCopySupportedInterfaces();
    }

    return cfArray;
}

// CFDictionaryRef CNCopyCurrentNetworkInfo        (CFStringRef interfaceName)
static CFDictionaryRef (*orig_CNCopyCurrentNetworkInfo)(CFStringRef interfaceName);

static CFDictionaryRef dv_CNCopyCurrentNetworkInfo(CFStringRef interfaceName) {
    CFDictionaryRef dic = NULL;
    DVWifiModel *wifi = [DVWifiModel getSaveWifiInfo];
    if(wifi) {
        NSDictionary *dictionary = @{
                                     @"BSSID" : (wifi.BSSID ? wifi.BSSID : @""),
                                     @"SSID" : (wifi.SSID ? wifi.SSID : @""),
                                     @"SSIDDATA" : (wifi.SSIDDATA ? wifi.SSIDDATA : @""),
                                     };
        dic = CFRetain((__bridge CFDictionaryRef)(dictionary));
    }
    if(!dic) {
        dic = orig_CNCopyCurrentNetworkInfo(interfaceName);
    }
    return dic;
}

+(NSDictionary *) getCurrentSSIDInfo
{
    NSString *currentSSID = @"";
    CFArrayRef myArray = orig_CNCopySupportedInterfaces();
    if (myArray != nil){
        NSDictionary* myDict = (__bridge NSDictionary *) orig_CNCopyCurrentNetworkInfo(CFArrayGetValueAtIndex(myArray, 0));
        if (myDict!=nil){
            currentSSID=[myDict valueForKey:@"SSID"];
        } else {
            currentSSID=@"NULL";
        }
    } else {
        currentSSID=@"NULL";
    }
    
    NSArray *ifs = (__bridge id)orig_CNCopySupportedInterfaces();
    NSLog(@"%s: Supported interfaces: %@", __func__, ifs);
    id info = nil;
    NSMutableDictionary *resultDic = nil;
    for (NSString *ifnam in ifs) {
        info = (__bridge id)orig_CNCopyCurrentNetworkInfo((CFStringRef)CFBridgingRetain(ifnam));
        if (info && [info count]) {
            resultDic = [[NSMutableDictionary alloc] initWithDictionary:info];
            [resultDic setValue:ifnam forKey:@"ifnam"];
            break;
        }
    }
    
    NSLog(@"wifi info %@",resultDic);
    
    return resultDic;
}

+(void) saveCurrentWifi
{
    NSDictionary *info = [DVWifiHook getCurrentSSIDInfo];
    NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
    [dic setValue:info[@"SSID"] forKey:@"SSID"];
    [dic setValue:info[@"BSSID"] forKey:@"BSSID"];
    [dic setValue:info[@"ifnam"] forKey:@"ifnam"];
    [DataManage saveDataForObject:dic AndKey:@"Wifi_info"];
    [[[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"设置成功\nWifi名称:%@\nBSSID:%@\nifnam:%@",info[@"SSID"],info[@"BSSID"],info[@"ifnam"]] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil] show];
}

PS:调用saveCurrentWifi方法后,无论连接什么WIFI,返回的都是保存的WIFI信息。原理不难,具体你想怎么去完善功能就靠你自己发挥了。

最后附上完整GitHub项目地址(VirtualLocationTweak),欢迎讨论交流。