乐安全内嵌广告屏蔽原理
一、原理
分析完了手机毒霸去广告功能的原理,接下来我们来看看“乐安全”是如何去内嵌广告的。对手机毒霸去广告功能感兴趣的读者可以看一下我们之前发的三篇技术博客:
手机毒霸去广告功能分析一:总体分析
http://blog.sina.com.cn/s/blog_be6dacae01018hjg.html
手机毒霸去广告功能分析二:广告View的识别
http://blog.sina.com.cn/s/blog_be6dacae01018lfb.html
手机毒霸去广告功能分析三:java代码(dex)注入
http://blog.sina.com.cn/s/blog_be6dacae01018pz2.html
好,言归正传。乐安全的去内嵌广告功能非常强大(如图)。
整个去内嵌广告功能就一个开关,“一键拦截全部应用内嵌广告”。要使用这个功能必须首先获得root权限。一开始笔者以为乐安全和手机毒霸的原理差不多,也是通过进程注入广告应用进程,然后将广告View隐藏来实现的。可是当笔者将广告应用进程的/proc/xxxx/maps打开一看,发现乐安全并没有注入它。于是对乐安全进行了逆向工程分析,发现乐安全原来是这么实现的。
首先,在介绍实现原理之前,你必须要知道/etc/hosts这个文件。这个文件里存放的就是本地域名解析规则,也就是说如果本机要访问某个域名,首先会到这个文件里查找,有没有这个域名对应的IP地址,如果有直接就访问这个IP地址了,如果没有才会去向DNS服务器发出域名解析请求。
其次,绝大多数广告的实现,都是后台从网络服务器上下载广告资源,然后再在本机显示。这个下载过程就必须要涉及到域名解析过程。如果在域名解析阶段,返回错误的IP地址,那么广告资源也就无法下载了。这样也就达到了屏蔽广告的目的。
最后,乐安全就是按照这一原理,修改了系统的/etc/hosts文件。让广告域名都指向本机IP地址。这里列出这个文件的片段,让大家有一个更加直观的感受:
127.0.0.1 localhost
::1 localhost
127.0.0.1 banners.vivilatina.com
127.0.0.1 208.185.87.8.liveadvert.com
127.0.0.1 counter.bizland.com
127.0.0.1 t2.leadlander.com
127.0.0.1 line07.metriweb.be
127.0.0.1 adpop.theglobe.net
127.0.0.1 ad.bharatstudent.com
…
二、优劣势
这种实现方式有其优势:
1. 开发和实现成本低,只需要维护一个广告服务器的黑名单即可
2. 用户操作简单,就一个总开关,打开之后,整个系统都没有广告了(前提是黑名单足够全)。
3. 对原系统的影响小:内存占用少,性能损失小
也有其劣势:
1. 有些服务器既提供广告服务,又提供正常的其他服务,比如web服务。那么如果这个服务器的域名被加入黑名单,那么用户要访问这个服务器上的网站也就不可能了。如果这个服务器的域名没有被加入黑名单,那么来自这个服务器的广告又屏蔽不了。
2. 无法屏蔽非实时网络抓取的广告。有的广告插件显示的广告并不是实时从网络上抓取的,而是内嵌在应用内部,或者是之前从网络上抓取下来放到本地的(假设之前乐安全屏蔽广告功能尚未开启)。那么乐安全就无法屏蔽这类广告。
三、与手机毒霸比较
下面将手机毒霸和乐安全的广告屏蔽功能做一比较
实现方式
实现难易和维护成本
去广告颗粒度
事前还是事后
是否会影响正常上网
是否需要root
卸载后是否有文件残留
乐安全
修改/etc/hosts防止广告资源下载
容易,维护成本低
粗颗粒度,一个开关控制整个系统是否屏蔽广告,简单粗暴
事前防御,广告资源无法到达手机。
可能会影响。如果用户本意就是就要通过浏览器访问黑名单中的域名。
需要
是。广告屏蔽打开时,卸载乐安全,/etc/hosts不会自动复原。所以拦截依然生效。
手机毒霸
进程注入,隐藏广告View
困难,维护成本高
细颗粒度,可以分别屏蔽每个应用的广告
事后补救,广告资源已到手机,只是内容显示不出来。
基本不会。
需要
否。进程注入本身不会修改系统文件。
更多内容请关注 http://blog.sina.com.cn/u/3194858670 以及 sina微博@ 安卓安全小分队
主要作用可以分为两种:
一种是建立一个信息安全通道,来保证数据传输的安全。
另一种就是确认网站的真实性。
HTTPS和HTTP的区别:
一、https协议需要到CA机构申请证书,一般免费证书很少,需要交费。
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行
加密传输、身份认证的网络协议,比http协议安全。
SSL介绍:
SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。
SSL协议可分为两层:
1、SSL记录协议(SSL Record Protocol):
它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、
加密等基本功能的支持。
2、SSL握手协议(SSL Handshake Protocol):
它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、
协商加密算法、交换加密密钥等。
SSL协议主要有哪些作用?
1)认证用户和服务器,确保数据发送到正确的客户机和服务器
2)加密数据以防止数据中途被窃取
3)维护数据的完整性,确保数据在传输过程中不被改变。
SSL协议的工作流程:太复杂了,这里省略。
主要有2个阶段:
步骤:
keytool证书与密钥管理
1)创建服务端密钥库
>keytool -genkey -keystore E:\serverkey.jks -keyalg rsa -alias ssl1 -validity 700
上述命令中:
-genkey 生成密钥对
-keystore 指定密码仓库的文件地址
-keyalg 密钥所使用的算法
-alias 密钥别名..使用密钥时是使用此别名来区分的
-validity 密钥有效期(天)..从当前系统时间开始计算
该命令成功后会要求输入密码仓库的密码..例如xiawenquan
然后是输入你的个人信息..等等。最后在E盘生成一个serverkey.jks证书
2)将服务端的公钥导出成证书
>keytool -export -alias ssl1 -file e:\ssl1.cer -keystore e:\serverkey.jks
-export 导出的意思
导出成功后就得到了ssl1.cer这一份证书了..然后就是要将这一份证书分发给客户端..客户端有了该证书后就能与服务端建立安全连接了
3)生成客户端的密钥仓库
keytool -genkey -keystore e:\clientkey.jks -keyalg rsa -alias ssl1 -validity 700
4)将ssl1.cer导入到客户端的密钥仓库里
>keytool -import -file e:\ssl1.cer -keystore e:\clientkey.jks
上述命令成功后会要求输入客户端密钥仓库的密码..,比如:xiawenquan
成功后在每次要访问服务端之前还要将该证书添加到受信域中..系统会自动从受信域中检查可用的证书来完成SSL连接通讯..
package com.xiawenquan.ssl;
/**
* Java sslSocket 聊天实例
*SSL Socket的服务器端
*@Author xiawenquan
*/
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
public class SSLServer {
public static void startSSLServer() throws IOException {
final int port = 16666;// 监听端口
String keyFile = "E:\\serverkey.jks";// 密钥库文件
String keyFilePass = "xiawenuqan";// 密钥库的密码
String keyPass = "changeit";// 密钥别名的密码
SSLServerSocket sslsocket = null;// 安全连接套接字
KeyStore ks;// 密钥库
KeyManagerFactory kmf;// 密钥管理工厂
SSLContext sslc = null;// 安全连接方式
// 初始化安全连接的密钥
try {
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyFile), keyFilePass.toCharArray());
// 创建管理JKS密钥库的X.509密钥管理器
kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keyPass.toCharArray());
//构造SSL环境,指定SSL版本为3.0,也可以使用TLSv1,但是SSLv3更加常用
sslc = SSLContext.getInstance("SSLv3");
//初始化SSL环境。第二个参数是告诉JSSE使用的可信任证书的来源,
//设置为null是从javax.net.ssl.trustStore中获得证书。第三个参数是JSSE生成的随机数,
//这个参数将影响系统的安全性,设置为null是个好选择,可以保证JSSE的安全性。
sslc.init(kmf.getKeyManagers(), null, null);
} catch (KeyManagementException ex) {
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 用安全连接的工厂来创建安全连接套接字
SSLServerSocketFactory sslssf = sslc.getServerSocketFactory();
sslsocket = (SSLServerSocket) sslssf.createServerSocket();// 创建并进入监听
SocketAddress sa=new InetSocketAddress("localhost",port);
sslsocket.bind(sa);
System.out.println("Listening...");
SSLSocket ssocket = (SSLSocket) sslsocket.accept();// 接受客户端的连接
System.out.println("Server Connection OK~");
System.out.println("========================");
System.out.println("");
// 以下代码同socket通讯实例中的代码
BufferedReader socketIn = new BufferedReader(new InputStreamReader(
ssocket.getInputStream()));
BufferedReader userIn = new BufferedReader(new InputStreamReader(
System.in));
PrintStream socketOut = new PrintStream(ssocket.getOutputStream());
String s;
while (true) {
System.out.println("Please wait client 's message..");
System.out.println("");
s = socketIn.readLine().trim();
if(s != null && !s.equals("")){
System.out.println("Client Message: " + s);
if (s.trim().equals("BYE")){
break;
}
System.out.print("Server Message: ");
}
s = userIn.readLine().trim();
if(s != null && !s.equals("")){
socketOut.println(s);
if (s.trim().equalsIgnoreCase("BYE")){
break;
}
}
}
socketIn.close();
socketOut.close();
userIn.close();
sslsocket.close();
}
public static void main(String[] args) {
try {
startSSLServer();
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
}
package com.xiawenquan.ssl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import javax.net.ssl.SSLSocketFactory;
/**
* Java sslSocket 聊天实例
* SSL Socket的客户端
* @author xiawenquan
*
*/
public class SSLClient {
static int port = 28080;
public static void startSSLClient() throws IOException {
int port = 16666;// 要连接的服务器端口
String serverAdd = "localhost";// 要连接的服务器地址192.168.1.39
try {
System.setProperty("javax.net.ssl.trustStore", "E:\\serverkey.jks");// 设置可信任的密钥仓库
System.setProperty("javax.net.ssl.trustStorePassword", "xiawenuqan"); // 设置可信任的密钥仓库的密码
SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory
.getDefault();// 利用工厂来创建SSLSocket安全套接字
Socket csocket = sslsf.createSocket(serverAdd, port);// 创建并连接服务器
System.out.println("Client OK~");
System.out.println("===============");
System.out.println("");
// 以下代码同socket通讯实例中的代码
BufferedReader socketIn = new BufferedReader(new InputStreamReader(
csocket.getInputStream()));// 接受到的信息
PrintStream socketOut = new PrintStream(csocket.getOutputStream());// 要发送的信息
BufferedReader userIn = new BufferedReader(new InputStreamReader(
System.in));// 用户输入信息
String s;
while (true) {
System.out.print("Client Message: ");
s = userIn.readLine().trim();
if(s != null && !s.equals("")){
socketOut.println(s);
if (s.trim().equals("BYE"))
break;
else {
System.out.println("Please wait Server Message..");
System.out.println("");
}
s = socketIn.readLine();
System.out.println("Server Message: " + s);
if (s.trim().equalsIgnoreCase("BYE")){
break;
}
}
}
socketIn.close();
socketOut.close();
userIn.close();
csocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
startSSLClient();
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
}
布局文件:
<RelativeLayout android:id="@+id/date_picker"
android:layout_width="wrap_content" android:layout_height="wrap_content">
<TextView android:id="@+id/birthday_text" android:editable="false"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textColor="#000000" android:textSize="24sp" android:background="@drawable/text_bg"/>
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/date_select_icon" android:layout_alignRight="@id/birthday_text"
android:layout_centerVertical="true" android:layout_marginRight="8dp"/>
</RelativeLayout>
public class TestActivity extends Activity {
private int mYear;
private int mMonth;
private int mDay;
static final int DATE_DIALOG_ID = 1;
TextView date;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
date = (TextView) findViewById(R.id.birthday_text);
final Calendar c = Calendar.getInstance();
mYear = c.get(Calendar.YEAR);
mMonth = c.get(Calendar.MONTH);
mDay = c.get(Calendar.DAY_OF_MONTH);
RelativeLayout datePicker = (RelativeLayout) findViewById(R.id.date_picker);
datePicker.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showDialog(DATE_DIALOG_ID);
}
});
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DATE_DIALOG_ID:
return new DatePickerDialog(TestActivity.this, mDateSetListener,
mYear, mMonth, mDay);
}
return null;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DATE_DIALOG_ID:
((DatePickerDialog) dialog).updateDate(mYear, mMonth, mDay);
break;
}
}
private DatePickerDialog.OnDateSetListener mDateSetListener = new DatePickerDialog.OnDateSetListener() {
public void onDateSet(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
mYear = year;
mMonth = monthOfYear;
mDay = dayOfMonth;
date.setText(mYear + "-" + (mMonth + 1) + "-" + mDay);
}
};
}