前言
- 阅读本文需要有一定逆向相关基础,否则可能看不懂,哈。
- 虚拟定位、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];
}
- XCode11以上可能遇到的问题
问题一:
An empty identity is not valid when signing a binary for the product type 'Dynamic Library'.
解决方案:
在Building Settings中点击下方的 + 号,选择Add User-Defined Setting,最后添加此变量CODE_SIGNING_ALLOWED = NO
问题二:
building for iOS, but linking in .tbd file (/opt/theos/vendor/lib/CydiaSubstrate.framework/CydiaSubstrate.tbd) built for iOS Simulator, file '/opt/theos/vendor/lib/CydiaSubstrate.framework/CydiaSubstrate.tbd' for architecture arm64.
解决方案:
打开/opt/theos/vendor/lib/CydiaSubstrate.framework/CydiaSubstrate.tbd,删除其中的i386,x86_64架构,保存。