前段时间突然和别人讨论到arp检测这块的实现,心血来潮,将腾讯的wifi管家给反编译了一下,看了它如何实现arp检测,下面是分析的结果。
下面是wifi管家检测arp攻击的类,进入的时候会发送一个message,然后执行handleMessage函数
public void handleMessage(Message arg8) {
int v6 = 10001;
boolean v0 = true;
super.handleMessage(arg8);
switch(arg8.what) {
case 10001: {
goto label_7;
}
case 10002: {
goto label_42;
}
}
return;
label_7:
if((bth.a(this.eES)) && (bth.b(this.eES).refresh()) && (bap.a(bth.b(this.eES)))) {
bth.c(this.eES);
if(bth.d(this.eES) == 3) {
bth.e(this.eES);
bth.a(this.eES, 0);
}
bth.b(this.eES).Fu();
try {
v0 = bth.b(this.eES, 10001);
}
catch(Exception v1) {
}
}
if(bth.f(this.eES) == null) {
return;
}
if(!v0) {
return;
}
this.sendEmptyMessageDelayed(v6, bth.g(this.eES));
return;
label_42:
bth.a(this.eES, false);
bth.c(this.eES, 0);
int v2 = 0;
boolean v1_1 = true;
while(v2 != 2) {
try {
if((bth.b(this.eES).refresh()) && (bap.a(bth.b(this.eES)))) {
if(v2 == 0) {
bth.h(this.eES);
}
else {
bth.e(this.eES);
}
bth.b(this.eES).Fu();
v1_1 = bth.b(this.eES, 10002);
if(bth.i(this.eES) != 0) {
break;
}
SystemClock.sleep(700);
}
}
catch(Throwable v2_1) {
break;
}
++v2;
}
if(bth.f(this.eES) != null && (v1_1)) {
this.sendEmptyMessageDelayed(v6, bth.g(this.eES));
}
bth.a(this.eES, true);
}
刚开始执行的时候,runnable的run函数调用handler发送10002,所以label_42是检测的入口函数,bth.h是调用this.Fo函数,首先看一下this.Fo函数:
private void Fo() {
int v1 = this.eGX.Ft();
if(v1 != 0) {
int v0;
for(v0 = 0; v0 != 256; ++v0) {
String v2 = bao.kR(v1);
if(!v2.equals(this.eGX.eHl) && !v2.equals(this.eGX.eHj)) {
ban.io(v2);
}
v1 = bao.kS(v1);
}
}
}
public int Ft() {
int v0;
DhcpInfo v1 = this.eHC.getDhcpInfo();
if(v1 == null) {
v0 = 0;
}
else {
v0 = 16777215;
if(v1.netmask != 0) {
v0 = v1.netmask;
}
v0 &= v1.gateway;
}
return v0;
}
public static String kR(int arg2) {
return (arg2 & 255) + "." + (arg2 >> 8 & 255) + "." + (arg2 >> 16 & 255) + "." + (arg2 >> 24 & 255);
}
static {
ban.eHh = new byte[]{-126, 40, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1};
}
public static void io(String arg6) {
try {
DatagramSocket v0_1 = new DatagramSocket();
DatagramPacket v1 = new DatagramPacket(ban.eHh, ban.eHh.length, InetAddress.getByName(arg6), 137);
v0_1.setSoTimeout(100);
v0_1.send(v1);
v0_1.close();
}
catch(Exception v0) {
}
}
public static int kS(int arg2) {
int v0 = (arg2 >> 24 & 255) + 1;
return v0 <= 255 ? v0 << 24 | 16777215 & arg2 : -1;
}
可以看到这个函数的作用是针对这个局域网的子网下除了当前连接wifi的网关以外的所有ip发送udp请求,以此来让arp文件里记录局域网子网下所有的ip和mac地址,bth.e也是发送udp,不过它只向网关ip发送
arp文件初始化之后,紧接着调用Fu()函数,如下:
public ArrayList Fu() {
BufferedReader v0_2;
BufferedReader v1 = null;
this.eHy.clear();
try {
v0_2 = new BufferedReader(new FileReader("/proc/net/arp"));
}
catch(Throwable v0) {
goto label_50;
}
catch(Exception v0_1) {
v0_2 = v1;
goto label_40;
}
try {
v0_2.readLine();
while(true) {
String v1_3 = v0_2.readLine();
if(v1_3 == null) {
break;
}
String[] v1_4 = v1_3.split("[ ]+");
if(v1_4.length < 6) {
continue;
}
String v2 = v1_4[0];
v1_3 = v1_4[3];
if(v2.equals(this.eHj)) {
this.eHk = v1_3;
}
if(v1_3.equals("00:00:00:00:00:00")) {
continue;
}
this.eHy.add(new String[]{v2, v1_3, ""});
}
}
catch(Exception v1_1) {
goto label_40;
}
catch(Throwable v1_2) {
goto label_58;
}
if(v0_2 == null) {
goto label_42;
}
try {
v0_2.close();
}
catch(IOException v0_3) {
}
...
这段代码对this.eGX.eHy变量重新赋值,这个变量就是用来存放arp文件内容的,下面会用到这个变量,它的内容其实就是”/proc/net/arp”文件的内容
变量初始化完之后,这时候arp内容就可以进行分析了,然后执行bth.b函数,它调用了dY函数,如下:
private boolean dY(int arg11) {
Object v1_1;
Object v2;
boolean v8 = false;
if(!TextUtils.equals(this.eHc, this.eGX.BSSID)) {
this.Fn();
}
else {
int v7 = 0;
boolean v1 = true;
while(v7 != this.eGX.eHy.size()) {
CharSequence v4 = this.eGX.eHy.get(v7)[0];
Object v3 = this.eGX.eHy.get(v7)[1];
if(!TextUtils.equals(v4, this.eGX.eHj)) {
this.eGZ.put(v3, v4);
}
else {
Object v0 = this.eGY.get(v4);
if(TextUtils.isEmpty(((CharSequence)v0))) {
this.eGY.put(v4, v3);
}
else {
if(v3 != null && (TextUtils.equals(((CharSequence)v0), ((CharSequence)v3)))) {
goto label_29;
}
if(TextUtils.equals(((CharSequence)v3), this.eGX.eHk)) {
v2 = this.eGY.get(v4);
}
else {
String v2_1 = ((String)v3);
}
v0 = this.eGZ.get(v3);
if(v0 == null || (((String)v0).equals(v4))) {
this.nx();
String v1_2 = this.ah(((String)v2), ((String)v4));
}
else {
v1_1 = v0;
}
this.kP(arg11);
if(this.eHb != null) {
this.eHb.a(((String)v1_1), ((String)v2), this.im(((String)v1_1)), arg11, this.eGU, this.c(this.eGX.eHj, this.eGX.eHk, ((String)v1_1), ((String)v2)));
}
this.reset();
v1 = false;
}
}
label_29:
++v7;
}
v8 = v1;
}
return v8;
}
上面的v7是this.eGX.eHy的元素下标,目的是为了遍历this.eGX.eHy,所以这边是对arp文件内容进行分析的函数,首先对this.eGX.eHy进行解释,这个变量是一个ArrayList,上面提到的Fu()函数初始化了这个变量,它将arp文件里的每行记录变成this.eGX.eHy的一条记录,下面是执行查看arp的结果,可以看到每行的数据包含六个元素,第一个是Ip,第四个是mac address
然后再解释一下this.eGX.BSSID和this.eGX.eHj怎么来的,下面是它的初始化函数
public boolean refresh() {
int v7 = 34;
boolean v0 = false;
try {
WifiInfo v2 = this.eHC.getConnectionInfo();
DhcpInfo v3 = this.eHC.getDhcpInfo();
if(!this.eHC.isWifiEnabled()) {
return v0;
}
if(!this.Fs()) {
return v0;
}
if(v2 != null && v3 != null && v2.getSupplicantState() == SupplicantState.COMPLETED) {
this.SSID = v2.getSSID();
int v4 = v2.getIpAddress();
if(this.SSID.charAt(0) == v7 && this.SSID.charAt(this.SSID.length() - 1) == v7) {
this.SSID = this.SSID.substring(1, this.SSID.length() - 1);
}
if(!this.Fs()) {
return v0;
}
if(TextUtils.isEmpty(this.SSID)) {
return v0;
}
if("<unknown ssid>".equalsIgnoreCase(this.SSID)) {
return v0;
}
if(v4 == 0) {
return v0;
}
this.BSSID = v2.getBSSID();
this.eHj = bao.kR(v3.gateway);
...
通过上面可以看到this.eGX.BSSID其实就是当前连接的wifi的bssid,this.eGX.eHj其实就是网关ip
最后来看一下怎样检测arp攻击,this.eGZ是一个HashMap,key是mac地址,value是ip,下面是找出现在的arp文件里网关Ip对应的mac是否和之前的不一样的arp记录
if(!TextUtils.equals(v4, this.eGX.eHj)) {
this.eGZ.put(v3, v4);
}
else {
Object v0 = this.eGY.get(v4);
if(TextUtils.isEmpty(((CharSequence)v0))) {
this.eGY.put(v4, v3);
}
this.eGY也是一个HashMap,key是ip,value是mac,下面的代码是找出网关ip对应的多个mac地址情况,如果发现网关ip对应的mac和之前的不一样,那么说明遇到arp攻击,就需要发送攻击者相关信息给服务器,注意的是this.eGY在开始检测前会先将arp里面的网关ip和mac地址放进去,然后刷新arp,所以才会出现同一个网关ip对应多个Mac情况
else {
if(v3 != null && (TextUtils.equals(((CharSequence)v0), ((CharSequence)v3)))) {
goto label_29;
}
if(TextUtils.equals(((CharSequence)v3), this.eGX.eHk)) {
v2 = this.eGY.get(v4);
}
else {
String v2_1 = ((String)v3);
}
v0 = this.eGZ.get(v3);
if(v0 == null || (((String)v0).equals(v4))) {
this.nx();
String v1_2 = this.ah(((String)v2), ((String)v4));
}
else {
v1_1 = v0;
}
this.kP(arg11);
if(this.eHb != null) {
this.eHb.a(((String)v1_1), ((String)v2), this.im(((String)v1_1)), arg11, this.eGU, this.c(this.eGX.eHj, this.eGX.eHk, ((String)v1_1), ((String)v2)));
}
this.reset();
v1 = false;
}
private String ah(String arg6, String arg7) {
int v1;
for(v1 = 0; v1 != this.eGX.eHy.size(); ++v1) {
String v3 = this.eGX.eHy.get(v1)[0];
if((this.eGX.eHy.get(v1)[1].equals(arg6)) && !v3.equals(arg7)) {
String v0 = v3;
return v0;
}
}
return "0.0.0.0";
}
现在真相大白了,主要检测方法: 1. arp攻击就是发送arp广播让局域网内的所有设备将网关ip对应的mac地址改成攻击者的mac地址,这样的话被攻击者的网关ip是对的,但是mac地址变了,因此只要找出来当前网络的网关mac地址对应的所有Ip,里面肯定包括了真正的网关ip和攻击者的网关ip,另外根据上面的代码还会出现同一个网关ip对应多个Mac的情况,这种事因为检测开始前先将arp里面的网关ip和mac地址放进去,然后检测开始时会刷新arp文件,如果受到攻击,此刻的网关ip对应的mac可能会和之前的不一样
2. 扫描局域网内子网下的所有ip,然后获取arp文件内容,生成一个三维数组,第一个元素是ip,第二个是mac地址,最后一个不用管
3. 将上面获取到的三维数组进行遍历,找出网关ip对应是否多个mac地址
4. 如果发现网关ip对应至少两个mac地址,那么不等于网关Ip的那个mac地址就是攻击者的mac地址,然后再找出这个mac对应的不等于网关ip的那个ip就是攻击者的真实ip
5. 可能大家也会遇到一个疑惑,上面的检测是基于刷新后的arp里面的网关对应的mac是正确的基础上的,那如何保证网关mac此刻是正确的呢,抓包看了一下,wfi管家并没有加快wifi网关mac的更新,测了一下,我的手机上是30秒左右更新一次网关mac,所以初步怀疑只有在刚好更新mac的这段时间内wifi管家才能检测出问题。