HTTPDNS使用HTTP協議進行域名解析,代替現有基於UDP的DNS協議,域名解析請求直接發送到阿里雲的HTTPDNS服務器,從而繞過運營商的Local DNS,可以避免Local DNS形成的域名劫持問題和調度不精準問題。java
阿里雲文檔中,提到的HttpDns的最佳實踐的幾個場景,雖然在必定程度上能解決咱們的問題,可是存在必定的缺陷、node
那麼,咱們是否可以尋找一種全局替換的方案呢?或者解決的場景可以更多一點的方案呢?android
通過一番搜索,找到了 Android弟的這邊文章 一種全局攔截並監控 DNS 的方式 以及這邊文章如何爲Android應用提供全局的HttpDNS服務。 在第一篇文章提到的方案中,缺陷是很是明顯的c++
而在第一篇文章中,提到的方案也是這樣。在7.0之下 hook coonnect這個,沒有相應的代碼,對我我等菜雞來講,難度太大。git
所以,現有的方案中,不合適。github
咱們先來看下Dns解析的過程是什麼樣的,以API 25的SDK爲例,發現下面的代碼片斷。web
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); 複製代碼
那麼,咱們繼續跟蹤源代碼。看看Libcore.os是個什麼東西。bash
public final class Libcore {
private Libcore() { }
public static Os os = new BlockGuardOs(new Posix());
}
複製代碼
而Os是一個接口,代碼片斷以下。服務器
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;
複製代碼
Posix的代碼片斷以下app
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;
複製代碼
Libcore.os.android_getaddrinfo其實是調用的Posix的android_getaddrinfo這個native方法。
到此爲止,咱們能夠明確,經過動態代理Os這個接口,而且替換Libcore.os這個字段,咱們在調用android_getaddrinfo這個方法中,插入HttpDns解析,那麼,就能夠解決部分的問題(除webview之外的全部場景)。固然,咱們要考慮適配的問題,在4.4版本上,android_getaddrinfo這個方法是getaddrinfo。所以,咱們寫下以下的代碼。
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)) {
//這裏須要注意的是,當host爲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 {
return method.invoke(mOrigin, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
}
複製代碼
可是,這種方法也是有缺陷的。
既然他調用的是native的方法,那麼,咱們可不能夠經過hook 這個native方法去達到目的呢?固然能夠。咱們先看下Posix的native方法對應的代碼。代碼在Libcore_io_Posix.cpp中,以下。
注意看,這個其實是調用的android_getaddrinfofornet這個方法,實際上這個方法的實現是在libc中的,可是,咱們如今暫時略過inline hook這個方法,一步一步來。那麼。咱們能如何hook到這個方法呢?咦,好像愛奇藝最近開源了一個hook方案,試一下?捎帶說一下,這些代碼最後是編譯成libjavacore.so的。說試就試,動手寫下以下代碼。PS:忽略我垃圾的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("下面是hostname");
LOGE(hostname, "");
if (hints->ai_flags == AI_NUMERICHOST) {
if (fp_android_getaddrinfofornet) {
return fp_android_getaddrinfofornet(hostname, servname, hints, netid, mark, res);
}
} else {
const char *ip = getIpByHttpDns(hostname);
if (ip != NULL) {
LOGE("httpdns 解析成功,直接走IP");
LOGE("下面是ip");
LOGE(ip, "");
//這裏就比較神奇了,傳的是IP,可是ai_flags 不是ip,可是還正常,查看源碼的時候,沒發如今這個代碼裏面判斷
// ai_flags 不numberhost仍是其餘
return fp_android_getaddrinfofornet(ip, servname, hints, netid, mark, res);
} else {
return fp_android_getaddrinfofornet(hostname, servname, hints, netid, mark, res);
}
}
return 0;
}
extern "C" int hook_android_getaddrinfofornet() {
if (fp_android_getaddrinfofornet) {
return 0;
}
//libjavacore.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;
}
複製代碼
上面省略了一點代碼(完整的代碼連接放在末尾)。運行,通常場景,一點問題沒有,舒服。那麼,用Webview試下。結果,出問題了,沒走。。好吧,咱們把".*\libjavacore.so",一股腦都替換,我就不信了。再試。仍是不行。。。。Webview成精了。這個方案仍是沒解決Webview的問題,好氣!
優勢:
缺點:
一番搜索以後,發現以下文章。webview接入HttpDNS實踐 可是,結果啪啪打臉,不要緊,總算給了咱們點思路不是麼?文中做者說咱們hook掉libwebviewchromium.so 就行,ok,按照他的思路,咱們去/system/app/WebViewGoogle/lib/arm/libwebviewchromium.so這個路徑下看看有沒有這個so(這裏說明一下,我是小米6 8.0),看下結果。
沒有啊!!
我把這個apk拉出來,用jadx反編譯,看了看。發現這個庫是經過System.loadLibrary去加載的
場面一度十分尷尬。如今,咱們查看下進程的maps信息。
在關於webview的這幾個so裏面,都沒有找到android_getinfofornet。沒得辦法,咱們只能去看下libwebviewchromium。
哦?也是用的libc的。愛奇藝的xHook,是PLT/GOT表hook方案,而這個so,咱們又加載不到咱們的進程來,沒辦法,只能inline hook libc.so了。
咱們先看下相關的代碼。
__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
複製代碼
在上面的過程當中,咱們已經hook 掉了android_getaddrinfofornet,所以,咱們只要hook,getaddrinfo,讓這個方法調用咱們本身的掉了android_getaddrinfofornet方法就能夠解決了,美滋滋。
咱們如今的問題變成了,哪一個inline hook的方案穩定。這個事情很恐怖,由於不少inline hook的相對穩定的方案是不開源的,我這裏呢?用的是Lody的AndHook。全部,最後的代碼以下。
static int my_getaddrinfo(const char *__node, const char *__service, const struct addrinfo *__hints,
struct addrinfo **__result) {
// 這裏有用xHook的時候,把全部的的android_getaddrinfofornet方法都替換爲new_android_getaddrinfofornet,
// 所以咱們這裏直接調用 new_android_getaddrinfofornet就行
if (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 use case
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, // hooked function
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;
}
複製代碼
編譯運行、測試,ok。一切順利。
到此,對HttpDns全局替換的研究就告一段落了。咱們最終實現的方案仍是不錯的。
固然,缺點也是至關比較明顯的。