Android 4.4 從url.openConnection到DNS解析

2021-02-23 看雪學院
其實並不是要分析Dns流程的,一開始是分析Android設置wifi代理,怎麼起到全局代理的作用的。因為壞習慣,在線看源碼,全憑記憶,沒有做筆記的習慣,忽略了一個點,直接分析到dns了,回過頭才發現分析過了。而之前群裡有人問應用進程hook hosts文件能不能改dns,很久之前分析過4.1,記得是有個遠程進程負責dns解析的,但是太久了,細節都忘光了。所以分析完了wifi代理、dns後記錄下吧,年紀大了記憶力真的不如以前了。除了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();
connection.setConnectTimeout(10000);

connection.addRequestProperty("accept-encoding", "gzip, deflate, br");

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;
    
    
    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 (protocol != null && context != null && !protocol.equals(context.protocol)) {
            context = null;
        }
        
        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);
            }
        }
        
        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.txtrule com.squareup.** com.android.@1所以會在編譯後改包名,所以就是我們要找的HttpHandler,而這個模塊好像是早期的okhttp,集成進來改名應該是為了防止衝突,影響應用使用更新的okhttp。

public class HttpHandler extends URLStreamHandler {
    @Override protected URLConnection openConnection(URL url) throws IOException {
        return newOkHttpClient(null ).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;
    }
}

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();
        
        
        if (!(verifier instanceof DefaultHostnameVerifier)) {
            client.setHostnameVerifier(verifier);
        }
        return client;
    }
}

分析發現openConnection調用的是OkHttpClient的open函數。


  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);
  }
  
  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 (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 {
    
    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) {
      
      
      requestBodyOut = transport.createRequestBody();
    }
  }
    
  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(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()) {
      
      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;
    
    socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
    
    Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
    socket.setSoTimeout(readTimeout);
    in = socket.getInputStream();
    out = socket.getOutputStream();
    if (route.address.sslSocketFactory != null) {
      upgradeToTls(tunnelRequest);
    }
    
    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);
  }

connection = routeSelector.next(method);
    public Connection next(String method) throws IOException {
    
    for (Connection pooled; (pooled = pool.get(address)) != null; ) {
      if (method.equals("GET") || pooled.isReadable()) return pooled;
      pooled.close();
    }
    
    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 route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
    if (routeDatabase.shouldPostpone(route)) {
      postponedRoutes.add(route);
      
      
      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;
      nextSocketAddressIndex = 0;
    }
    return result;
  }

返回的是private InetAddress[] socketAddresses數組內的一個值,而對該數組賦值的函數只有一個,可以直接定位到真是幸福。

private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
    socketAddresses = null;
    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();
    }
    
    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;
        
        
        
        hints.ai_socktype = SOCK_STREAM;
        
        InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
        
        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;
    }
    ...
    
    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)
{
        .
    
    const char* cache_mode = getenv("ANDROID_DNS_MODE");
    .
    
    if (cache_mode == NULL || strcmp(cache_mode, "local") != 0) {
        
        return android_getaddrinfo_proxy(hostname, servname, hints, res, iface);
    }
    

for (ex = explore; ex->e_af >= 0; ex++) {
*pai = ai0;

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;
        
error = explore_fqdn(pai, hostname, servname,
&cur->ai_next, iface, mark);
while (cur && cur->ai_next)
cur = cur->ai_next;
}

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;
    
    
static const ns_dtab dtab[] = {
NS_FILES_CB(_files_getaddrinfo, NULL)
{ NSSRC_DNS, _dns_getaddrinfo, NULL },
NS_NIS_CB(_yp_getaddrinfo, NULL)
{ 0, 0, 0 }
};
assert(pai != NULL);


assert(res != NULL);
result = NULL;

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);

}
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

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;


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;
return (result ? result : NS_NOTFOUND);
}

到這裡出現了兩個分支,一個是讀取本地的hosts,一個是訪問dns,先看本地的。


#define NSSRC_FILES "files"
{ NSSRC_FILES, F, __UNCONST(C) },

static const ns_src default_dns_files[] = {
{ NSSRC_FILES, NS_SUCCESS },
{ NSSRC_DNS, NS_SUCCESS },
{ 0, 0 }
};

#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 *);

memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
    
_sethtent(&hostf);
    
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);

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:

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) {



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;
}

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是無效的。

InetAddress.getByName ->
    getAllByNameImpl ->
        lookupHostByName ->
            Libcore.os.getaddrinfo ->
                getaddrinfo ->
                    android_getaddrinfoforiface ->
                        android_getaddrinfo_proxy ->
                            connect
                            fprintf
遠程系統進程netd:
new DnsProxyListener ->
dpl->startListener ->
    pthread_create ->
        SocketListener::threadStart ->
            me->runListener ->
                select
                accept
                onDataAvailable ->
                    dispatchCommand ->
                        runCommand ->
                            DnsProxyListener::GetAddrInfoCmd::runCommand ->
                                new DnsProxyListener::GetAddrInfoHandler
                                handler->start ->
                                    DnsProxyListener::GetAddrInfoHandler::start ->
                                        DnsProxyListener::GetAddrInfoHandler::threadStart ->
                                            handler->run ->
                                                DnsProxyListener::GetAddrInfoHandler::run ->
                                                    android_getaddrinfoforiface ->
                                                        explore_fqdn ->
                                                            nsdispatch ->
                                                                _files_getaddrinfo
                                                                _dns_getaddrinfo
                                                    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;
    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();
    }
    
    
    socketAddresses = dns.getAllByName(socketHost);
    nextSocketAddressIndex = 0;
  }

經過比對代碼,發現wifi代理生成Proxy時needResolved為false。


    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();

private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
    
    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");
    }
    
    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) {
      
      
      
      return socketAddress.getHostName();
    }
    
    
    return address.getHostAddress();
  }


相關焦點

  • OkHttp3源碼解析(整體流程)
    上面講到了OkHttpClient的兩種構造方式, 通過查看源碼,兩種方式的配置是相同的,下面具體看一下到底配置了什麼:public Builder() { dispatcher = new Dispatcher(); protocols = DEFAULT_PROTOCOLS; connec
  • 國內外免費DNS解析總結
    4、DnsExit是一個提供免費動態DNS解析和域名DNS解析服務的公司,DnsExit的Dns解析支持MX、A、CNAME、SRV、TXT等記錄。要使用DnsExit的免費DNS解析服務,需要首先註冊DnsExit帳戶,在登陸之後點擊「Manage Domain」開通免費dns服務,之後才可以添加域名。然後再點「Manage Domain」就可以看到自己添加的域名了。
  • 網際網路初識系列3:DNS解析
    解析概述1、DNS解析結構DNS系統一般採用樹狀結構進行組織,以i.baidu.com為例,com為頂級域名,baidu為二級域名,i為三級域名>②瀏覽器將接收到的url中抽取出域名欄位,就是訪問的主機名, 並將這個主機名傳送給DNS應用的客戶端③DNS客戶機端向DNS伺服器端發送一份查詢報文,報文中包含著要訪問的主機名欄位(中間包括一些列緩存查詢以及分布式DNS集群的工作)④該DNS客戶機最終會收到一份回答報文,其中包含有該主機名對應的IP位址
  • Linux DNS 查詢剖析(第一部分) | Linux 中國
    |O_CLOEXEC) = 3open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4open("/etc/nsswitch.conf",
  • 誰動了站長的奶酪 Dns.la專治網絡「蝸牛病」
    據調查發現,七月份很多DNS提供商被攻擊的直接原因就是因為利益而默許了大量的私服、棋牌類網站接入,這無疑是給安裝了個定時炸彈,隨時都有可能被引爆,為此DNSLA自主研發了域名安全審核機制,杜絕一切非法網站的接入,給用戶提供一個乾淨穩定的DNS解析平臺。
  • dns是什麼意思 - 百度經驗
    下面和大家分享dns是什麼;如果你覺得這個分享對你有幫助,請將之分享到朋友圈或微博中,讓更多小夥伴知多點。dns是domain name service的縮寫,它的作用簡單的說,可以理解為:將域名翻譯成ip地址。
  • 詳解DNS 與 CoreDNS 的實現原理
    這時就需要通過一個 DNS 解析器負責域名的解析,下面的圖片展示了 DNS 查詢的執行過程:dns-resolution本地的 DNS 客戶端向 DNS 解析器發出解析 draveness.me 域名的請求;
  • 如何用 JavaScript 來解析 URL
    https://dmitripavlutin.com/parse-url-JavaScript很多時候你需要獲取到一段 URL 的某個組成部分。它們可能是 hostname(例如 dmitripavlutin.com),或者 pathname(例如 /parse-url-JavaScript)。
  • 國內5大免費智能DNS解析
    國外的域名註冊公司大多支持無限解析,而咱們國內呢?萬網,僅僅給出的10條免費域名解析記錄(國內域名註冊公司給的最少的),新網20條。萬網域名解析生效時間巨慢,現在講的是高效、快捷,官方所謂的「24~72小時解析生效的為正常現象」,這完全是在放屁,真會忽悠人!這同時也說明,國內創新意識薄弱,大多都是樂不思蜀、安於現狀,國內的技術實力明顯不足以與國外抗衡。
  • Android WebView 與 JS 的交互方式最全面匯總
    方法對比使用建議兩種方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2Android通過 WebViewClient 的回調方法shouldOverrideUrlLoading ()攔截 url解析該 url 的協議如果檢測到是預先約定好的協議,就調用相應方法即JS需要調用Android的方法JS代碼:javascript.html以.html格式放到src/main
  • 如何在JavaScript中解析URL
    在 JavaScript 中解析 URL 一種快捷的方式是使用 URL API。目前已經在現代瀏覽器中得到了廣泛支持。在本文中我們將介紹如何使用 URL API 來解析一個 URL 以及它的組件。URL() 構造函數使用 URL() 構造函數可以解析一個 URL 的組件。url 參數可以是絕對路徑也可以是相對路徑,如果第一個參數是相對路徑,則第二個參數 base 是必需的,需要填寫一個絕對的路徑作為基礎 URL。
  • Android 代碼規範文檔
    不推薦用 layout_marginLeft,而應該用 layout_marginStart;不推薦用 layout_marginRight,而應該用 layout_marginEnd,原因有兩個,一個是適配 Android 4.4 反方向特性(可在開發者選項中開啟),第二個是 XML 布局中使用 layout_marginLeft 和 layout_marginRight 會有代碼警告
  • 絕地求生connection timeout 3.6.7解決方法/遊戲連接超時怎麼辦
    絕地求生遊戲中有很多的玩家遇到了connection timeout 3.6.7,這是怎麼回事呢?下面遊戲吧小編為大家帶來絕地求生connection timeout 3.6.7解決方法,感興趣的小夥伴們快來一起了解一下吧!
  • 基於PJSIP協議棧和Android的VoIP系統設計方案介紹【詳解】
    1.2 總體設計  本方案基本上符合Android的NDK框架的開發規範,將系統分為4層,如圖1所示。  2.1.1 PJSIP協議棧  PJSIP協議棧遵循標準的SIP協議,採用分層架構:SIP/SDP消息編碼解析層、傳輸管理層、SIP終端、事務層、會話層以及應用層等。由於SIP協議採用文本消息發送請求和響應,所以首先需要將SIP消息按照巴斯克範式(ABNF)編碼和解析,這就是SIP/SDP消息編碼解析層所完成的功能。
  • Windows10系統提示DNS解析失敗解決方法介紹
    windows10系統在使用的時候經常會出現各種各樣的問題,就有的用戶在進行上網的時候系統提示【DNS解析失敗】,從而導致電腦沒有辦法上網,那麼出現這種情況我們該怎麼處理呢?下面就給大家介紹下win10電腦提示DNS解析失敗的解決方法。
  • HGAME-Week4-Web writeup
    4444/tcp filtered krb524 6667/tcp filtered irc 9876/tcp open sd 31337/tcp open Elite然後針對開放埠繼續進行掃描。通過分析,可以得出,他首先對url進行解析,獲取ip,判斷ip是否在黑名單中。之後進行對url訪問。此時我們可以通過DNS rebinding來進行ssrf。簡單來說就是,讓他在判斷ip的時候,將域名解析為正常ip,然後訪問時,將ip解析為127.0.0.1。
  • 在Linux上安裝Android 4.4 KitKat
    Android (x86)項目致力於移植Android系統到X86處理器上,使用戶可以更容易的在任何電腦上安裝Android。他們通過使用android源碼,增加補丁來使Android能夠在X86處理器,筆記本電腦和平板電腦下工作。
  • Camera 入門第一篇 openCamera
    (frameworks\base\core\java\android\hardware\camera2)的 openCamera 方法,根據指定的CameraId 打開。 //調用異步 Camera 助手 打開Camera ,調用 1.3 openCameraDeviceUserAsync openCameraDeviceUserAsync(cameraId, callback, executor, clientUid); }1.3 CameraManager.openCameraDeviceUserAsync
  • Android系統編譯指南
    remotes/origin/secure_linux_android_development remotes/origin/streamlined_code_engineeringTest@Test:/Test/Qualcomm_p/E5527M_MSM8917_QM215_r26/LA.UM.7.6.2/LINUX/android$二、切換到目標分支