其实并不是要分析Dns流程的,一开始是分析Android设置wifi代理,怎么起到全局代理的作用的。
因为坏习惯,在线看源码,全凭记忆,没有做笔记的习惯,忽略了一个点,直接分析到dns了,回过头才发现分析过了。而之前群里有人问应用进程hook hosts文件能不能改dns,很久之前分析过4.1,记得是有个远程进程负责dns解析的,但是太久了,细节都忘光了。
所以分析完了wifi代理、dns后记录下吧,年纪大了记忆力真的不如以前了。
下一篇 wifi代理流程。
除了http外,ftp和webview是怎么代理的。
以及6.0以下不用root,不申请特殊权限,怎么设置wifi代理。由此引出app检测代理的必要,之前考虑实际的攻击场景可能就是钓鱼wifi,dns劫持等,所以认为一些app检测代理或者不走代理主要是应对渗透抓包,但是忽略了安卓6.0及以下版本恶意app可以设置wifi代理,把http(s)代理到服务器的,如果没有做正确的证书校验或者诱导用户安装证书,https也可以中间人。
以及除了root后监控网卡解析出http代理和使用VpnService外怎么通用的hook达到代理的目的,比如自己封装发送http协议,不使用标准api,第三方框架等、比如直接socket连接,写http。因为工作中还是有一部分这样不走寻常路的应用,往往web渗透的同事即没root机器,版本还很高(比如8.0的手机信任证书),甚至还在驻场的情况,对其进行支持就很头疼(多开框架)。
```
URL url = new URL("c34K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8b7`.`.");
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.2.1", 8081));
HttpURLConnection connection = (HttpURLConnection) url.openConnection(/*proxy*/);
connection.setConnectTimeout(10000);
// connection.addRequestProperty("accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
connection.addRequestProperty("accept-encoding", "gzip, deflate, br");
// connection.addRequestProperty("accept-language", "zh-CN,zh;q=0.9");
connection.setRequestMethod("GET");
InputStream is = connection.getInputStream();
Map<String, List<String>> map = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
Log.e("zhuo", entry.getKey()+"="+entry.getValue());
}
GZIPInputStream gis = new GZIPInputStream(is);
byte buf[] = new byte[1024];
gis.read(buf);
Log.e("zhuo", new String(buf));
connection.disconnect();
```
以上是一个简单到网络请求,而如果使用proxy的话会产生异常,我们借助异常堆栈可以较清晰的看到函数调用流程。(至于为什么产生这个异常,最后分析,而有经验的可能看下异常已经知道了)
```
W/System.err: java.net.ConnectException: failed to connect to localhost/127.0.0.1 (port 8081) after 10000ms: isConnected failed: ECONNREFUSED (Connection refused)
W/System.err: at libcore.io.IoBridge.isConnected(IoBridge.java:223)
at libcore.io.IoBridge.connectErrno(IoBridge.java:161)
at libcore.io.IoBridge.connect(IoBridge.java:112)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459)
at java.net.Socket.connect(Socket.java:843)
at com.android.okhttp.internal.Platform.connectSocket(Platform.java:131)
at com.android.okhttp.Connection.connect(Connection.java:101)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:179)
at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:246)
```
可以发现真正的网络请求是从getInputStream开始。但是为什么完全了解整个流程,我们还是简单看下Url类
```
transient URLStreamHandler streamHandler;
//仅是包装,而streamHandler的赋值是在构造函数
public URLConnection openConnection() throws IOException {
return streamHandler.openConnection(this);
}
public URL(String spec) throws MalformedURLException {
this((URL) null, spec, null);
}
//被上面的构造函数调用
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
if (spec == null) {
throw new MalformedURLException();
}
if (handler != null) {
streamHandler = handler;
}
spec = spec.trim();
protocol = UrlUtils.getSchemePrefix(spec);
int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;
// If the context URL has a different protocol, discard it because we can't use it.
if (protocol != null && context != null && !protocol.equals(context.protocol)) {
context = null;
}
// Inherit from the context URL if it exists.
if (context != null) {
set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(),
context.getUserInfo(), context.getPath(), context.getQuery(),
context.getRef());
if (streamHandler == null) {
streamHandler = context.streamHandler;
}
} else if (protocol == null) {
throw new MalformedURLException("Protocol not found: " + spec);
}
if (streamHandler == null) {
//赋值
setupStreamHandler();
if (streamHandler == null) {
throw new MalformedURLException("Unknown protocol: " + protocol);
}
}
// Parse the URL. If the handler throws any exception, throw MalformedURLException instead.
try {
streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
} catch (Exception e) {
throw new MalformedURLException(e.toString());
}
}
void setupStreamHandler() {
...
else if (protocol.equals("http")) {
try {
String name = "com.android.okhttp.HttpHandler";
streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
} else if (protocol.equals("https")) {
try {
String name = "com.android.okhttp.HttpsHandler";
streamHandler = (URLStreamHandler) Class.forName(name).newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
}
...
}
```
(因为基本上所有的https相关的类都是继承自http,覆盖某些方法,整体逻辑是类似的,为了少些跳转,我们直接看http相关的即可)
根据以上分析,发现最终实现类为com.android.okhttp.HttpsHandler,但是在源码里搜索并没有找到这个类。找到的有external/okhttp/android/main/java/com/squareup/okhttp/HttpHandler.java
看一下external/okhttp/jarjar-rules.txt
rule com.squareup.** com.android.@1
所以会在编译后改包名,所以就是我们要找的HttpHandler,而这个模块好像是早期的okhttp,集成进来改名应该是为了防止冲突,影响应用使用更新的okhttp。
```
public class HttpHandler extends URLStreamHandler {
@Override protected URLConnection openConnection(URL url) throws IOException {
return newOkHttpClient(null /* proxy */).open(url);
}
@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
if (url == null || proxy == null) {
throw new IllegalArgumentException("url == null || proxy == null");
}
return newOkHttpClient(proxy).open(url);
}
@Override protected int getDefaultPort() {
return 80;
}
protected OkHttpClient newOkHttpClient(Proxy proxy) {
OkHttpClient client = new OkHttpClient();
client.setFollowProtocolRedirects(false);
if (proxy != null) {
client.setProxy(proxy);
}
return client;
}
}
//可以看到确实如所说的http和https的关系,所以之后的分析,不在列出https专属部分
public final class HttpsHandler extends HttpHandler {
private static final List<String> ENABLED_TRANSPORTS = Arrays.asList("http/1.1");
@Override protected int getDefaultPort() {
return 443;
}
@Override
protected OkHttpClient newOkHttpClient(Proxy proxy) {
OkHttpClient client = super.newOkHttpClient(proxy);
client.setTransports(ENABLED_TRANSPORTS);
HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
// Assume that the internal verifier is better than the
// default verifier.
if (!(verifier instanceof DefaultHostnameVerifier)) {
client.setHostnameVerifier(verifier);
}
return client;
}
}
```
分析发现openConnection调用的是OkHttpClient的open函数。
```
//对应上了我们异常日志中的HttpsURLConnectionImpl,看名字也知道肯定是继承自HttpsURLConnection
HttpURLConnection open(URL url, Proxy proxy) {
String protocol = url.getProtocol();
OkHttpClient copy = copyWithDefaults();
copy.proxy = proxy;
if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
/**
* Returns a shallow copy of this OkHttpClient that uses the system-wide default for
* each field that hasn't been explicitly configured.
*/
private OkHttpClient copyWithDefaults() {
OkHttpClient result = new OkHttpClient(this);
result.proxy = proxy;
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
result.sslSocketFactory = sslSocketFactory != null
? sslSocketFactory
: HttpsURLConnection.getDefaultSSLSocketFactory();
result.hostnameVerifier = hostnameVerifier != null
? hostnameVerifier
: OkHostnameVerifier.INSTANCE;
result.authenticator = authenticator != null
? authenticator
: HttpAuthenticator.SYSTEM_DEFAULT;
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
result.followProtocolRedirects = followProtocolRedirects;
result.transports = transports != null ? transports : DEFAULT_TRANSPORTS;
result.connectTimeout = connectTimeout;
result.readTimeout = readTimeout;
return result;
}
```
看异常知道还是调用的HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:179),略过https
```
@Override public final InputStream getInputStream() throws IOException {
if (!doInput) {
throw new ProtocolException("This protocol does not support input");
}
//触发在这里
HttpEngine response = getResponse();
// if the requested file does not exist, throw an exception formerly the
// Error page from the server was returned if the requested file was
// text/html this has changed to return FileNotFoundException for all
// file types
if (getResponseCode() >= HTTP_BAD_REQUEST) {
throw new FileNotFoundException(url.toString());
}
InputStream result = response.getResponseBody();
if (result == null) {
throw new ProtocolException("No response body exists; responseCode=" + getResponseCode());
}
return result;
}
private HttpEngine getResponse() throws IOException {
//HttpEngine赋值
initHttpEngine();
if (httpEngine.hasResponse()) {
return httpEngine;
}
while (true) {
if (!execute(true)) {
continue;
}
...
}
private boolean execute(boolean readResponse) throws IOException {
try {
httpEngine.sendRequest();
if (readResponse) {
httpEngine.readResponse();
}
return true;
} catch (IOException e) {
if (handleFailure(e)) {
return false;
} else {
throw e;
}
}
}
```
最终调用HttpEngine类的sendRequest
```
public final void sendRequest() throws IOException {
...
//略过设置请求头,缓存等
if (responseSource.requiresConnection()) {
sendSocketRequest();
} else if (connection != null) {
client.getConnectionPool().recycle(connection);
connection = null;
}
}
private void sendSocketRequest() throws IOException {
if (connection == null) {
//连接
connect();
}
if (transport != null) {
throw new IllegalStateException();
}
transport = (Transport) connection.newTransport(this);
if (hasRequestBody() && requestBodyOut == null) {
// Create a request body if we don't have one already. We'll already
// have one if we're retrying a failed POST.
requestBodyOut = transport.createRequestBody();
}
}
/** Connect to the origin server either directly or via a proxy. */
protected final void connect() throws IOException {
if (connection != null) {
return;
}
if (routeSelector == null) {
String uriHost = uri.getHost();
if (uriHost == null) {
throw new UnknownHostException(uri.toString());
}
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
if (uri.getScheme().equalsIgnoreCase("https")) {
sslSocketFactory = client.getSslSocketFactory();
hostnameVerifier = client.getHostnameVerifier();
}
//这一部分待会回来再分析
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports());
routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
}
//待会分析
connection = routeSelector.next(method);
if (!connection.isConnected()) {
//最终调用的是connection.connect
connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
client.getConnectionPool().maybeShare(connection);
client.getRoutesDatabase().connected(connection.getRoute());
} else {
connection.updateReadTimeout(client.getReadTimeout());
}
connected(connection);
if (connection.getRoute().getProxy() != client.getProxy()) {
// Update the request line if the proxy changed; it may need a host name.
requestHeaders.getHeaders().setRequestLine(getRequestLine());
}
}
```
```
public Connection(Route route) {
this.route = route;
}
public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
throws IOException {
if (connected) {
throw new IllegalStateException("already connected");
}
connected = true;
//这里是直接new Socket()
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
//后面的不用再追,因为在这里传入route.inetSocketAddress已经是127.0.0.1(因为我们自定义了proxy,如果没有使用,则域名已经变为ip),之后我们往回追,因为后面的就是socket建立连接,和本篇关系不大了。
Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
socket.setSoTimeout(readTimeout);
in = socket.getInputStream();
out = socket.getOutputStream();
if (route.address.sslSocketFactory != null) {
upgradeToTls(tunnelRequest);
}
// Use MTU-sized buffers to send fewer packets.
int mtu = Platform.get().getMtu(socket);
if (mtu < 1024) mtu = 1024;
if (mtu > 8192) mtu = 8192;
in = new BufferedInputStream(in, mtu);
out = new BufferedOutputStream(out, mtu);
}
```
至此顺着异常日志大概知道了网络请求的流程。
## Dns流程
按照倒叙的方式,我们看下
```
connection = routeSelector.next(method);
public Connection next(String method) throws IOException {
// Always prefer pooled connections over new connections.
for (Connection pooled; (pooled = pool.get(address)) != null; ) {
if (method.equals("GET") || pooled.isReadable()) return pooled;
pooled.close();
}
// Compute the next route to attempt.
if (!hasNextTlsMode()) {
if (!hasNextInetSocketAddress()) {
if (!hasNextProxy()) {
if (!hasNextPostponed()) {
throw new NoSuchElementException();
}
return new Connection(nextPostponed());
}
lastProxy = nextProxy();
resetNextInetSocketAddress(lastProxy);
}
lastInetSocketAddress = nextInetSocketAddress();
resetNextTlsMode();
}
boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
//根据前面的分析route.inetSocketAddress=lastInetSocketAddress
Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
if (routeDatabase.shouldPostpone(route)) {
postponedRoutes.add(route);
// We will only recurse in order to skip previously failed routes. They will be
// tried last.
return next(method);
}
return new Connection(route);
}
```
根据前面的route.inetSocketAddress,我们可以简单看下Route这个类,
```
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
boolean modernTls) {
if (address == null) throw new NullPointerException("address == null");
if (proxy == null) throw new NullPointerException("proxy == null");
if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
this.address = address;
this.proxy = proxy;
this.inetSocketAddress = inetSocketAddress;
this.modernTls = modernTls;
}
```
因为inetSocketAddress不能为空,所以lastInetSocketAddress肯定就是后面用到的route.inetSocketAddress,而lastInetSocketAddress由nextInetSocketAddress函数赋值。
```
private InetSocketAddress nextInetSocketAddress() throws UnknownHostException {
InetSocketAddress result =
new InetSocketAddress(socketAddresses[nextSocketAddressIndex++], socketPort);
if (nextSocketAddressIndex == socketAddresses.length) {
socketAddresses = null; // So that hasNextInetSocketAddress() returns false.
nextSocketAddressIndex = 0;
}
return result;
}
```
返回的是private InetAddress[] socketAddresses数组内的一个值,而对该数组赋值的函数只有一个,可以直接定位到真是幸福。
```
private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
String socketHost;
if (proxy.type() == Proxy.Type.DIRECT) {
socketHost = uri.getHost();
socketPort = getEffectivePort(uri);
} else {
SocketAddress proxyAddress = proxy.address();
if (!(proxyAddress instanceof InetSocketAddress)) {
throw new IllegalArgumentException(
"Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
}
InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
socketHost = proxySocketAddress.getHostName();
socketPort = proxySocketAddress.getPort();
}
// Try each address for best behavior in mixed IPv4/IPv6 environments.
socketAddresses = dns.getAllByName(socketHost);
nextSocketAddressIndex = 0;
}
```
dns.getAllByName函数传入的是域名"ab1K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6h3u0S2K9h3c8#2i4K6u0W2j5$3!0E0",返回的域名对应的ip数组。
dns在上面的代码中设置过,为Dns.DEFAULT
```
routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
public interface Dns {
Dns DEFAULT = new Dns() {
@Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
return InetAddress.getAllByName(host);
}
};
InetAddress[] getAllByName(String host) throws UnknownHostException;
}
```
Dns为接口只有一个默认的实现,调用InetAddress类的getAllByName
```
private static InetAddress[] lookupHostByName(String host) throws UnknownHostException {
...
//略过查找缓存,第一次没有缓存
StructAddrinfo hints = new StructAddrinfo();
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = AF_UNSPEC;
// If we don't specify a socket type, every address will appear twice, once
// for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
// anyway, just pick one.
hints.ai_socktype = SOCK_STREAM;
//入口
InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints);
// TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
for (InetAddress address : addresses) {
address.hostName = host;
}
addressCache.put(host, addresses);
}
```
最终获取dns又由Libcore.os.getaddrinfo实现,而这个Libcore.os,追过的朋友应该知道有两三层包装,这里就不再浪费时间去追,直接看函数对应的native函数。
```
NATIVE_METHOD(Posix, getaddrinfo, "(Ljava/lang/String;Llibcore/io/StructAddrinfo;)[Ljava/net/InetAddress;"),
对应的c++实现为Posix_getaddrinfo
static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jobject javaHints) {
...
//略过一些无关紧要的代码
addrinfo* addressList = NULL;
errno = 0;
int rc = getaddrinfo(node.c_str(), NULL, &hints, &addressList);
UniquePtr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);
if (rc != 0) {
throwGaiException(env, "getaddrinfo", rc);
return NULL;
}
...
//result为addressList->ai_addr
return result;
}
```
代码有些多,省略掉,主要就是getaddrinfo函数,之后返回的类数组赋值取的是addressList中的值,所以我们关注的是getaddrinfo函数的第四个参数。实现在libc中,bionic/libc/netbsd/net/getaddrinfo.c
```
int
getaddrinfo(const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res)
{
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-6-21 19:34
被卓桐编辑
,原因: