首页
社区
课程
招聘
[原创]android 4.4 从url.openConnection到Dns解析
发表于: 2019-6-21 19:29 6842

[原创]android 4.4 从url.openConnection到Dns解析

2019-6-21 19:29
6842

其实并不是要分析Dns流程的,一开始是分析Android设置wifi代理,怎么起到全局代理的作用的。
因为坏习惯,在线看源码,全凭记忆,没有做笔记的习惯,忽略了一个点,直接分析到dns了,回过头才发现分析过了。而之前群里有人问应用进程hook hosts文件能不能改dns,很久之前分析过4.1,记得是有个远程进程负责dns解析的,但是太久了,细节都忘光了。

所以分析完了wifi代理、dns后记录下吧,年纪大了记忆力真的不如以前了。

下一篇 wifi代理流程。
除了http外,ftp和webview是怎么代理的。
以及6.0以下不用root,不申请特殊权限,怎么设置wifi代理。由此引出app检测代理的必要,之前考虑实际的攻击场景可能就是钓鱼wifi,dns劫持等,所以认为一些app检测代理或者不走代理主要是应对渗透抓包,但是忽略了安卓6.0及以下版本恶意app可以设置wifi代理,把http(s)代理到服务器的,如果没有做正确的证书校验或者诱导用户安装证书,https也可以中间人。

以及除了root后监控网卡解析出http代理和使用VpnService外怎么通用的hook达到代理的目的,比如自己封装发送http协议,不使用标准api,第三方框架等、比如直接socket连接,写http。因为工作中还是有一部分这样不走寻常路的应用,往往web渗透的同事即没root机器,版本还很高(比如8.0的手机信任证书),甚至还在驻场的情况,对其进行支持就很头疼(多开框架)。

```
URL url = new URL("c34K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8b7`.`.");
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.2.1", 8081));

HttpURLConnection connection = (HttpURLConnection) url.openConnection(/*proxy*/);
connection.setConnectTimeout(10000);
//                        connection.addRequestProperty("accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
connection.addRequestProperty("accept-encoding", "gzip, deflate, br");
//                        connection.addRequestProperty("accept-language", "zh-CN,zh;q=0.9");
connection.setRequestMethod("GET");
InputStream is = connection.getInputStream();
Map<String, List<String>> map = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
    Log.e("zhuo", entry.getKey()+"="+entry.getValue());
}
GZIPInputStream gis = new GZIPInputStream(is);
byte buf[] = new byte[1024];
gis.read(buf);
Log.e("zhuo", new String(buf));
connection.disconnect();

```

以上是一个简单到网络请求,而如果使用proxy的话会产生异常,我们借助异常堆栈可以较清晰的看到函数调用流程。(至于为什么产生这个异常,最后分析,而有经验的可能看下异常已经知道了)
```
W/System.err: java.net.ConnectException: failed to connect to localhost/127.0.0.1 (port 8081) after 10000ms: isConnected failed: ECONNREFUSED (Connection refused)
W/System.err:     at libcore.io.IoBridge.isConnected(IoBridge.java:223)
        at libcore.io.IoBridge.connectErrno(IoBridge.java:161)
        at libcore.io.IoBridge.connect(IoBridge.java:112)
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192)
        at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459)
        at java.net.Socket.connect(Socket.java:843)
        at com.android.okhttp.internal.Platform.connectSocket(Platform.java:131)
        at com.android.okhttp.Connection.connect(Connection.java:101)
        at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
        at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
        at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
        at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
        at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
        at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:179)
        at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:246)
```
可以发现真正的网络请求是从getInputStream开始。但是为什么完全了解整个流程,我们还是简单看下Url类
```
    transient URLStreamHandler streamHandler;
    
    //仅是包装,而streamHandler的赋值是在构造函数
    public URLConnection openConnection() throws IOException {
            return streamHandler.openConnection(this);
    }

    public URL(String spec) throws MalformedURLException {
            this((URL) null, spec, null);
    }

    //被上面的构造函数调用
    public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
        if (spec == null) {
            throw new MalformedURLException();
        }
        if (handler != null) {
            streamHandler = handler;
        }
        spec = spec.trim();

        protocol = UrlUtils.getSchemePrefix(spec);
        int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;

        // If the context URL has a different protocol, discard it because we can't use it.
        if (protocol != null && context != null && !protocol.equals(context.protocol)) {
            context = null;
        }

        // Inherit from the context URL if it exists.
        if (context != null) {
            set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(),
                    context.getUserInfo(), context.getPath(), context.getQuery(),
                    context.getRef());
            if (streamHandler == null) {
                streamHandler = context.streamHandler;
            }
        } else if (protocol == null) {
            throw new MalformedURLException("Protocol not found: " + spec);
        }

        if (streamHandler == null) {
            //赋值
            setupStreamHandler();
            if (streamHandler == null) {
                throw new MalformedURLException("Unknown protocol: " + protocol);
            }
        }

        // Parse the URL. If the handler throws any exception, throw MalformedURLException instead.
        try {
            streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
        } catch (Exception e) {
            throw new MalformedURLException(e.toString());
        }
    }

    void setupStreamHandler() {
        ...
        else if (protocol.equals("http")) {
            try {
                String name = "com.android.okhttp.HttpHandler";
                streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        } else if (protocol.equals("https")) {
            try {
                String name = "com.android.okhttp.HttpsHandler";
                streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        ...
    }

```
(因为基本上所有的https相关的类都是继承自http,覆盖某些方法,整体逻辑是类似的,为了少些跳转,我们直接看http相关的即可)
根据以上分析,发现最终实现类为com.android.okhttp.HttpsHandler,但是在源码里搜索并没有找到这个类。找到的有external/okhttp/android/main/java/com/squareup/okhttp/HttpHandler.java
看一下external/okhttp/jarjar-rules.txt
rule com.squareup.** com.android.@1
所以会在编译后改包名,所以就是我们要找的HttpHandler,而这个模块好像是早期的okhttp,集成进来改名应该是为了防止冲突,影响应用使用更新的okhttp。
```
public class HttpHandler extends URLStreamHandler {
    @Override protected URLConnection openConnection(URL url) throws IOException {
        return newOkHttpClient(null /* proxy */).open(url);
    }

    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
        if (url == null || proxy == null) {
            throw new IllegalArgumentException("url == null || proxy == null");
        }
        return newOkHttpClient(proxy).open(url);
    }

    @Override protected int getDefaultPort() {
        return 80;
    }

    protected OkHttpClient newOkHttpClient(Proxy proxy) {
        OkHttpClient client = new OkHttpClient();
        client.setFollowProtocolRedirects(false);
        if (proxy != null) {
            client.setProxy(proxy);
        }

        return client;
    }
}

//可以看到确实如所说的http和https的关系,所以之后的分析,不在列出https专属部分
public final class HttpsHandler extends HttpHandler {
    private static final List<String> ENABLED_TRANSPORTS = Arrays.asList("http/1.1");

    @Override protected int getDefaultPort() {
        return 443;
    }

    @Override
    protected OkHttpClient newOkHttpClient(Proxy proxy) {
        OkHttpClient client = super.newOkHttpClient(proxy);
        client.setTransports(ENABLED_TRANSPORTS);

        HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
        // Assume that the internal verifier is better than the
        // default verifier.
        if (!(verifier instanceof DefaultHostnameVerifier)) {
            client.setHostnameVerifier(verifier);
        }

        return client;
    }
}

```
分析发现openConnection调用的是OkHttpClient的open函数。
```
    //对应上了我们异常日志中的HttpsURLConnectionImpl,看名字也知道肯定是继承自HttpsURLConnection
  HttpURLConnection open(URL url, Proxy proxy) {
    String protocol = url.getProtocol();
    OkHttpClient copy = copyWithDefaults();
    copy.proxy = proxy;

    if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
    if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
    throw new IllegalArgumentException("Unexpected protocol: " + protocol);
  }

  /**
   * Returns a shallow copy of this OkHttpClient that uses the system-wide default for
   * each field that hasn't been explicitly configured.
   */
  private OkHttpClient copyWithDefaults() {
    OkHttpClient result = new OkHttpClient(this);
    result.proxy = proxy;
    result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
    result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
    result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
    result.sslSocketFactory = sslSocketFactory != null
        ? sslSocketFactory
        : HttpsURLConnection.getDefaultSSLSocketFactory();
    result.hostnameVerifier = hostnameVerifier != null
        ? hostnameVerifier
        : OkHostnameVerifier.INSTANCE;
    result.authenticator = authenticator != null
        ? authenticator
        : HttpAuthenticator.SYSTEM_DEFAULT;
    result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
    result.followProtocolRedirects = followProtocolRedirects;
    result.transports = transports != null ? transports : DEFAULT_TRANSPORTS;
    result.connectTimeout = connectTimeout;
    result.readTimeout = readTimeout;
    return result;
  }

```
看异常知道还是调用的HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:179),略过https
```
    @Override public final InputStream getInputStream() throws IOException {
    if (!doInput) {
      throw new ProtocolException("This protocol does not support input");
    }

    //触发在这里
    HttpEngine response = getResponse();

    // if the requested file does not exist, throw an exception formerly the
    // Error page from the server was returned if the requested file was
    // text/html this has changed to return FileNotFoundException for all
    // file types
    if (getResponseCode() >= HTTP_BAD_REQUEST) {
      throw new FileNotFoundException(url.toString());
    }

    InputStream result = response.getResponseBody();
    if (result == null) {
      throw new ProtocolException("No response body exists; responseCode=" + getResponseCode());
    }
    return result;
  }
  
  private HttpEngine getResponse() throws IOException {
    //HttpEngine赋值
    initHttpEngine();

    if (httpEngine.hasResponse()) {
      return httpEngine;
    }

    while (true) {
      if (!execute(true)) {
        continue;
      }
      ...
  }

    private boolean execute(boolean readResponse) throws IOException {
    try {
      httpEngine.sendRequest();
      if (readResponse) {
        httpEngine.readResponse();
      }

      return true;
    } catch (IOException e) {
      if (handleFailure(e)) {
        return false;
      } else {
        throw e;
      }
    }
  }
  
```

最终调用HttpEngine类的sendRequest
```
public final void sendRequest() throws IOException {
    ...

    //略过设置请求头,缓存等
    if (responseSource.requiresConnection()) {
      sendSocketRequest();
    } else if (connection != null) {
      client.getConnectionPool().recycle(connection);
      connection = null;
    }
  }
  
  private void sendSocketRequest() throws IOException {
    if (connection == null) {
        //连接
      connect();
    }

    if (transport != null) {
      throw new IllegalStateException();
    }

    transport = (Transport) connection.newTransport(this);

    if (hasRequestBody() && requestBodyOut == null) {
      // Create a request body if we don't have one already. We'll already
      // have one if we're retrying a failed POST.
      requestBodyOut = transport.createRequestBody();
    }
  }

    /** Connect to the origin server either directly or via a proxy. */
  protected final void connect() throws IOException {
    if (connection != null) {
      return;
    }
    if (routeSelector == null) {
      String uriHost = uri.getHost();
      if (uriHost == null) {
        throw new UnknownHostException(uri.toString());
      }
      SSLSocketFactory sslSocketFactory = null;
      HostnameVerifier hostnameVerifier = null;
      if (uri.getScheme().equalsIgnoreCase("https")) {
        sslSocketFactory = client.getSslSocketFactory();
        hostnameVerifier = client.getHostnameVerifier();
      }
      //这一部分待会回来再分析
      Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
          hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports());
      routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
          client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
    }
    //待会分析
    connection = routeSelector.next(method);
    if (!connection.isConnected()) {
        //最终调用的是connection.connect
      connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
      client.getConnectionPool().maybeShare(connection);
      client.getRoutesDatabase().connected(connection.getRoute());
    } else {
      connection.updateReadTimeout(client.getReadTimeout());
    }
    connected(connection);
    if (connection.getRoute().getProxy() != client.getProxy()) {
      // Update the request line if the proxy changed; it may need a host name.
      requestHeaders.getHeaders().setRequestLine(getRequestLine());
    }
  }

  
```

```
  public Connection(Route route) {
    this.route = route;
  }

  public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
      throws IOException {
    if (connected) {
      throw new IllegalStateException("already connected");
    }
    connected = true;
    //这里是直接new Socket()
    socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
    //后面的不用再追,因为在这里传入route.inetSocketAddress已经是127.0.0.1(因为我们自定义了proxy,如果没有使用,则域名已经变为ip),之后我们往回追,因为后面的就是socket建立连接,和本篇关系不大了。
    Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
    socket.setSoTimeout(readTimeout);
    in = socket.getInputStream();
    out = socket.getOutputStream();

    if (route.address.sslSocketFactory != null) {
      upgradeToTls(tunnelRequest);
    }

    // Use MTU-sized buffers to send fewer packets.
    int mtu = Platform.get().getMtu(socket);
    if (mtu < 1024) mtu = 1024;
    if (mtu > 8192) mtu = 8192;
    in = new BufferedInputStream(in, mtu);
    out = new BufferedOutputStream(out, mtu);
  }
```

至此顺着异常日志大概知道了网络请求的流程。

## Dns流程
按照倒叙的方式,我们看下
```
connection = routeSelector.next(method);

    public Connection next(String method) throws IOException {
    // Always prefer pooled connections over new connections.
    for (Connection pooled; (pooled = pool.get(address)) != null; ) {
      if (method.equals("GET") || pooled.isReadable()) return pooled;
      pooled.close();
    }

    // Compute the next route to attempt.
    if (!hasNextTlsMode()) {
      if (!hasNextInetSocketAddress()) {
        if (!hasNextProxy()) {
          if (!hasNextPostponed()) {
            throw new NoSuchElementException();
          }
          return new Connection(nextPostponed());
        }
        lastProxy = nextProxy();
        resetNextInetSocketAddress(lastProxy);
      }
      lastInetSocketAddress = nextInetSocketAddress();
      resetNextTlsMode();
    }

    boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
    //根据前面的分析route.inetSocketAddress=lastInetSocketAddress
    Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
    if (routeDatabase.shouldPostpone(route)) {
      postponedRoutes.add(route);
      // We will only recurse in order to skip previously failed routes. They will be
      // tried last.
      return next(method);
    }

    return new Connection(route);
  }

```
根据前面的route.inetSocketAddress,我们可以简单看下Route这个类,
```
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
      boolean modernTls) {
    if (address == null) throw new NullPointerException("address == null");
    if (proxy == null) throw new NullPointerException("proxy == null");
    if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
    this.address = address;
    this.proxy = proxy;
    this.inetSocketAddress = inetSocketAddress;
    this.modernTls = modernTls;
  }
```
因为inetSocketAddress不能为空,所以lastInetSocketAddress肯定就是后面用到的route.inetSocketAddress,而lastInetSocketAddress由nextInetSocketAddress函数赋值。

```
  private InetSocketAddress nextInetSocketAddress() throws UnknownHostException {
    InetSocketAddress result =
        new InetSocketAddress(socketAddresses[nextSocketAddressIndex++], socketPort);
    if (nextSocketAddressIndex == socketAddresses.length) {
      socketAddresses = null; // So that hasNextInetSocketAddress() returns false.
      nextSocketAddressIndex = 0;
    }

    return result;
  }
```
返回的是private InetAddress[] socketAddresses数组内的一个值,而对该数组赋值的函数只有一个,可以直接定位到真是幸福。

```
  private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
    socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!

    String socketHost;
    if (proxy.type() == Proxy.Type.DIRECT) {
      socketHost = uri.getHost();
      socketPort = getEffectivePort(uri);
    } else {
      SocketAddress proxyAddress = proxy.address();
      if (!(proxyAddress instanceof InetSocketAddress)) {
        throw new IllegalArgumentException(
            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
      }
      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
      socketHost = proxySocketAddress.getHostName();
      socketPort = proxySocketAddress.getPort();
    }

    // Try each address for best behavior in mixed IPv4/IPv6 environments.
    socketAddresses = dns.getAllByName(socketHost);
    nextSocketAddressIndex = 0;
  }
```
dns.getAllByName函数传入的是域名"ab1K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3u0S2K9h3c8#2i4K6u0W2j5$3!0E0",返回的域名对应的ip数组。
dns在上面的代码中设置过,为Dns.DEFAULT
```
routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
          client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());


public interface Dns {
  Dns DEFAULT = new Dns() {
    @Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
      return InetAddress.getAllByName(host);
    }
  };

  InetAddress[] getAllByName(String host) throws UnknownHostException;
}

```
Dns为接口只有一个默认的实现,调用InetAddress类的getAllByName
```
    private static InetAddress[] lookupHostByName(String host) throws UnknownHostException {
        ...
        //略过查找缓存,第一次没有缓存
        StructAddrinfo hints = new StructAddrinfo();
        hints.ai_flags = AI_ADDRCONFIG;
        hints.ai_family = AF_UNSPEC;
        // If we don't specify a socket type, every address will appear twice, once
        // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
        // anyway, just pick one.
        hints.ai_socktype = SOCK_STREAM;
        //入口
        InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
        // TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
        for (InetAddress address : addresses) {
            address.hostName = host;
        }
        addressCache.put(host, addresses);
    }
```
最终获取dns又由Libcore.os.getaddrinfo实现,而这个Libcore.os,追过的朋友应该知道有两三层包装,这里就不再浪费时间去追,直接看函数对应的native函数。
```
NATIVE_METHOD(Posix, getaddrinfo, "(Ljava/lang/String;Llibcore/io/StructAddrinfo;)[Ljava/net/InetAddress;"),
对应的c++实现为Posix_getaddrinfo

static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jobject javaHints) {
    ...
    //略过一些无关紧要的代码
    addrinfo* addressList = NULL;
    errno = 0;
    int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);
    UniquePtr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);
    if (rc != 0) {
        throwGaiException(env, "getaddrinfo", rc);
        return NULL;
    }

    ...
    //result为addressList->ai_addr    
    return result;
}

```
代码有些多,省略掉,主要就是getaddrinfo函数,之后返回的类数组赋值取的是addressList中的值,所以我们关注的是getaddrinfo函数的第四个参数。实现在libc中,bionic/libc/netbsd/net/getaddrinfo.c

```
int
getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res)
{

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-6-21 19:34 被卓桐编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (2)
雪    币: 5003
活跃值: (3585)
能力值: ( LV6,RANK:96 )
在线值:
发帖
回帖
粉丝
2
666, 排版没搞好
2019-6-21 23:38
0
雪    币: 0
活跃值: (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
厉害 学习了
2020-11-3 19:56
0
游客
登录 | 注册 方可回帖
返回