热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

webview接入HttpDNS实践

本文是对去年做的webview接入HttpDNS工作的一个总结,拖的时间有点久了。主要分享了GOTHookwebview中域名解析函数的方法。HttpDNS简介首先简单介绍下移动Ap

本文是对去年做的webview接入HttpDNS工作的一个总结,拖的时间有点久了。主要分享了GOT Hook webview中域名解析函数的方法。

HttpDNS简介

首先简单介绍下移动App接入HttpDNS后有什么好处,这里直接引用腾讯云文档中的说明:

HttpDNS是通过将移动APP及桌面应用的默认域名解析方式,替换为通过Http协议进行域名解析,以规避由运营商Local DNS服务异常所导致的用户网络接入异常。减少用户跨网访问,降低用户解析域名时延。

更详细的内容可以参考这篇文章:【鹅厂网事】全局精确流量调度新思路-HttpDNS服务详解

移动端的实现原理

域名的解析工作将在HttpDNS服务器上完成,客户端只要把待解析的域名作为参数发起一个HTTP请求,HttpDNS服务器就会把解析结果下发给客户端了。
在客户端,默认的域名解析是系统的getaddrinfo()库函数实现的,默认的域名解析请求会走到LocalDNS。
所以域名解析的工作必须要交给app应用层来实现。下面介绍几种实现方案。

1、okhttp

okhttp的实现是建立在socket之上的,并且实现了HTTP协议。对于客户端发起的http请求,okhttp首先会跟远端服务器建立socket连接,在此之前okhttp会根据http请求中url的domain做域名解析,默认的实现是java网络库提供的InetAddress.getAllByName

如果项目中用的网络库是okhttp,所有的网络请求都是通过它完成的话就可以使用okhttp提供的DNS解析接口,实现自己的DNS resolver,代码如下:

public class HttpDns implements Dns {
@Override
public List lookup(String hostname) throws UnknownHostException {
//DNSHelper完成DNS解析的具体工作,向HttpDNS服务器请求服务。
String ip = DNSHelper.getIpByHost(hostname);
List inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
return inetAddresses;
}
}

2、native hook的方法

通过Hook libc的getaddrinfo库函数,将函数指针指向app应用层实现的DNS解析函数地址。
要深入了解linux native hook的技术的话,需要了解ELF文件格式和动态链接的相关知识,可参考ELF文件及android hook原理。

getaddrinfo是在libc.so中的定义的,其它库如libandroid_runtime.solibjavacore.so要使用这个函数的话,只能通过动态导入符号的形式,好在java网络库底层是就是通过这个方式实现的。

android nativehook原理

下面代码是arm架构的一种实现方案,具体实现参考Andrey Petrov的blog.

#include "linker.h" // get it from bionic

unsigned elfhash(const char *_name); //hash函数
//查找散列表。经典的链接法解决散列冲突

static Elf32_Sym *soinfo_elf_lookup(soinfo *si, unsigned hash, const char *name)
{
Elf32_Sym *s;
Elf32_Sym *symtab = si->symtab;
const char *strtab = si->strtab;
unsigned n;
n = hash % si->nbucket;
for(n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]){
s = symtab + n;
if(strcmp(strtab + s->st_name, name)) continue;
return s;
}
return NULL;
}


//soname:动态库名称;
//symbol:待hook的函数名;
//newval:新函数地址
int hook_call(char *soname, char *symbol, unsigned newval) {
soinfo *si = NULL;
Elf32_Rel *rel = NULL;
Elf32_Sym *s = NULL;
unsigned int sym_offset = 0;
//打开动态库,得到soinfo对象
si = (soinfo*) dlopen(soname, 0);
//通过查找散列表找到symbol对应符号表的索引
s = soinfo_elf_lookup(si, elfhash(symbol), symbol);
sym_offset = s - si->symtab;

rel = si->plt_rel;//指向plt表的起始位置
//遍历plt表
for (int i = 0; i plt_rel_count; i++, rel++) {
unsigned type = ELF32_R_TYPE(rel->r_info);
unsigned sym = ELF32_R_SYM(rel->r_info);
//加上动态库的基址,定位到该符号重定向元素的内存
unsigned reloc = (unsigned)(rel->r_offset + si->base);
uint32_t page_size = 0;
uint32_t entry_page_start = 0;
unsigned oldval = 0;
if (sym_offset == sym) { //是否是待hook的符号位置
switch(type) {
case R_ARM_JUMP_SLOT:
//修改内存页的属性为可读写
page_size = getpagesize();
entry_page_start = reloc& (~(page_size - 1));
int ret = mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_WRITE);

oldval = *(unsigned*) reloc;
*((unsigned*)reloc) = newval; //成功替换这块内存的值为新函数的地址值
return 1;
default:
return 0;
}
}
}
return 0;
}

程序中调用mprotect的作用是: 修改一段指定内存区域的保护属性。以防万一,将这它改为可读写,因为后面就要对这块内存做写操作了。
函数原型为:int mprotect(const void *start, size_t len, int prot);
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

用法:

hook_call("libjavacore.so", "getaddrinfo", &my_getaddrinfo);  
  • 1.调用dlopen拿到so的句柄,得到soinfo,它包含了符号表、重定位表、plt表等信息。
  • 2.查找需要hook的函数的符号,得到它在符号表中的索引。
  • 3.遍历plt表,直到匹配第2步中找到的符号索引。
    如果是JUMP_SLOT类型(函数调用),替换为新的符号地址(函数指针)。
    如下图所示,my_code_func的函数地址替换了GOT表项中原来指向libc中的getaddrinfo函数地址,达到了hook的效果。
    )) {
    try {
    URL oldUrl = new URL(url);
    URLConnection cOnnection= oldUrl.openConnection();
    // 获取HttpDns域名解析结果
    String ips = MSDKDnsResolver.getInstance().getAddrByName(oldUrl.getHost());
    String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
    // 设置HTTP请求头Host域
    cOnnection= (HttpURLConnection) new URL(newUrl).openConnection();
    connection.setRequestProperty("Host", oldUrl.getHost());
    }
    return new WebResourceResponse("text/css", "UTF-8", connection.getInputStream());
    }
    }}

    必须要对webview的DNS域名解析函数进行拦截替换。
    webview的DNS域名解析函数具体实现是在chromiumn.so,不同版本的实现也不同,5.0版本的代码见host_resolver.h
    webview的DNS域名解析函数是否也跟java的网络库一样最终调用的libc.so动态库中getaddrinfo呢?
    通过源码注释得知确实如此。

    用Android Studio调试Framework层代码中也对其进行过断点调试。
    所以解决方法很简单,只需要hook libchromium_net.sogetaddrinfo导入符号即可。

    hook_call("libchromium_net.so", "getaddrinfo", &my_getaddrinfo);  

    机型问题

    在实践中我们发现,不同机型不同版本的android在实现DNS解析函数的导出符号是不同的,更糟糕的是调用DNS解析函数的动态库也不一定就是libjavacore.so
    我之前定位过Android5.0设备的DNS解析函数,发现它的名字改为android_getaddrinfofornet

    webview的so库位置也曾遇到过找不到的问题。

    解决方法是通过一个脚本,pull下测试设备上的所有so到主机上,然后用readelf工具查找so的导入符号,观察是否有getaddrinfo字样的导入符号。
    为此我写了一个脚本,方便自动化进行。运行如下命令即可

    $ python sofinder.py -e getaddrinfo


    在上面输出的第一行可以看到,android 5.0以上版本webview的so已经被放在system/app目录中了。
    需要写全so的路径:

    hook_call("/system/app/WebViewGoogle/lib/arm/libwebviewchromium.so", "getaddrinfo", &my_getaddrinfo);  

    参考

    【鹅厂网事】全局精确流量调度新思路-HttpDNS服务详解
    ELF文件及android hook原理
    Andrey Petrov’s blog


推荐阅读
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 在单位的一台4cpu的服务器上部署了esxserver,挂载了6个虚拟机,目前运行正常。在安装部署过程中,得到了cnvz.net论坛精华区 ... [详细]
  • linux6.5 配置邮件服务,centos 6.5服务器搭建邮件服务postfix和dovecot
    centos6.5搭建邮件服务postfix和dovecot------------------------------------------------安装DNS指定邮件交换记 ... [详细]
  • windows平台使用NSP拦截具体进程的域名解析过程(xFsRedir的代理功能之域名代理)
    byfanxiushu2022-10-17转载或引用请注明原始作者。xFsRedir软件其中之一的功能就是实现了全方位的网络代理,从主机代理,到本地代理 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在RHEL 7中的系统日志管理和网络管理。系统日志管理包括rsyslog和systemd-journal两种日志服务,分别介绍了它们的特点、配置文件和日志查询方式。网络管理主要介绍了使用nmcli命令查看和配置网络接口的方法,包括查看网卡信息、添加、修改和删除配置文件等操作。 ... [详细]
  • 浅解XXE与Portswigger Web Sec
    XXE与PortswiggerWebSec​相关链接:​博客园​安全脉搏​FreeBuf​XML的全称为XML外部实体注入,在学习的过程中发现有回显的XXE并不多,而 ... [详细]
  • 域名解析系统DNS
    文章目录前言一、域名系统概述二、因特网的域名结构三、域名服务器1.根域名服务器2.顶级域名服务器(TLD,top-leveldomain)3.权威(Authoritative)域名 ... [详细]
  • CentOS 7配置SSH远程访问及控制
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了移动web性能测试笔记之一相关的知识,希望对你有一定的参考价值。收集整理@2017/12/16 ... [详细]
  • IamsettingupApacheserverwithTortoiseSVNforalocalsourcecoderepository.Ihaveobservedt ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
author-avatar
夏y儿
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有