Popular science film

1. Harm of DNS hijacking

I don’t know if you have noticed such a phenomenon that when you open some web pages, some irrelevant content will pop up strange (strange) things

The reason for this is DNS hijacking, where strange things are maliciously inserted into the links of web pages we normally read. Not only that, DNS hijacking can do a lot of damage to our personal information security, phishing sites and the like, maybe the site we visit is not the one we need, or it doesn’t open at all, and sometimes it consumes too much traffic.

2. What is DNS resolution

Now if we visit a website www.baidu.com and press Enter to go to baidu page display on our computer, we will go through the following steps

  • 1: The computer will send a request to our operators (mobile, telecom, Unicom, etc.) to open www.baidu.com.
  • 2: After receiving the request, the carrier will search its DNS server for the IP address of the server corresponding to the domain name www.baidu.com (that is, the IP address of baidu’s server), for example, 180.149.132.47.
  • 3: The operator uses the IP address obtained in the second step to find baidu’s server and request data to be returned to us.

The second step is what we call a DNS lookup process, the relationship between domain name and IP address is our, the relationship between the name and id number is to mark a person or a web site, only the IP address \ id just a bunch of meaningless Numbers, identification degree is low, and is not good, so will the IP with a domain name in order to distinguish, Or do more personalized, but if you really want to accurately distinguish or rely on id number or IP, so DNS resolution emerged.

3: What is DNS hijacking

DNS hijacking refers to the interception of domain name resolution requests during THE DNS resolution process, and then do some own processing, such as returning a fake IP address or doing nothing to make the request unresponsive. The effect is that the specific network cannot respond to or access a fake ADDRESS. There are two fundamental reasons:

  • 1: malicious attack, intercept the parsing process of operators, embed their own illegal things in it.
  • 2: for the sake of interests or some other factors, operators allow some third parties to advertise in their links.
4: Prevents DNS hijacking

Knowledge of the relevant materials of the DNS hijacking we knew, prevent NDS hijack will start from the second step, because the DNS lookup process is operators to operate, we can’t go to interfere with them, or we also became the hijackers, so we have to do is before we request for our request link to make some changes, Change our original request link www.baidu.com to 180.149.132.47, and then request to go out. In this way, after the operator gets our request and finds that we directly use the IP address, it will directly release us, instead of going through its own DNS resolution. That is to say, we have done the things the operators want to do first. Do not take his DNS resolution will not exist DNS hijacked problem, is fundamentally solved.

Technical articles

5: Actual operation in the project
5.1: DNSPOD correlation

As we know, it is very simple to replace the interface requested in the project with IP. The URL is a string, and the domain name replaces THE IP, which is nothing more than a string replacement. Indeed, this part has no technical content, and now, like Aliyun (no open source), Qiniuyun (open source), Some of the larger platforms in this respect also have a more mature solution, an SDK, pass a common URL into it will return a domain name to be replaced by the IP URL, it is also easier to use, here to say the source of IP address, how to get a domain name corresponding to the IP? Here is the need to use another service – HTTPDNS, the domestic more famous is DNSPOD, including Ali, qiniu and so on also use their DNS service to resolve, is this

It’s going to provide us with an interface, and we’re going to request that interface in the form of an HTTP request, with our domain name, and they’re going to send back a list of IP addresses that correspond to that domain name. Something like this:

119.29.29.29 is the fixed server address of DNSPOD. The TTL parameter means whether the result is returned with TTL. It is a BOOL. Id is the KEY that he gave us when we registered with dnspod NSString * URL = [NSString StringWithFormat: @ "http://119.29.29.29/d?ttl=1&dn=www.baidu.com&id=KEY"); NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&networkError];Copy the code

It is ok to use synchronous or asynchronous, depending on your business needs.

5.2: Use in the project

Dnspod actually the hardest part is part of the access, because different APP different network environment will lead to all sorts of problems, if you are a new project then access the difficulty will be greatly reduced, because you can encapsulate a web request, the DNS related logic is encapsulated to their networks in the request, So you get control of all the network layers of your APP and you can do whatever you want, but it’s difficult to build DNS anti-hijacking into an already well-established APP, Because you can’t get control of all network requests, in this article I mainly use NSURLProtocol + Runtime hook to handle these things. NSURLProtocol is a kind of iOS dark magic that can intercept any request from APP URL Loading System, including the following

  • File Transfer Protocol (ftp://)
  • Hypertext Transfer Protocol (http://)
  • Hypertext Transfer Protocol with encryption (https://)
  • Local file URLs (file:///)
  • Data URLs (data://)

If your request is not in the above list, it can not be blocked, such as WKWebview, AVPlayer(special, although the request is HTTP/HTTPS but does not go through this system, apple father is such ~), in fact, for normal use only NSURLProtocol is enough. NSURLProtocol is a class that we can’t use directly, we need to create a subclass of it and manipulate it in our subclass like this

// Register custom protocol [NSURLProtocol registerClass:[CustomURLProtocol Class]]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; configuration.protocolClasses = @[[CustomURLProtocol class]];Copy the code

In this class we can intercept the request and then process it. There are four very important methods in this class

+ (BOOL)canInitWithRequest:(NSURLRequest *)request; + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request; - (void)startLoading; // For intercepted requests, the NSURLProtocol object calls this method when it stops loading - (void)stopLoading;Copy the code
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;

The return value tells NSUrlProtocol whether to block incoming requests, such as I only block HTTP requests, or a domain name request, etc

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;

If the above method returns YES then the request will be sent to this place, which usually does nothing and returns the request directly

– (void)startLoading;

This is where we’re going to do some processing on the request that we’re intercepting, the IP-to-domain substitution that we’re doing in this article, and then we’re going to forward the request, like this

- (void)startLoading {/// Where customRequest is the processed request (after domain name is replaced) NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil]; NSURLSessionDataTask *task = [session dataTaskWithRequest:customRequest]; [task resume]; }Copy the code

You can use any method in -startLoading to forward requests held by protocol objects, including NSURLSession, NSURLConnection or even AFNetworking. As long as you can pass data back to the client in the callback method to help it render correctly, for example:

- (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];
}
Copy the code

The client will explain that later.

– (void)stopLoading;

The approximate execution flow of the call after the request is completed is as follows

There is a variable in NSURLProtocol that runs through

/ *! @method client @abstract Returns the NSURLProtocolClient of the receiver. @result The NSURLProtocolClient of the receiver. */ @property (nullable, readonly, retain) id <NSURLProtocolClient> client;Copy the code

You can think that this is the sender of the request. For example, A wants to send A message to B, but A goes to the post office due to the distance. A tells the post office the content of the message, and A registers his name in the post office so that the post office can inform A to check it when B has feedback. In this example, the post office is the NSURLProtocol, and the name A registers with the post office is the client. All clients implement the NSURLProtocolClient protocol, which transmits data to other objects when an HTTP request is made and a response is received:

@protocol NSURLProtocolClient <NSObject> ... - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy; - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data; - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol; . @endCopy the code

Of course, there are many other methods in this protocol, such as HTTPS authentication, redirection, and response caching methods, and you need to call these proxy methods when appropriate to pass information. To the DNS normally parsing process has ended, if you find that, in accordance with the above operation after did not achieve the desired effect so please look down, (usually done above operating the format of the original URL will be http://123.456.789.123/XXX/XXX/XXX. If you find your request unsuccessful, read on.)

6: Pit points encountered
6.1: As we know, the carrier used to determine a URL according to the domain name. After we changed the domain name to IP, although the carrier did not need to help us resolve it, the carrier was still confused when it received a string of numbers. We still need to send the domain name to them, but it could not be transmitted in a normal way. We need to add the original domain name to the host field in the Header of the HTTP request. According to the provisions of THE HTTP protocol, if the domain name cannot be found in the URL, we will look for it in the Header. In this way, we not only tell the domain name to the carrier, but also directly specify the IP address, which must be configured. Otherwise the request is unsuccessful.
[mutableRequest setValue:self.request.URL.host forHTTPHeaderField:@"HOST"];
Copy the code
It’s ok to put a Header in there and make a request, but there are special cases where you need to put a cookie in there, which is also in the Header
[mutableRequest setValue:YOUR Cookie forHTTPHeaderField:@"Cookie"];
Copy the code
6.2: Regarding AfNetworking, most network requests are now based on AfNetworking. There is a pit here, as we know it was when we registered for CustomProtocol
// Register custom protocol [NSURLProtocol registerClass:[CustomURLProtocol Class]]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; configuration.protocolClasses = @[[CustomURLProtocol class]];Copy the code
Add the CustomProtocol in the configuration of the system. The protocolClasses are an array of customProtocols. Let’s take a look at the initialization method of AFNetworking.
AFHTTPSessionManager * sessionManager = [AFHTTPSessionManager manager];
Copy the code
I’m sure you would normally do it this way, but here I’m going to say that manager is not a simple method, and it always ends up being a method
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (! self) { return nil; } if (! configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self.sessionConfiguration = configuration; self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; ...}Copy the code
Note the second check, if no configuration is passed in, it will create a default one, so that the classes registered in the Configuration protocolClasses are all replaced by this new configuration, so they cannot be resolved. The method I adopted here is Runtime hook, because hook third-party code is not a good method, so I directly hook NSURLSession sessionWithConfiguration method, Because by looking at the source code of Afnetworking, that’s where it ended up. After Hook, change your configuration, like this
+ (NSURLSession *)swizzle_sessionWithConfiguration:(NSURLSessionConfiguration *)configuration { NSURLSessionConfiguration *newConfiguration = configuration; // Insert our custom protocol if (Configuration) {NSMutableArray *protocolArray = [NSMutableArray arrayWithArray:configuration.protocolClasses]; [protocolArray insertObject:[CustomProtocol class] atIndex:0]; newConfiguration.protocolClasses = protocolArray; } else { newConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSMutableArray *protocolArray = [NSMutableArray arrayWithArray:configuration.protocolClasses]; [protocolArray insertObject:[CustomProtocol class] atIndex:0]; newConfiguration.protocolClasses = protocolArray; } return [self swizzle_sessionWithConfiguration:newConfiguration]; }Copy the code
And then it worked out perfectly. But notice that there are two ways to do it systematically
/*
 * Customization of NSURLSession occurs during creation of a new session.
 * If you only need to use the convenience routines with custom
 * configuration options it is not necessary to specify a delegate.
 * If you do specify a delegate, the delegate will be retained until after
 * the delegate has been sent the URLSession:didBecomeInvalidWithError: message.
 */
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
Copy the code
These two methods can not be sure which one will eventually go, so to be on the safe side, they hook in the same way
6.3: AVPlayer request, AVPlayer is our iOS system built-in video playing framework, there are many uses, but this is a bit of a hole, because although AVPlayer also has HTTP/HTTPS /file…… AVPlayer does not use URL Loading System to load AVPlayer requests. This means that AVPlayer requests cannot be blocked by our CustomProtocol. It could have been intercepted during our normal debugging. On the apple actually mean AVPlayer in real machine debugging and simulation debugging is not a set of strategies, that is to say, when running in the simulator is completely normal, can be intercepted can also be parsed, but in the real machine is contrary to the above, because we finally will be subject to real machine, so we take the method or the hook, Because we need to configure the related things before we send the media URL to AVPlayer, such as changing the domain name, adding host, etc., so we need to find the entry of AVPlayer, first look at the initialization method, I found that the project uses an AVURLAsset to initialize AVPlayer, So what is AVURLAsset? Continue to look at the AVURLAsset initialization method, you can find this method:
/ *! @method initWithURL:options: @abstract Initializes an instance of AVURLAsset for inspection of a media resource. @param URL An instance of NSURL that  references a media resource. @param options An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above. @result An instance of AVURLAsset. */ - (instancetype)initWithURL:(NSURL *)URL  options:(nullable NSDictionary<NSString *, id> *)options NS_DESIGNATED_INITIALIZER;Copy the code
The URL is the URL that we send to AVPlayer to play, and we Hook it when we find the target. We don’t need to talk about the specific process or string substitution, but one thing to note is that AS I mentioned above, after IP is replaced with domain name, we also need to set the Host of the request. But what if this place only has a URL and no Request? And that’s actually what the opinion argument in this method does, you can add cookies or something like httpHeader, you can add these keys
AVF_EXPORT NSString *const AVURLAssetPreferPreciseDurationAndTimingKey NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVURLAssetReferenceRestrictionsKey NS_AVAILABLE(10_7, 5_0);
AVF_EXPORT NSString *const AVURLAssetHTTPCookiesKey NS_AVAILABLE_IOS(8_0);
AVF_EXPORT NSString *const AVURLAssetAllowsCellularAccessKey NS_AVAILABLE_IOS(10_0);
Copy the code
But did not find the Host related Key, in fact, the Key is some are exposed AVURLAssetHTTPHeaderFieldsKey just because this Key. I am not sure whether this place is apple’s private API. I have checked a lot of information on the Internet, but there is no answer. Even I went to ask apple developers personally, But Apple did not give any reply
[self swizzle_initWithURL:videoURL options:@{AVURLAssetHTTPHeaderFieldsKey : @{@"Host":host}}]
Copy the code
Such use is no problem, but after all is not exposed method, we can’t use, so blatant for string or are better to avoid, as long as the KEY can not expressly appeared, here I am using a encryption, KEY into a cipher text then this place through decryption access, like this:
/ / the encrypted KEY const nsstrings * headerKey = @ "35905 ff45afa4c579b7de2403c7ca0ccb59aa83d660e60c9d444afe13323618f"; Return [self swizzle_initWithURL:videoURL options:@{[self getRequestHeaderKey] : @{@"Host":host}}];Copy the code
And then you’re done, AVPlayer can play if DNS is hijacked,
6.4: POST request is also a big hole, we know that HTTP POST request will contain a body, which contains the parameters we need to upload some information, for POST request our NSURLProtocol can normally intercept, But we intercepted it and found that we got the body nil no matter what! Then I checked some information and found that it was Apple’s dad. NSURLProtocol does not get the HTTPBody of the Request when intercepting the POST Request of the NSURLSession. The HTTPBody is an NSData type, which can be used as binary content. And there is no size limit, so it can be very large, for performance reasons, simply do not copy when intercepting (streaming face). To solve this problem, we can put the Body data into the Header, but the size of the Header seems to be limited. I tried 2M without any problem, but if the size exceeds 10M, I will Request timeout directly. And this doesn’t work when the Body data is binary, because the headers are all text data, so another way to do this is to use an NSDictionary or an NSCache to hold the Body data that’s not being requested, and use the URL as the key, and finally, don’t use an NSURLSession, Old honest practical old NSURLConnection Forget it… You think this is the end of it? HTTPBodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream = bodyStream
Pragma mark - #pragma mark handles POST requests using HTTPBodyStream to handle the BODY - (NSMutableURLRequest) *)handlePostRequestBodyWithRequest:(NSMutableURLRequest *)request { NSMutableURLRequest * req = [request mutableCopy]; if ([request.HTTPMethod isEqualToString:@"POST"]) { if (! request.HTTPBody) { uint8_t d[1024] = {0}; NSInputStream *stream = request.HTTPBodyStream; NSMutableData *data = [[NSMutableData alloc] init]; [stream open]; while ([stream hasBytesAvailable]) { NSInteger len = [stream read:d maxLength:1024]; if (len > 0 && stream.streamError == nil) { [data appendBytes:(void *)d length:len]; } } req.HTTPBody = [data copy]; [stream close]; } } return req; }Copy the code
Then the reQ will be a body request, and you can happily make a post request.
6.5: WKWebview is a new browser control, so I will not say more here. WKWebview does not use URL Loading System, so it will not be intercepted, but there is a way, but because it is not used in this project, so I do not have too much research. I will write a blog about this later, it is not difficult. It’s still the runtime.
SNI is an Indication of the size of a Server’s domain Name and the size of a certificate. This is an Indication of the size of a Server’s domain Name and the size of a certificate. It works by sending the Hostname of the site to be visited before connecting to the server to establish an SSL connection, and the server returns an appropriate certificate based on that domain name. In fact, the SNI environment is not much explained here, **Aliyun Document** has a very clear explanation, and he also has the processing documents of Android and iOS in the SNI environment. We found that the Android part is very detailed, but when it comes to the iOS side, it is like this:

Three lines of text plus three links and that’s it. Most companies or projects do not have such heavy Httpdns requirements, so there will not be this environment, even if encountered also directly shut down Httpdns. Later, I had to use CFNetwork to implement it. I’m not going to paste the actual code because it involves some internal code, but I’m going to put my **Main references** to everyone. There’s a little bit of a trick here, because it’s said that CFNetwork is a low-level network implementation, and there’s a lot of stuff that developers have to deal with by themselves like releasing some variables and things like that, so we can use it as little as possible, Because Cfnetwork serves the SNI(HTTPS) environment, we can distinguish whether to use the upper-layer network request forwarding or the lower-layer Cfnetwork for forwarding during interception and judgment.
If ([self.request.url.scheme isEqualToString:@" HTTPS "]) {// Use CFnetwork curRequest = req; self.task = [[CustomCFNetworkRequestTask alloc] initWithURLRequest:originalRequest swizzleRequest:curRequest delegate:self]; if (self.task) { [self.task startLoading]; }} else {/ / use normal network configuration request NSURLSessionConfiguration * = [NSURLSessionConfiguration defaultSessionConfiguration]; self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURLSessionTask *task = [self.session dataTaskWithRequest:req]; [task resume]; }Copy the code
Here’s what I did.
6.7: The class methods in NSURLProtocol can send synchronization requests, but the instance method will be blocked when sending synchronization requests, so the instance method cannot have any blocking. Or you get stuck.
7:

After completing the above steps, you will find that in the case of DNS failure, all other apps except wechat QQ(they have also done DNS resolution) can not access the Internet, but your App can still browse the network data normally. These are some problems I have encountered recently. If you have any questions, please contact me in time