【 ASO项目使用的技术】之切换IP的实现(三种切换IP的方式,涉及两种实现方案:基于DoSources0、递归执行切换任务)

2021-03-24 16:00:07 浏览数 (1)

点击上方蓝字关注我们

前言

  • 原文:

https://kunnan.blog.csdn.net/article/details/114634689

  • 项目描述

1、下载流程:清理进程和数据(包括keychain及修改设备信息)、切换IP、登录appID、打开App Store、在App Store搜索应用、下载并安装app(打码)、注销app ID、关闭App Store、卸载app 2、 评论流程:在下载流程的基础上进行评论 ———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/z929118967/article/details/114634689

本文介绍IP切换的实现方案:三种切换IP的方式,涉及两种实现方案

方案一:使用performSelectorOnMainThread 结合递归来执行切换IP的任务

应用场景:使用代理app进行切换 切换IP方式一: 直接调用注入SB的方法,而非请求 切换IP方式二:直接请求接口进行IP切换

方案二: 使用基于CFRunLoopDoSources0自定义Operation执行切换IP任务

1、每个Operation 暂时定为尝试切换5次,5次失败之后(尝试切换Wi-Fi,);直到OperationSucceed ,才执行下一步登录appID 2、应用场景:在设置->通用->V_P_N 配置代理IP账号 切换IP方式三:通过代码来开启和关闭设置->通用->V_P_N开关进行切换

I、 方案一:采用递归执行切换IP任务

  • SwitchIPOperation的用法
代码语言:javascript复制

        // start  使用xxxx切换IP
        while (YES) {
            @autoreleasepool {
                SwitchIPOperation   *knop = [[[SwitchIPOperation alloc] initWithTimeOut:3 Operation:YES tryTimes:1] autorelease];    //tryTimes表示尝试次数,0为无限次,直到成功为止
                //时间initWithTimeOut设置长些,避免,OperationTimeOut = 4,
                [self runSubOperation:knop];
                CheckCancel(self);
                [LogWindow info: FMSTR(@"[sb] op.status:%ld",(long)knop.status)];
                if (knop.knstatus == 1) {//Succeed
                    break;
                }
                
                //                    [LogWindow warn: FMSTR(@"[sb] 发现切换IP状态异常, 尝试切换Wi-Fi修正")];//
                //                op = [[[KillProcessOperation alloc] initWithProcessName:@"pppd" containsMode:YES] autorelease]; // 并没有什么卵用, 改到daemon里去做
                //                RunSubOperation(self, op);
                //                [[objc_getClass("SBWiFiManager") sharedInstance] setWiFiEnabled:offOrOn];
                //                DPost(@"kill", @"pppd");//尝试切换Wi-Fi pppd修正()-- 
                //                    [[objc_getClass("SBWiFiManager") sharedInstance] setPowered:NO];
                //                    [[objc_getClass("SBWiFiManager") sharedInstance] setWiFiEnabled:NO];
                //
                //                    [[objc_getClass("SBWiFiManager") sharedInstance] setPowered:YES];
                //                    [[objc_getClass("SBWiFiManager") sharedInstance] setWiFiEnabled:YES];
                //                    [WindowTool setupCloseOpenWifi];
                
                [LogWindow info: FMSTR(@"[sb] 稍后重新尝试切换IP!")];
                Sleep(self, 3);
            }
        }
        // end 使用xxxx切换IP


1.1 SwitchIPOperation

  • SwitchIPOperation.h
代码语言:javascript复制
@interface SwitchIPOperation : Operation


// (void) handleVPNChanged:(NSNotification*) notification;//处理VPN鉴权的事件
-(id) initWithTimeOut:(NSTimeInterval) expire Operation:(BOOL) operation tryTimes:(int) tryTimes;


@property (atomic, assign) NSInteger knstatus;
@end

  • private : SwitchIPOperation.m
代码语言:javascript复制

@implementation SwitchIPOperation {
    int _wannaTryTimes;
    int _tryTimes;
    BOOL operation;
}

-(id) initWithTimeOut:(NSTimeInterval) expire Operation:(BOOL) op tryTimes:(int) tryTimes{
    //tryTimes 重试操作的次数,每次超时expire
    //tryTimes 为0时,无限尝试,直到成功,或被取消为止
    self = [super initWithTimeOut:expire];
    if (self) {
        _tryTimes = 0;
        _wannaTryTimes = tryTimes;
        operation = op;
        [LogWindow debug:@"[switch ip] 任务创建!"];
    }
    return self;
}


-(void) dealloc {
    [super dealloc];
}

-(void) willLeaveRunloop {
}

-(NSString*) name {
    return @"switch ip操作";
}

-(void) actionVpn:(id) op {
    
   
    
//   使用递归,在block中触发退出条件

    
    
    __block typeof(self) weakSelf = self;
        [WindowTool setupSwitchIp:^(NSString *errorMsg, id result) {
            if (errorMsg == nil && result != nil) {
                [LogWindow info:FMSTR(@"[switch ip] result %@!", result)];
                
                    [LogWindow debug:FMSTR(@"[switch ip] -%@- 成功!", [op boolValue] ? @"开启" : @"关闭")];
                    [weakSelf wakeUpForReason:OperationSucceed];// 切换成功调用的方法


                weakSelf.knstatus = 1;
                
                
            }else{
                [LogWindow info:FMSTR(@"[switch ip] errorMsg %@!", errorMsg)];
                
                if (_wannaTryTimes > 0 && _tryTimes >= _wannaTryTimes){//递归结束
                    
                    [weakSelf wakeUpForReason:OperationFail];
                    [LogWindow warn:FMSTR(@"[switch IP] 尝试次数:%d 已达到尝试上限次数:%d 仍未成功!", _tryTimes, _wannaTryTimes)];
                    return;
                    
                }else{
                    
                    [weakSelf runOnce];
                    
                }
                
            }
            
    
}


-(void) runOnce{//多次执行runOnce,采用递归的方式
    _tryTimes  ;
    if (_tryTimes >= 1) {
        [LogWindow info:MGSTR(@"[switch IP] 尝试%@切换IP 第%d次!", operation?@"开启":@"关闭", _tryTimes)];
    }
    [self performSelectorOnMainThread:@selector(actionVpn:) withObject:@(operation) waitUntilDone:YES];
}

-(void) run {
            [self runOnce];// 要求这个方法必须是同步的,否则会存在多次执行此方法的情况,可以采用dispatch信号量(dispatch semaphore)dispatch_semaphore_wait 实现同步,目前采用递归
    [super run];
}

@end

1.2 工具类:WindowTool

WindowTool功能:

1、 网络异常的时候自动关闭VPN(防止VPN有效性过期),并且自动断开Wi-Fi,再次重新连接(防止IP有效期超时)。

  • WindowTool.h
代码语言:javascript复制
///errorMsg 等于 nil 就表示切换成功了, errorMsg不等于nil里面就是失败的原因
typedef void(^ABYCallback)( NSString *errorMsg, id result);


@interface WindowTool : NSObject

  (void) setupCloseOpenWifi;

  (void) setupSwitchIp:(ABYCallback)callback;// 切换IP,可通过直接调用方法切换,也可以通过请求API进行切换IP

@end

  • WindowTool.m

切换IP方式一: 直接调用注入SB的方法,而非请求 切换IP方式二:直接请求接口进行IP切换

代码语言:javascript复制
@implementation ASWindowTool



static int launchProcess(const char* path, char** args) {
    posix_spawnattr_t attr;
    posix_spawn_file_actions_t fact;
    posix_spawnattr_init(&attr);
    posix_spawn_file_actions_init(&fact);
    char* const environ[2] = {"", NULL};
    pid_t pid = 0;
    posix_spawnp(&pid, path, &fact, &attr, args, environ);
    return pid;
}

 (BOOL) noZombieRun:(NSString*) path withParams:(NSArray*)args {
    const char* _path = [path UTF8String];
    int n = args ? args.count : 0;
    char** _args = alloca(sizeof(char*) * (n   2));
    _args[0] = "";
    _args[n 1] = NULL;
    
    for (int i = 0; i < n; i  ) {
        NSString* arg = args[i];
        _args[i 1] = [arg UTF8String];
    }
    
    pid_t pid = fork();
    if (pid < 0) {
        return NO;
    } else if (pid == 0) {
        launchProcess(_path, _args);
        _exit(0);
    }
    waitpid(pid, NULL, 0);
    return YES;
}


/**
 网络异常的时候自动关闭VPN(防止VPN有效性过期),并且自动断开Wi-Fi,再次重新连接(防止IP有效期超时)。
文章: https://blog.csdn.net/z929118967/article/details/112829168
*/
  (void)setupWifi{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        
        VPNOperation  *op = [[[VPNOperation alloc] initWithTimeOut:10 Operation:NO tryTimes:1] autorelease];
        [op actionVpn:NO];
        
        
        
//        //打开Wi-Fi开关
//        popen(@"wifiutil enable-wifi", @"r");
//        popen(@"wifiutil disassociate", @"r");//断开连接
//        //重新连接
//        popen(@"wifiutil associate @PHICOMM_08 -p kunnan2018", @"r");


        NSLog(@"wifiutil enable-wifi");
        
        //打开Wi-Fi开关
//        run(@"/usr/bin/wifiutil enable-wifi");
//        run(@"/usr/bin/wifiutil disassociate");//断开连接
        //重新连接
//        run(@"/usr/bin/wifiutil associate @PHICOMM_08 -p kunnan2018");
//        NSArray* args = @[[@(self.level) stringValue]];//构建一个数组
        
        NSArray* args = @[@"enable-wifi"];
        [self noZombieRun:@"/usr/bin/wifiutil" withParams:args];
        
        NSArray* args1 = @[@"disassociate"];
        [self noZombieRun:@"/usr/bin/wifiutil" withParams:args1];
        
        
        
        NSArray* args2 = @[@"associate",@"@PHICOMM_08",@"-p",@"kunnan2018"];
        [self noZombieRun:@"/usr/bin/wifiutil" withParams:args2];

    });

    
    
    
    
}


/**
 目前用于处理网络异常情况:
 
 
 1、自动连接Wi-Fi:   xxx/ASO/wifiutil.git
 2、自动设置永不锁屏
 3、自动处理进程通信的问题
 4、网络异常的时候自动关闭VPN(防止VPN有效性过期),并且自动断开Wi-Fi,再次重新连接(防止IP有效期超时)。
 
 */
  (void) setupCloseOpenWifi{
    
    
    if (!isUsingAbunSwithIp) {// 处理场景1: 【通过代码来开启和关闭`设置->通用->V_P_N`开关进行切换】
        
        [self setupWifi];
        
        return;
        
        [[objc_getClass("SBWiFiManager") sharedInstance] setPowered:NO];//-fno-objc-arc
        [[objc_getClass("SBWiFiManager") sharedInstance] setWiFiEnabled:NO];
        
        
        __block typeof(self) weakSelf = self;
        
        
        //
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            [[objc_getClass("SBWiFiManager") sharedInstance] setPowered:YES];
            [[objc_getClass("SBWiFiManager") sharedInstance] setWiFiEnabled:YES];
            //如果发现网络异常,就立马关闭VPN 一定要放到主进程运行
            VPNOperation  *op = [[[VPNOperation alloc] initWithTimeOut:10 Operation:NO tryTimes:1] autorelease];
            [op actionVpn:NO];

        });
        

        
        
    }else{//处理场景2:【直接调用注入SB的方法,而非请求 or  直接请求接口进行IP切换】
        // 考虑异步通知xxxxx切换IP,此时可能IP 失效了,目前的IP失效时间是900‘
        
        [self setupSwitchIp:nil];// 此方法是异步的
    
        
    }
    
    

}
    
    /**
     ABYCallback callback =  ^(NSString *errorMsg, id result)
     {
     if (errorMsg)
     {
     NSLog(@"lg error:%@",errorMsg);
     } else
     {
     NSLog(@"lg switch ip success");
     }
     };
     
     必须异步处理,否则sb 会进入安全模式
     @param callback <#callback description#>
     */
      (void) setupSwitchIp:(ABYCallback)callback{
        
        
        
        if(isUsingAbunSwithIpType1){
            
            //直接调用方法
            [self setupSwitchIpABUYUN1:callback];
        }else{
            
            
            
            //最新的方案,直接请求接口进行IP切换
            [self actionSwitchIP:callback];
        }
        
        
        
        
        return;
       
    }


/**
 ///errorMsg 等于 nil 就表示切换成功了, errorMsg不等于nil里面就是失败的原因
 typedef void(^ABYCallback)( NSString *errorMsg, id result);
 { code : 10000, status : 407, error : 'proxy authentication required' }
 { code : 10000, status : 402, error : 'payment required' }
 @param callback <#callback description#>
 */
  (void) actionSwitchIP:(ABYCallback)callback{
    
    NSString *urlstring = @"http://proxy.xxxyun.com/switch-ip";
    
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlstring]];
    
    request.timeoutInterval = 10.0;//最多10‘
    

    
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        
        NSLog(@"requestFinished");
        NSHTTPURLResponse *hTTPURLResponse =(NSHTTPURLResponse*)response;
        NSLog(@"statusCode: %li", (long)[hTTPURLResponse statusCode]);
        NSString * errorMsg = nil;
        
        if (error == nil) {//请求成功
            
            if (hTTPURLResponse.statusCode == 500)
            {
                
                NSLog(@"%s  %@",__func__,@"系统内部错误");
                
                errorMsg =@"系统内部错误";
                
                if (callback) {
                    
                    callback(errorMsg,nil);
                    
                }

                
                return;
            }
            
            
            if (hTTPURLResponse.statusCode != 200){
                NSLog(@"%s  %ld",__func__,(long)hTTPURLResponse.statusCode);
                
                errorMsg =[NSString stringWithFormat:@"%s  %ld",__func__,(long)hTTPURLResponse.statusCode];
                //非200
                if (callback) {
                    
                    callback(errorMsg,nil);
                    
                }
                
                return;
                
            }
            
            NSError *JSONObjectWithDataError = nil;
            
            NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONObjectWithDataError];
            if (JSONObjectWithDataError) {
                NSLog(@"%s  %@",__func__,JSONObjectWithDataError);
                // invalid json data
                errorMsg = [JSONObjectWithDataError description];
                
                if (callback) {
                    
                    callback(errorMsg,nil);
                    
                }
                
                return;
            }
            
            
            //处理返回数据
            
            NSNumber *code = response[@"code"];
            
            
            
            if (code.intValue == 0){
                
                if (callback) {
                    
                    //切换成功
                    callback(nil,response[@"proxy"]);//返回代理信息
                }
            }else{
                
                
                if (callback) {//业务逻辑失败
                    
                    
                    errorMsg = [NSString stringWithFormat:@"switch-ip failed, error:%@ %@",response[@"status"],response[@"error"]];
                    callback(errorMsg,nil);
                    
                    
                }else{//默认的业务逻辑失败处理
                    //可选
                }
                
            }
            
            
            
            
        }else{ //请求失败
            
            NSString *errorMsg = @"请求失败,请检查网络是否可用,或者阿布云app未打开";
            
            if (callback) {
                
                
                
                callback(errorMsg,nil);

            }else{//默认的请求失败处理
                
                NSLog(@"%s  %@",__func__,@"请求失败,请检查网络是否可用");
                
                
            }
            
        }
        
        
    }]resume];//所有类型的task都要调用resume方法才会开始进行请求.
    
    
    return;
    
//    NSString *urlstring = @"http://proxy.xxxyun.com/switch-ip";
//    NSString *urlstring2 = @"http://proxy.xxxyun.com/current-ip";
    NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString: urlstring]];
    NSDictionary * root = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    if (root) {
        NSNumber *code = root[@"code"];
        if ( code.intValue == 0) {
            NSDictionary *proxy = root[@"proxy"];
            
            NSLog(@"switch-ip success,当前IP: %@,剩余时间: %@",proxy[@"ip"],proxy[@"left"]);
        
            callback(nil,proxy);
        }
        else{
            NSString *errorMsg = [NSString stringWithFormat:@"switch-ip failed, error:%@ %@",root[@"status"],root[@"error"]];
            NSLog(@"switch-ip failed, error:%@ %@",root[@"status"],root[@"error"]);
            callback(errorMsg,nil);
        }
    }
    else{
        
        NSLog(@"switch-ip error,invalid json data");
         NSString *errorMsg = @"switch-ip error,invalid json data";
        callback(errorMsg,nil);
    }
    
}


/**
 切换IP方式一:直接调用方法,而非请求
 @param callback <#callback description#>
 */
  (void)setupSwitchIpABUYUN1:(ABYCallback)callback{
    
    //sb 已经包含这个ABuyunClient2
    
    if (NSClassFromString(@"xxxyunClient2") == nil) {
        
        if(callback){
            callback(@"尚未安xxxdeb 包",nil);
        }
        
        return;
    }
    [NSClassFromString(@"xxxyunClient2") performSelector:NSSelectorFromString(@"actionSwitchIP:") withObject:callback];
    
}


II、 方案二: 使用基于CFRunLoopDoSources0自定义Operation执行切换IP任务

  • VPNOperation的用法

0 人点赞