What is HttpDns and why do YOU want to access HttpDns

HTTPDNS uses HTTP to resolve domain names instead of the existing UDP-based DNS protocol. Domain name resolution requests are directly sent to the HTTPDNS server of Aliyun, bypassing carriers’ Local DNS and avoiding domain name hijacking and inaccurate scheduling problems caused by Local DNS.

HttpDns best practices?

Ali Cloud documentation, mentioned HttpDns best practices several scenarios, although to a certain extent can solve our problems, but there are certain shortcomings,

  • Webview has a limited number of requests that can be made using HTTPDNS
  • Global substitution cannot be done

So can we find a global alternative? Or is the solution scenario a bit more of a solution?

Look for a plan from the available data

After some searching, I found Android’s article here a global interception and monitoring DNS way and here article how to provide global HttpDNS service for Android applications. In the scenario mentioned in the first article, the flaws are obvious

  1. Only versions later than 7.0 are supported
  2. Webview is not supported. Although the author says that webView is supported, it is actually not supported. Why not

In the first article, the solution was the same. There is no corresponding code for Hook Coonnect under 7.0. It is too difficult for us.

Therefore, the existing scheme is not suitable.

Seek breakthrough method from source code – dynamic proxy Os interface

Let’s take a look at what Dns resolution looks like, using API 25’s SDK as an example, and find the following code snippet.

            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.android_getaddrinfo(host, hints, netId);Copy the code

So, let’s keep tracing the source code. Let’s see what libcore. OS is.

public final class Libcore {
    private Libcore() { }
    public static Os os = new BlockGuardOs(new Posix());
}
Copy the code

Os is an interface, as shown in the following code snippet.


public interface Os {
    public FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException;
    public boolean access(String path, int mode) throws ErrnoException;
    public InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException;
Copy the code

The Posix code snippet is shown below

public final class Posix implements Os {
    Posix() { }
    public native FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException;
    public native boolean access(String path, int mode) throws ErrnoException;
    public native InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException;
Copy the code

Libcore.os.android_getaddrinfo is actually a call to the Posix android_getaddrinfo native method.

At this point, we can make it clear that we can solve part of the problem (in all scenarios except webView) by dynamically proxy the Os interface and replacing the libcore. Os field by inserting HttpDns resolution in the call to android_getaddrinfo. Of course, there are adaptation issues to consider. On version 4.4, android_getaddrInfo is getaddrInfo. Therefore, we write the following code.


    public static void globalReplaceByHookOs() {
        if (mHooked) {
            return;
        }
        mHooked = true;
        try {
            Class libcoreClz = Class.forName("libcore.io.Libcore");
            Field osField = libcoreClz.getField("os");
            Object origin = osField.get(null);
            Object proxy = Proxy.newProxyInstance(libcoreClz.getClassLoader(),
                    new Class[]{Class.forName("libcore.io.Os")},
                    new OsInvokeHandler(origin));
            osField.set(null, proxy);
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("xhook"."globalReplaceByHookOs: " + e.getMessage());
        }
    }


public class OsInvokeHandler implements InvocationHandler {

    private Object mOrigin;
    private Field mAiFlagsField;

    OsInvokeHandler(Object os) {
        mOrigin = os;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("android_getaddrinfo")
                || method.getName().equals("getaddrinfo")) {
            try {
                if (mAiFlagsField == null) {
                    mAiFlagsField = args[1].getClass().getDeclaredField("ai_flags");
                    mAiFlagsField.setAccessible(true);
                }
            }catch (Exception e) {
                e.printStackTrace();
                Log.e("xhook"."ai_flag get error ");
            }

            if(args[0] instanceof String && mAiFlagsField ! = null && ((int) mAiFlagsField.get(args[1]) ! = OsConstants.AI_NUMERICHOST)) {// It is important to note here that there is no interception when host is IP. String host = (String) args[0]; String ip = HttpDnsProvider.getHttpDnsService().getIpByHostAsync(host); Log.e("xhook"."invoke: success -> host:" + host + "; ip :" + ip);
                return InetAddress.getAllByName(ip);
            }
        }
        try {
            returnmethod.invoke(mOrigin, args); } catch (InvocationTargetException e) { throw e.getCause(); }}}Copy the code

However, this approach is also flawed.

  1. 9.0 non-public API restrictions
  2. Webview you haven’t solved for me (don’t worry, Webview is put last, dog head save life)

Further optimisation – sink to so hook

Since he calls the native method, can we hook the native method to achieve the goal? B: Sure. Let’s first look at the Posix native method corresponding code. The code is in libcore_io_posix.cpp as follows.

Notice that this is actually a call to the android_getaddrInfoFornet method, which is actually implemented in liBC, but we’ll skip the inline hook method for now and go step by step. Then. How can we hook into this approach? Hey, it seems that iQiyi has recently opened an open source hook solution, give it a try? By the way, this code was eventually compiled to libjavore. So. Just try it. Write the following code. PS: ignore my garbage c++ style


static int new_android_getaddrinfofornet(const char *hostname, const char *servname,
                                         const struct addrinfo *hints, unsigned netid,
                                         unsigned mark, struct addrinfo **res) {
    LOGE("hahahha,wo hook dao l ->android_getaddrinfofornet ");
    LOGE("Next is hostname.");
    LOGE(hostname, "");
    if (hints->ai_flags == AI_NUMERICHOST) {
        if (fp_android_getaddrinfofornet) {
            returnfp_android_getaddrinfofornet(hostname, servname, hints, netid, mark, res); }}else {
        const char *ip = getIpByHttpDns(hostname);
        if(ip ! = NULL) { LOGE("HTTPDNS resolution successful, direct to IP");
            LOGE("Here's the IP.");
            LOGE(ip, ""); // ai_flags is not an IP address, but ai_flags is not an IP address, but it is not an IP addressreturn fp_android_getaddrinfofornet(ip, servname, hints, netid, mark, res);
        } else {
            returnfp_android_getaddrinfofornet(hostname, servname, hints, netid, mark, res); }}return 0;
}

extern "C" int hook_android_getaddrinfofornet() {
    if (fp_android_getaddrinfofornet) {
        return0; } // libjavore. So int result = xhook_register(".*\\libjavacore.so$"."android_getaddrinfofornet",
                                (void *) new_android_getaddrinfofornet,
                                reinterpret_cast<void **>(&fp_android_getaddrinfofornet));
    xhook_refresh(1);
#if DEBUG
    xhook_enable_sigsegv_protection(0);
    xhook_enable_debug(1);
    LOGE("built type debug");
#elif RELEASE
    xhook_enable_sigsegv_protection(1);
    xhook_enable_debug(0);
    LOGE("built type release");
#endif
    return result;
}

Copy the code

A bit of code is omitted above (the full code link is at the end). Run, general scene, no problem, comfortable. So, let’s try it out with a Webview. As a result, something went wrong and didn’t go. Ok, let’s put “.*\ libjavore. So“I don’t buy it. Try again. Still no… The Webview is refined. This solution still doesn’t solve the Webview problem.

Advantages:

  1. Strong compatibility, compatible 4.4-9.0

Disadvantages:

  1. Still no Webview support

Look for Webview solutions

After some searching, I found the following article. Webview access to HttpDNS practice however, the result is a slap in the face, it doesn’t matter, finally gives us some ideas, right? We need to hook libwebviewchromium.so. We go to/system/app/WebViewGoogle/lib/arm/libwebviewchromium. So did this path to see this so (note, here I am millet 6 8.0), look at the results.

No!!

I pulled out this APK, decompiled it with JADX, and took a look. It is found that the library is loaded through system.loadLibrary

There was an awkward moment. Now, let’s take a look at the process’s maps information.

Android_getinfofornet is not found in these so about webview. We have no choice but to look at libWebViewChromium.

Oh? I also use liBC. Iqiyi xHook is a PLT/GOT table hook scheme, so we cannot load our process, we can only inline hook libc.so.

Inline hook, webView also works.

Let’s take a look at the relevant code.


__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int
getaddrinfo(const char *hostname, const char *servname,
    const struct addrinfo *hints, struct addrinfo **res)
{
	return android_getaddrinfofornet(hostname, servname, hints, NETID_UNSET, MARK_UNSET, res);
}
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
Copy the code

Android_getaddrinfofornet (); android_getAddrInfoFornet (); android_getAddrInfoFornet (); android_getAddrInfoFornet (); The alacrity.

The question now becomes which of the inline hook schemes is stable. This is scary because many of the relatively stable schemes for inline hooks are not open source. Lody’s AndHook. So, the final code is as follows.

static int my_getaddrinfo(const char *__node, const char *__service, const struct addrinfo *__hints, Struct addrinfo **__result) { Replace all android_getaddrInfofornet methods with new_android_getaddrInfofornet, so we call new_android_getaddrInfofornet directlyif(fp_android_getaddrinfofornet ! = NULL) {return new_android_getaddrinfofornet(__node, __service, __hints, NETID_UNSET, MARK_UNSET,
                                             __result);
    } else if(fp_android_getaddrinfoforiface ! = NULL) {return new_android_getaddrinfoforiface(__node, __service, __hints, NULL, 0,
                                               __result);
    }
    return EAI_FAIL;
}

static int JNICALL hooj_libc_getaddrinfo(JNIEnv *, jobject) {
    static bool hooked = false;
    int result = -1;

    AKLog("starting native hook...");
    if(! hooked) { AKHook(getaddrinfo); // typical usecase
        const void *libc = AKGetImageByName("libc.so");
        if(libc ! = NULL) { AKLog("base address of libc.so is %p", AKGetBaseAddress(libc));

            void *p = AKFindSymbol(libc, "getaddrinfo");
            if(p ! = NULL) { AKHookFunction(p, // hookedfunction
                               reinterpret_cast<void *>(my_getaddrinfo),   // our function
                               reinterpret_cast<void **>(&sys_getaddrinfo) // backup function pointer
                );
                AKLog("hook getaddrinfo success"); result = 0; } AKCloseImage(libc); } / /if

        hooked = true; } / /if

    return result;
}

Copy the code

Compile run, test, OK. Everything is going well.

conclusion

At this point, the study of HttpDns global replacement is over. We ended up with a good solution.

  1. Support 4.4 or later
  2. Support the Webview

Of course, the disadvantages are quite obvious.

  1. It relies on inline hook. The scheme of inline hook is relatively complex and poor compatibility, so it cannot guarantee the absolute stability and reliability of Lody AndHook
  2. The ghost knows the domestic manufacturer chamber of commerce will not casually modify the function name, so name

Here is the complete code, like to give a star