IOS 越狱插件

2025-08-21 14:48:03

前言

阅读本文需要有一定逆向相关基础,否则可能看不懂,哈。

虚拟定位、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

#import

%hook CLLocation

-(CLLocationCoordinate2D) coordinate

{

CLLocationCoordinate2D location;

//纬度

location.latitude = 26.012345;

//经度

location.longitude = 106.49328;

return location;

}

%end

只要修改为你想去的地方的经纬度即可,也可以参考我另一篇文章IOS非越狱虚拟定位(保持定位)查看经纬度怎么写。

可以看到,其实就只需要几行代码而已。但是每次都要研究经纬度是多少,再重新安装一次插件,太麻烦。所以我提供一个简易版(就是简单在地图上直接选位置)的选择位置信息控制器ViewController(目标APP需使用的是高德地图)。

SelectLocationVct.h

#import

#import

#import

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 ()

@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

#import

#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

@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

@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架构,保存。

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

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