iOS - 阿里云 HTTPDNS iOS 植入

首先说明一下,要解决的问题:DNS劫持。

对,就是要解决DNS劫持这个问题。不太懂网络的同学们可能不太懂什么是DNS,什么又是DNS劫持,这里简单介绍一下。

DNS就是域名解析系统,就是把我们平时用的网址域名(如www.baidu.com   www.sina.com.cn)解析成相对应的服务器IP,只有解析成IP之后,网络请求才能找到服务器。

DNS劫持是啥呢?更简单,就是有人把你的域名解析成了其他的IP,进而达到给你返回他想要返回的内容,比如广告、钓鱼等等.........

那么,iOS APP哪里会遇到DNS劫持的问题?当然是我们的UIWebView和WKWebView。当我们的webView加载网页时,很容易被DNS劫持,从而产生很多小广告之类的东西。为了防止这些现象,我们引入了HTTPDNS


OK!说明白了要解决的问题,那接着看HTTPDNS是怎么工作的。

其实很简单,就是阿里云自己提供了一套可靠的DNS系统,我们每次要做域名解析时,直接去阿里云上取IP,而不用通常的DNS方案,所以除了阿里云可以进行DNS劫持,其他人都没戏了(我们是相信阿里云的)。


HTTPDNS植入iOS APP 方案:

我们需要把所有的APP产生的请求(或者我们想做HTTPDNS的请求)都通过HTTPDNS来进行域名解析,通过NSURLProtocol类即可实现。

NSURLProtocol可以接管app所发出的所有请求。NSURLProtocol是抽象类,需要创建一个子类才能使用。在子类中重写必要的方法,来实现响应的接管功能:

(1)

第一步:判断哪些request需要拦截,需要返回YES;不需要返回NO
+ (BOOL)canInitWithRequest:(NSURLRequest *)request

(2)

第二步:对拦截的request进行处理,修改host,添加cookie等
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
(3)

第三步:建立NSURLSesion对象,并发起请求
- (void)startLoading
(4)

第四步:停止请求
- (void)stopLoading

(5)
第五步:NSURLSessionDelegate中,完成
请求返回数据的回吐,即将请求返回的数据回吐给client(本来请求的发起者,如UIWebView)
https证书的校验


拦截流程图:


拦截之后的处理流程图:




//
//  CustomURLProtocol.h
//  iphone-pay
//
//  Created by HLYUE on 2017/11/7.
//  Copyright © 2017年 RHJX Inc. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface CustomURLProtocol : NSURLProtocol

@end


//
//  CustomURLProtocol.m
//  iphone-pay
//
//  Created by HLYUE on 2017/11/7.
//  Copyright © 2017年 RHJX Inc. All rights reserved.
//

#import "CustomURLProtocol.h"
#import <AlicloudHttpDNS/AlicloudHttpDNS.h>

static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";

@interface CustomURLProtocol () <NSURLSessionDelegate>
@property (nonatomic, strong) NSURLSessionDataTask *dnstask;
@end

@implementation CustomURLProtocol

//判断哪些request需要拦截,需要返回YES;不需要返回NO
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    //只处理http和https请求
    NSString *scheme = [[request URL] scheme];
    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)) {
        //看看是否已经处理过了,防止无限循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        
        //post请求不拦截
        if ([request.HTTPMethod isEqualToString:@"POST"]) {
            return NO;
        }
        
        NSString *hostStr = [request.URL host];
        HttpDnsService *httpdns = [HttpDnsService sharedInstance];
        NSString *httpDnsIP = [httpdns getIpByHostAsync:hostStr];
        if (httpDnsIP) {
            return YES;
        }
    }

    return NO;
}

//对拦截的request进行处理,修改host,添加cookie
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    mutableReqeust = [self redirectHostInRequset:mutableReqeust];
    return [mutableReqeust copy];
}

+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request {
    if ([request.URL host].length == 0) {
        return request;
    }
    
    //从URL中获取域名
    NSString *originUrlString = [request.URL absoluteString];
    NSString *originHostString = [request.URL host];
    
    NSRange hostRange = [originUrlString rangeOfString:originHostString];
    if (hostRange.location == NSNotFound) {
        return request;
    }
    
    HttpDnsService *httpdns = [HttpDnsService sharedInstance];
    NSString *httpDnsIP = [httpdns getIpByHostAsync:originHostString];
    
    if (httpDnsIP) {
        //替换包头中的url开头的域名
        NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:httpDnsIP];
        NSURL *url = [NSURL URLWithString:urlString];
        request.URL = url;
        
        //注意:若包头的host(key-value中的host)本身就是一个IP,则需要将这个IP替换成域名(该域名需要从referer中获取)
        if ([self isValidIP:originHostString]) {
            //从referer中获取域名
            NSString *referStr = [request valueForHTTPHeaderField:@"Referer"];
            NSArray *firstArray = [referStr componentsSeparatedByString:@"://"];
            NSString *secondStr;
            if (firstArray.count >= 2) {
                secondStr = firstArray[1];
            } else if (firstArray.count == 1) {
                secondStr = firstArray[0];
            }
            if (secondStr) {
                NSRange range = [secondStr rangeOfString:@"/"];
                if (range.length > 0) {
                    originHostString = [[secondStr componentsSeparatedByString:@"/"] firstObject];
                    originUrlString = [originUrlString stringByReplacingOccurrencesOfString:httpDnsIP withString:originHostString];
                }
            }
        }
        //将从referer中取出的域名,放到请求包头的Host中
        [request setValue:originHostString forHTTPHeaderField:@"Host"];
        
        //设置http的header的cookie
        NSArray *cookiesArray = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[NSURL URLWithString:originUrlString]];
        NSDictionary *cookieDict = [NSHTTPCookie requestHeaderFieldsWithCookies:cookiesArray];
        NSString *cookie = [cookieDict objectForKey:@"Cookie"];
        [request setValue:cookie forHTTPHeaderField:@"Cookie"];
    }
    return request;
}

/**
 *判断一个字符串是否是一个IP地址
 **/
+ (BOOL)isValidIP:(NSString *)ipStr {
    if (nil == ipStr) {
        return NO;
    }
    
    NSArray *ipArray = [ipStr componentsSeparatedByString:@"."];
    if (ipArray.count == 4) {
        for (NSString *ipnumberStr in ipArray) {
            int ipnumber = [ipnumberStr intValue];
            if (!(ipnumber>=0 && ipnumber<=255)) {
                return NO;
            }
        }
        return YES;
    }
    return NO;
}

- (void)startLoading {
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    //标识该request已经处理过了,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
    
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.protocolClasses = @[[CustomURLProtocol class]];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    self.dnstask = [session dataTaskWithRequest:mutableReqeust];
    NSString *referStr = [mutableReqeust valueForHTTPHeaderField:@"Referer"];
    NSLog(@"start loading httpDNS********************\nstart loading httpDNS \n *****url :%@\n *****host:%@ \n *****referer:%@\n", mutableReqeust.URL, [mutableReqeust valueForHTTPHeaderField:@"Host"], referStr);
    [self.dnstask resume];
}

- (void)stopLoading {
    [self.dnstask cancel];
}

#pragma mark NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
    
    completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    [[self client] URLProtocol:self didLoadData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 请求完成,成功或者失败的处理
    if (!error) {
        //成功
        [self.client URLProtocolDidFinishLoading:self];
    } else {
        //失败
        [self.client URLProtocol:self didFailWithError:error];
    }
}

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
    /*
     * 创建证书校验策略
     */
    NSMutableArray *policies = [NSMutableArray array];
    if (domain) {
        [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
    } else {
        [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
    }
    /*
     * 绑定校验策略到服务端的证书上
     */
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
    /*
     * 评估当前serverTrust是否可信任,
     * 官方建议在result = kSecTrustResultUnspecified 或 kSecTrustResultProceed
     * 的情况下serverTrust可以被验证通过,https://developer.apple.com/library/ios/technotes/tn2232/_index.html
     * 关于SecTrustResultType的详细信息请参考SecTrust.h
     */
    SecTrustResultType result;
    SecTrustEvaluate(serverTrust, &result);
    if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {
        return YES;
    }
    return NO;
}


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
    if (!challenge) {
        return;
    }
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSURLCredential *credential = nil;
 
    
    /*
     * 获取原始域名信息。
     */
    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
    if (!host) {
        host = self.request.URL.host;
    }
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    } else {
        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    }
    // 对于其他的challenges直接使用默认的验证方案
    completionHandler(disposition,credential);
}

@end
需要对request进行的处理说明:

1.将URL中的域名替换成HTTPDNS解析出来的IP

2.添加包头Host

3.添加自己想要添加的Cookie,这一步视需求而定


HTTPDNS的注册初始化方法,在官网中已经说的很明确了,不做过多解释

HTTPDNS SDK手册链接 https://help.aliyun.com/document_detail/30141.html?spm=5176.doc30113.6.577.e8LhPM

#pragma mark -- HttpDNSDegradationDelegate
- (BOOL)shouldDegradeHTTPDNS:(NSString *)hostName {
    //根据HTTPDNS使用说明,存在网络代理情况下需降级为Local DNS
    if (self.configureProxies) {
        return YES;
    }
    return NO;
}

- (void)configHTTPDNS {
    self.configureProxies = [NetworkManager configureProxies];
    //注册CustomURLProtocol(NSURLProtocol子类)
    [NSURLProtocol registerClass:[CustomURLProtocol class]];
    
    // 设置AccoutID,当您开通HTTPDNS服务时,您可以在控制台获取到您对应的Accout ID信息
    HttpDnsService *httpdns = [[HttpDnsService alloc] initWithAccountID:156711];
    httpdns.delegate = self;
    
    //设置预解析域名列表
    NSArray * hosts = [[NSArray alloc] initWithObjects: @"你的域名", nil];//这里写上你要通过HTTPDNS解析的域名
    [httpdns setPreResolveHosts:hosts];
    
    //是否允许HTTPDNS返回TTL过期的域名
    [httpdns setExpiredIPEnabled:YES];
    
    //本地日志log开关,测试环境打开
    [httpdns setLogEnabled:NO];
    
    //使用缓存机制
    [httpdns setCachedIPEnabled:YES];
}
configHTTPDNS方法需要在APPdelegate的 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

中调用

对于iOS HTTPDNS的 SNI场景说明:HTTPDNS的官网并没有成熟的SNI场景解决方案(针对iOS,安卓是有的)。所以我并没有支持SNI场景。也希望其他朋友们珍重。如有大能使用了,请不吝赐教,谢谢!!


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值