java 权限url权限_Android 4.4 从url.openConnection到DNS解析

efbddadd535cbba786969f45c90caeab.png

本文为看雪论坛精华文章

看雪论坛作者ID:卓桐

其实并不是要分析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("https://www.baidu.com");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 ENABLED_TRANSPORTS = Arrays.asList("http/1.1");@Override protected int getDefaultPort() {return 443;
    }@Overrideprotected 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,看名字也知道肯定是继承自HttpsURLConnectionHttpURLConnection 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;
  }

看异 常知道还是调用的HttpURLConnecti onImpl.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 typesif (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函数传入的是域名"www.baidu.com",返回的域名对应的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_getaddrinfostatic 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 addressListDeleter(addressList);if (rc != 0) {
        throwGaiException(env, "getaddrinfo", rc);return NULL;
    }
    ...//result为addressList->ai_addrreturn 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)
{return android_getaddrinfoforiface(hostname, servname, hints, NULL, 0, res);
}//该函数内容特别多,
int
android_getaddrinfoforiface(const char *hostname, const char *servname,const struct addrinfo *hints, const char *iface, int mark, struct addrinfo **res)
{
        ......// 取ANDROID_DNS_MODE环境变量。只有Netd进程设置了它const char* cache_mode = getenv("ANDROID_DNS_MODE");
    ......// 由于Netd进程设置了此环境变量,故Netd进程继续下面的流程,至于android_getaddrinfo_proxy函数,这里不写了或者开其他篇章再写,简单描述这个函数作用,通过socket和远程进程Netd通信,把参数封装后传输过去,就是要Netd进程代理解析dns,而这个Netd进程最后也会调用到libc中的android_getaddrinfoforiface函数,所以从这里开始,我们后面分析的代码其实已经不是应用进程了,二是Netd进程在执行。if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {// we're not the proxy - pass the request to themreturn android_getaddrinfo_proxy(hostname, servname, hints, res, iface);
    }/*
* hostname as alphabetical name.
* we would like to prefer AF_INET6 than AF_INET, so we'll make a
* outer loop by AFs.
*/for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;/* require exact match for family field */if (pai->ai_family != ex->e_af)continue;if (!MATCH(pai->ai_socktype, ex->e_socktype,
WILD_SOCKTYPE(ex))) {continue;
}if (!MATCH(pai->ai_protocol, ex->e_protocol,
WILD_PROTOCOL(ex))) {continue;
}if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
pai->ai_socktype = ex->e_socktype;if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
pai->ai_protocol = ex->e_protocol;//代码太多,不知道该怎么描述分析过程了,简单来说反推得出该函数为解析dns,*res = sentinel.ai_next;->cur = &sentinel;
error = explore_fqdn(pai, hostname, servname,
&cur->ai_next, iface, mark);while (cur && cur->ai_next)
cur = cur->ai_next;
}/* XXX */if (sentinel.ai_next)
error = 0;if (error)goto free;if (error == 0) {if (sentinel.ai_next) {
 good:
*res = sentinel.ai_next;return SUCCESS;
} else
error = EAI_FAIL;
}
 free:
 bad:if (sentinel.ai_next)
freeaddrinfo(sentinel.ai_next);
*res = NULL;return error;
}

代码很多,之后转入explore_fqdn,我们关心参数res。 忽略了传入的host为数字的情况,即直接是ip的,不再列出了,再开分支感觉很乱。

//static int
explore_fqdn(const struct addrinfo *pai, const char *hostname,const char *servname, struct addrinfo **res, const char *iface, int mark)
{struct addrinfo *result;struct addrinfo *cur;int error = 0;//_files_getaddrinfo为读取system/etc/hosts文件//_dns_getaddrinfo就是访问dns服务器了static const ns_dtab dtab[] = {NS_FILES_CB(_files_getaddrinfo, NULL)
{ NSSRC_DNS, _dns_getaddrinfo, NULL }, /* force -DHESIOD */NS_NIS_CB(_yp_getaddrinfo, NULL)
{ 0, 0, 0 }
};
assert(pai != NULL);/* hostname may be NULL *//* servname may be NULL */
assert(res != NULL);
result = NULL;/*
* if the servname does not match socktype/protocol, ignore it.
*/if (get_portmatch(pai, servname) != 0)return 0;switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
default_dns_files, hostname, pai, iface, mark)) {case NS_TRYAGAIN:
error = EAI_AGAIN;goto free;case NS_UNAVAIL:
error = EAI_FAIL;goto free;case NS_NOTFOUND:
error = EAI_NODATA;goto free;case NS_SUCCESS:
error = 0;for (cur = result; cur; cur = cur->ai_next) {
GET_PORT(cur, servname);/* canonname should be filled already */
}break;
}
*res = result;return 0;
free:if (result)
freeaddrinfo(result);return error;
}

虽然explore_fqdn函数比较长,但是其实只有一个点nsdispatch,其实这个函数就是执行dtab中的函数,下面贴出代码,就不详细分析了。

static nss_method
_nsmethod(const char *source, const char *database, const char *method,const ns_dtab disp_tab[], void **cb_data)
{int curdisp;if (disp_tab != NULL) {for (curdisp = 0; disp_tab[curdisp].src != NULL; curdisp++) {if (strcasecmp(source, disp_tab[curdisp].src) == 0) {
*cb_data = disp_tab[curdisp].cb_data;return (disp_tab[curdisp].callback);
}
}
}
*cb_data = NULL;return (NULL);
}int/*ARGSUSED*/
nsdispatch(void *retval, const ns_dtab disp_tab[], const char *database,const char *method, const ns_src defaults[], ...)
{
va_list ap;int i, result;const ns_src *srclist;int srclistsize;
nss_method cb;void *cb_data;/* retval may be NULL *//* disp_tab may be NULL */
assert(database != NULL);
assert(method != NULL);
assert(defaults != NULL);if (database == NULL || method == NULL || defaults == NULL)return (NS_UNAVAIL);
        srclist = defaults;
        srclistsize = 0;while (srclist[srclistsize].name != NULL)
                srclistsize++;
result = 0;for (i = 0; i < srclistsize; i++) {
cb = _nsmethod(srclist[i].name, database, method,
    disp_tab, &cb_data);
result = 0;if (cb != NULL) {
va_start(ap, defaults);
result = (*cb)(retval, cb_data, ap);
va_end(ap);if (defaults[0].flags & NS_FORCEALL)continue;if (result & srclist[i].flags)break;
}
}
result &= NS_STATUSMASK; /* clear private flags in result */return (result ? result : NS_NOTFOUND);
}

到这里出现了两个分支,一个是读取本地的hosts,一个是访问dns,先看本地的。

//NS_FILES_CB宏展开为#define NSSRC_FILES "files"
{ NSSRC_FILES, F, __UNCONST(C) },//default_dns_files为static const ns_src default_dns_files[] = {
{ NSSRC_FILES, NS_SUCCESS },
{ NSSRC_DNS, NS_SUCCESS },
{ 0, 0 }
};//读取本地hosts#define _PATH_HOSTS "/system/etc/hosts"static void
_sethtent(FILE **hostf)
{if (!*hostf)
*hostf = fopen(_PATH_HOSTS, "r" );else
rewind(*hostf);
}static int
_files_getaddrinfo(void *rv, void *cb_data, va_list ap)
{const char *name;const struct addrinfo *pai;struct addrinfo sentinel, *cur;struct addrinfo *p;
FILE *hostf = NULL;
name = va_arg(ap, char *);
pai = va_arg(ap, struct addrinfo *);// fprintf(stderr, "_files_getaddrinfo() name = '%s'\n", name);
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;//读取本地hosts
_sethtent(&hostf);//读取每一行解析域名和ipwhile ((p = _gethtent(&hostf, name, pai)) != NULL) {
cur->ai_next = p;while (cur && cur->ai_next)
cur = cur->ai_next;
}
_endhtent(&hostf);
*((struct addrinfo **)rv) = sentinel.ai_next;if (sentinel.ai_next == NULL)return NS_NOTFOUND;return NS_SUCCESS;
}

以上就是解析本地hosts,如果找到对应的域名、ip,就返回,如果没有,执行访问dns服务器,最后一个很长的函数。

static int
_dns_getaddrinfo(void *rv, void *cb_data, va_list ap)
{struct addrinfo *ai;
querybuf *buf, *buf2;const char *name;const struct addrinfo *pai;struct addrinfo sentinel, *cur;struct res_target q, q2;
res_state res;const char* iface;int mark;
name = va_arg(ap, char *);
pai = va_arg(ap, const struct addrinfo *);
iface = va_arg(ap, char *);
mark = va_arg(ap, int);//fprintf(stderr, "_dns_getaddrinfo() name = '%s'\n", name);memset(&q, 0, sizeof(q));memset(&q2, 0, sizeof(q2));memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
buf = malloc(sizeof(*buf));if (buf == NULL) {
h_errno = NETDB_INTERNAL;return NS_NOTFOUND;
}
buf2 = malloc(sizeof(*buf2));if (buf2 == NULL) {free(buf);
h_errno = NETDB_INTERNAL;return NS_NOTFOUND;
}switch (pai->ai_family) {case AF_UNSPEC:/* prefer IPv6 */
q.name = name;
q.qclass = C_IN;
q.answer = buf->buf;
q.anslen = sizeof(buf->buf);int query_ipv6 = 1, query_ipv4 = 1;if (pai->ai_flags & AI_ADDRCONFIG) {// Only implement AI_ADDRCONFIG if the application is not// using its own DNS servers, since our implementation// only works on the default connection.if (_using_default_dns(iface)) {
query_ipv6 = _have_ipv6();
query_ipv4 = _have_ipv4();
}
}if (query_ipv6) {
q.qtype = T_AAAA;if (query_ipv4) {
q.next = &q2;
q2.name = name;
q2.qclass = C_IN;
q2.qtype = T_A;
q2.answer = buf2->buf;
q2.anslen = sizeof(buf2->buf);
}
} else if (query_ipv4) {
q.qtype = T_A;
} else {free(buf);free(buf2);return NS_NOTFOUND;
}break;case AF_INET:
q.name = name;
q.qclass = C_IN;
q.qtype = T_A;
q.answer = buf->buf;
q.anslen = sizeof(buf->buf);break;case AF_INET6:
q.name = name;
q.qclass = C_IN;
q.qtype = T_AAAA;
q.answer = buf->buf;
q.anslen = sizeof(buf->buf);break;default:free(buf);free(buf2);return NS_UNAVAIL;
}
res = __res_get_state();if (res == NULL) {free(buf);free(buf2);return NS_NOTFOUND;
}/* this just sets our iface val in the thread private data so we don't have to
* modify the api's all the way down to res_send.c's res_nsend. We could
* fully populate the thread private data here, but if we get down there
* and have a cache hit that would be wasted, so we do the rest there on miss
*/
res_setiface(res, iface);
res_setmark(res, mark);if (res_searchN(name, &q, res) < 0) {
__res_put_state(res);free(buf);free(buf2);return NS_NOTFOUND;
}
ai = getanswer(buf, q.n, q.name, q.qtype, pai);if (ai) {
cur->ai_next = ai;while (cur && cur->ai_next)
cur = cur->ai_next;
}if (q.next) {
ai = getanswer(buf2, q2.n, q2.name, q2.qtype, pai);if (ai)
cur->ai_next = ai;
}free(buf);free(buf2);if (sentinel.ai_next == NULL) {
__res_put_state(res);switch (h_errno) {case HOST_NOT_FOUND:return NS_NOTFOUND;case TRY_AGAIN:return NS_TRYAGAIN;default:return NS_UNAVAIL;
}
}
_rfc6724_sort(&sentinel);
__res_put_state(res);
*((struct addrinfo **)rv) = sentinel.ai_next;return NS_SUCCESS;
}

而dns服务器的设置、获取,具体的流程,其他篇幅再分析。 至此整个网络请求到dns的流程已经出来了,也已经知道了之前的问题,hosts不会在应用进程解析(如果应用进程自己解析、读取除外),所以hook应用进程读取hosts是无效的。 总结以下,应用进程解析dns流程如下:

InetAddress.getByName ->
    getAllByNameImpl ->
        lookupHostByName ->
            Libcore.os.getaddrinfo -> //包装,调用natvie函数
                getaddrinfo -> //bionic\libc\netbsd\net\getaddrinfo.c
                    android_getaddrinfoforiface ->
                        android_getaddrinfo_proxy -> //这里cache_mode为空,netd设置的ANDROID_DNS_MODE环境变量只在进程中有效。
                            connect //这里的socket name是/dev/socket/dnsproxyd,也就是通过dnsproxd来和netd dameon进程交互。
                            fprintf //往dnsproxyd写getaddrinfo命令,接下来就交由netd进程处理。
远程系统进程netd:new DnsProxyListener -> //system/netd/main.cpp
dpl->startListener ->
    pthread_create ->
        SocketListener::threadStart ->
            me->runListener ->
                select
                accept
                onDataAvailable -> //FrameworkListener.cpp 客户端写消息到socket dnsproxyd中,dnsproxyd是在FrameworkListener中注册。
                    dispatchCommand ->
                        runCommand ->
                            DnsProxyListener::GetAddrInfoCmd::runCommand ->new DnsProxyListener::GetAddrInfoHandler
                                handler->start ->
                                    DnsProxyListener::GetAddrInfoHandler::start ->
                                        DnsProxyListener::GetAddrInfoHandler::threadStart -> //DnsProxyListener.cpp netd初始化后会启动dnsProxyListener线程监听/dev/socket/dnsproxd来的消息。
                                            handler->run ->
                                                DnsProxyListener::GetAddrInfoHandler::run ->
                                                    android_getaddrinfoforiface -> //这里不会跑android_getaddrinfo_proxy了,因为此时的ANDROID_DNS_MODE值是local了,所以直接获取dns地址。
                                                        explore_fqdn ->
                                                            nsdispatch ->
                                                                _files_getaddrinfo //从文件/system/etc/hosts获取
                                                                _dns_getaddrinfo //或者从dns服务器获取
                                                    sendLenAndData //返回给应用进程

其实在分析到进入dns的时候就怀疑自己过了,因为虽然可以在解析dns的时候返回代理服务器的ip和端口,但是并没有传入域名,怎么区分这次是http请求还是socket连接,像brup之类的http代理服务并不会代理socket。 最后剩一个问题,就是为什么一开始的代码异常了,192.168.xxx.xxx会被解析成localhost,localhost解析为127.0.0.1,所以无法代理。 而一个诡异的情况是安卓6.0上测试没问题(这个待会分析),wifi设置的代理也没问题。

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 {//设置了proxy
      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();//返回localhost
      socketPort = proxySocketAddress.getPort();
    }// Try each address for best behavior in mixed IPv4/IPv6 environments.//localhost解析为127.0.0.1
    socketAddresses = dns.getAllByName(socketHost);
    nextSocketAddressIndex = 0;
  }

经过比对代码,发现wifi代理生成Proxy时needResolved为false。

//Wi-Fi代理时系统设置needResolved为false,而上面代码是true
    InetSocketAddress(String hostname, int port, boolean needResolved) {if (hostname == null || port < 0 || port > 65535) {throw new IllegalArgumentException("host=" + hostname + ", port=" + port);
        }
        InetAddress addr = null;if (needResolved) {try {
                addr = InetAddress.getByName(hostname);
                hostname = null;
            } catch (UnknownHostException ignored) {
            }
        }this.addr = addr;this.hostname = hostname;this.port = port;
    }

所以我们生成时使其为false即可使用192.168的代理ip。 而6.0的系统不出错是因为调用的函数address.getHostAddress(); 返回的是ip地址。

private void resetNextInetSocketAddress(Proxy proxy) throws IOException {// Clear the addresses. Necessary if getAllByName() below throws!
    inetSocketAddresses = new ArrayList<>();
    String socketHost;int socketPort;if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
      socketHost = address.getUriHost();
      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 = getHostString(proxySocketAddress);
      socketPort = proxySocketAddress.getPort();
    }if (socketPort < 1 || socketPort > 65535) {throw new SocketException("No route to " + socketHost + ":" + socketPort
          + "; port is out of range");
    }// Try each address for best behavior in mixed IPv4/IPv6 environments.for (InetAddress inetAddress : network.resolveInetAddresses(socketHost)) {
      inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
    }
    nextInetSocketAddressIndex = 0;
  }static String getHostString(InetSocketAddress socketAddress) {
    InetAddress address = socketAddress.getAddress();if (address == null) {// The InetSocketAddress was specified with a string (either a numeric IP or a host name). If// it is a name, all IPs for that name should be tried. If it is an IP address, only that IP// address should be tried.return socketAddress.getHostName();
    }// The InetSocketAddress has a specific address: we should only try that address. Therefore we// return the address and ignore any host name that may be available.return address.getHostAddress();
  }

b419222ebd69149104e4cb31ac255a83.gif - End - 3b4a935f807847dd93398c3ed1bdd721.png

看雪ID:卓桐

https://bbs.pediy.com/user-670707.htm 

*本文由看雪论坛  卓桐  原创,转载请注明来自看雪社区

推荐文章++++

0ed5f81cb742419309fba2e525f359ec.png

* 通过捕获段错误实现的自定义linker

* Xposed高级用法 实现Tinker热修复

* 连连看逆向

* 从“短信劫持马”的制作来谈App安全

* 入门级加固——3种加固方式学习记录

进阶安全圈,不得不读的一本书 45523d1d3c0e10decd7c63312096da5f.png ﹀ ﹀ ﹀ a873794914cd142df670edba8c88a8fd.png 公众号ID:ikanxue 官方微博:看雪安全 商务合作:wsc@kanxue.com 67919d235b4f9eddb9261c53e80c95bf.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值