編輯:關於Android編程
需求:Android 4.4 + okhttp 3.2;非root,在應用層,拿到DNS維度底層數據
方案:jni + hook libc.so中DNS關鍵getaddrinfo
分析:
1.人為制造DNS異常,拋出調用鏈路:
即:
java.net.InetAddress.lookupHostByName(InetAddress.java:424) java.net.InetAddress.getAllByNameImpl(InetAddress.java:236) java.net.InetAddress.getAllByName(InetAddress.java:214) okhttp3.Dns$1.lookup(Dns.java:39) okhttp3.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:173) okhttp3.internal.http.RouteSelector.nextProxy(RouteSelector.java:139) okhttp3.internal.http.RouteSelector.next(RouteSelector.java:81) okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:174) okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:127) okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:97) okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:289) okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:241) okhttp3.RealCall.getResponse(RealCall.java:240) okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:198) com.baidu.uaq.agent.android.instrumentation.okhttp3util.OkHttp3Interceptor.intercept(OkHttp3Interceptor.java:52) okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:187) okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160) okhttp3.RealCall.execute(RealCall.java:57)
2.okhttp3.Dns$1.lookup(Dns.java:39)
Dns SYSTEM = new Dns() { @Override public Listlookup(String hostname) throws UnknownHostException { if (hostname == null) throw new UnknownHostException("hostname == null"); return Arrays.asList(InetAddress.getAllByName(hostname)); } };
3.
java.net.InetAddress.getAllByName(InetAddress.java:214)
java.net.InetAddress.getAllByNameImpl(InetAddress.java:236)
java.net.InetAddress.lookupHostByName(InetAddress.java:424)
public static InetAddress[] getAllByName(String host) throws UnknownHostException { return getAllByNameImpl(host).clone(); } /** * Returns the InetAddresses for {@code host}. The returned array is shared * and must be cloned before it is returned to application code. */ private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException { if (host == null || host.isEmpty()) { return loopbackAddresses(); } // Is it a numeric address? InetAddress result = parseNumericAddressNoThrow(host); if (result != null) { result = disallowDeprecatedFormats(host, result); if (result == null) { throw new UnknownHostException("Deprecated IPv4 address format: " + host); } return new InetAddress[] { result }; } return lookupHostByName(host).clone(); } private static InetAddress parseNumericAddressNoThrow(String address) { // Accept IPv6 addresses (only) in square brackets for compatibility. if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) { address = address.substring(1, address.length() - 1); } StructAddrinfo hints = new StructAddrinfo(); hints.ai_flags = AI_NUMERICHOST; InetAddress[] addresses = null; try { addresses = Libcore.os.getaddrinfo(address, hints); } catch (GaiException ignored) { } return (addresses != null) ? addresses[0] : null; } private static InetAddress[] lookupHostByName(String host) throws UnknownHostException { BlockGuard.getThreadPolicy().onNetwork(); // Do we have a result cached? Object cachedResult = addressCache.get(host); if (cachedResult != null) { if (cachedResult instanceof InetAddress[]) { // A cached positive result. return (InetAddress[]) cachedResult; } else { // A cached negative result. throw new UnknownHostException((String) cachedResult); } } try { 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); return addresses; } catch (GaiException gaiException) { // If the failure appears to have been a lack of INTERNET permission, throw a clear // SecurityException to aid in debugging this common mistake. // http://code.google.com/p/android/issues/detail?id=15722 if (gaiException.getCause() instanceof ErrnoException) { if (((ErrnoException) gaiException.getCause()).errno == EACCES) { throw new SecurityException("Permission denied (missing INTERNET permission?)", gaiException); } } // Otherwise, throw an UnknownHostException. String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error); addressCache.putUnknownHost(host, detailMessage); throw gaiException.rethrowAsUnknownHostException(detailMessage); } }
對於應用層
OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(ipaddr) .build(); Response response = client.newCall(request).execute();
會調用Libcore.os.getaddrinfo(host, hints)兩次
第一次:
hints.ai_flags = AI_NUMERICHOST;
第二次:
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
所以:在c層,可以用ai_flags和ai_socktype來區別這兩個,從而過濾出真正的DNS請求
5./libcore/luni/src/main/java/libcore/io/ForwardingOs.java
public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException { return os.getaddrinfo(node, hints); }
public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException;
static jobjectArray Posix_getaddrinfo(JNIEnv* env, jobject, jstring javaNode, jobject javaHints) { ScopedUtfChars node(env, javaNode); if (node.c_str() == NULL) { return NULL; } static jfieldID flagsFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_flags", "I"); static jfieldID familyFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_family", "I"); static jfieldID socktypeFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_socktype", "I"); static jfieldID protocolFid = env->GetFieldID(JniConstants::structAddrinfoClass, "ai_protocol", "I"); addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_flags = env->GetIntField(javaHints, flagsFid); hints.ai_family = env->GetIntField(javaHints, familyFid); hints.ai_socktype = env->GetIntField(javaHints, socktypeFid); hints.ai_protocol = env->GetIntField(javaHints, protocolFid); 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; } // Count results so we know how to size the output array. int addressCount = 0; for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) { if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) { ++addressCount; } else { ALOGE("getaddrinfo unexpected ai_family %i", ai->ai_family); } } if (addressCount == 0) { return NULL; } // Prepare output array. jobjectArray result = env->NewObjectArray(addressCount, JniConstants::inetAddressClass, NULL); if (result == NULL) { return NULL; } // Examine returned addresses one by one, save them in the output array. int index = 0; for (addrinfo* ai = addressList; ai != NULL; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) { // Unknown address family. Skip this address. ALOGE("getaddrinfo unexpected ai_family %i", ai->ai_family); continue; } // Convert each IP address into a Java byte array. sockaddr_storage& address = *reinterpret_cast(ai->ai_addr); ScopedLocalRef inetAddress(env, sockaddrToInetAddress(env, address, NULL)); if (inetAddress.get() == NULL) { return NULL; } env->SetObjectArrayElement(result, index, inetAddress.get()); ++index; } return result; }
8.int rc = getaddrinfo(node.c_str(),NULL, &hints, &addressList); 調用BIONIC的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) { struct addrinfo sentinel; struct addrinfo *cur; int error = 0; struct addrinfo ai; struct addrinfo ai0; struct addrinfo *pai; const struct explore *ex; const char* cache_mode = getenv("ANDROID_DNS_MODE"); /* hostname is allowed to be NULL */ /* servname is allowed to be NULL */ /* hints is allowed to be NULL */ assert(res != NULL); memset(&sentinel, 0, sizeof(sentinel)); cur = &sentinel; pai = &ai; pai->ai_flags = 0; pai->ai_family = PF_UNSPEC; pai->ai_socktype = ANY; pai->ai_protocol = ANY; pai->ai_addrlen = 0; pai->ai_canonname = NULL; pai->ai_addr = NULL; pai->ai_next = NULL; if (hostname == NULL && servname == NULL) return EAI_NONAME; if (hints) { /* error check for hints */ if (hints->ai_addrlen || hints->ai_canonname || hints->ai_addr || hints->ai_next) ERR(EAI_BADHINTS); /* xxx */ if (hints->ai_flags & ~AI_MASK) ERR(EAI_BADFLAGS); switch (hints->ai_family) { case PF_UNSPEC: case PF_INET: #ifdef INET6 case PF_INET6: #endif break; default: ERR(EAI_FAMILY); } memcpy(pai, hints, sizeof(*pai)); /* * if both socktype/protocol are specified, check if they * are meaningful combination. */ if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) { for (ex = explore; ex->e_af >= 0; ex++) { if (pai->ai_family != ex->e_af) continue; if (ex->e_socktype == ANY) continue; if (ex->e_protocol == ANY) continue; if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) { ERR(EAI_BADHINTS); } } } } /* * check for special cases. (1) numeric servname is disallowed if * socktype/protocol are left unspecified. (2) servname is disallowed * for raw and other inet{,6} sockets. */ if (MATCH_FAMILY(pai->ai_family, PF_INET, 1) #ifdef PF_INET6 || MATCH_FAMILY(pai->ai_family, PF_INET6, 1) #endif ) { ai0 = *pai; /* backup *pai */ if (pai->ai_family == PF_UNSPEC) { #ifdef PF_INET6 pai->ai_family = PF_INET6; #else pai->ai_family = PF_INET; #endif } error = get_portmatch(pai, servname); if (error) ERR(error); *pai = ai0; } ai0 = *pai; /* NULL hostname, or numeric hostname */ for (ex = explore; ex->e_af >= 0; ex++) { *pai = ai0; /* PF_UNSPEC entries are prepared for DNS queries only */ if (ex->e_af == PF_UNSPEC) continue; if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex))) 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_family == PF_UNSPEC) pai->ai_family = ex->e_af; 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; if (hostname == NULL) error = explore_null(pai, servname, &cur->ai_next); else error = explore_numeric_scope(pai, hostname, servname, &cur->ai_next); if (error) goto free; while (cur->ai_next) cur = cur->ai_next; } /* * XXX * If numeric representation of AF1 can be interpreted as FQDN * representation of AF2, we need to think again about the code below. */ if (sentinel.ai_next) goto good; if (hostname == NULL) ERR(EAI_NODATA); if (pai->ai_flags & AI_NUMERICHOST) ERR(EAI_NONAME); /* * BEGIN ANDROID CHANGES; proxying to the cache */ 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; 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; }
總結:
hook libc.so中的getaddrinfo就能拿到DNS維度的數據,包括DNS時間、域名解析到哪幾個ip,是否命中緩存、解析錯誤信息等。
最近在學Android 學的不好 然後看到了用.9.png寫對話框的哪裡,但是書上寫的太簡單 感覺做出來和書上的不一樣 然後就去各種百度 感覺網上關於這個東西的資料都是粘
應用實現密碼登陸,記事本內容可增刪改除等操作,用listview顯示每次保存的記事內容,實現了記事本的基本功能。代碼都有詳細注解。1、代碼的目錄密碼登陸使用的是share
使用MediaRecorder錄制音頻步驟: 創建MediaRecorder對象MediaRecorder recorder = new MediaRecorder()
模版方法模式(Template Method):模版方法模式是類的行為模式。提供一個抽象類,把一部分的邏輯以具體方法或構造子形式實現,然後聲明一些抽象方法,迫使子類實現剩