本文为看雪论坛精华文章
看雪论坛作者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. java246
)
可以发现真正的网络请求是从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; }
看异 常知道还是调用的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 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函数传入的是域名"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_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) { 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 them return 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); //读取每一行解析域名和ip while ((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(); }
看雪ID: 卓桐
https://bbs.pediy.com/user-670707.htm
*本文由看雪论坛 卓桐 原创,转载请注明来自看雪社区