黑帽python第二版(Black Hat Python 2nd Edition)读书笔记 之 第十章 Windows提权(2)Windows令牌权限
文章目录
现在我们已经有了基本的进程监控,让我们在日志中填写特权字段。首先,我们了解一下Windows特权是如何工作的,以及为什么它们很重要。
Windows令牌权限
根据Microsoft的说法,Windows令牌是“描述进程或线程的安全上下文的对象”。换句话说,令牌的权限和特权决定了进程或线程可以执行哪些任务。
误解这些令牌会给我们带来麻烦。作为安全产品的一部分,善意的开发人员可能会创建一个系统托盘应用程序,他们希望在该应用程序上让非特权用户能够控制主Windows服务,即驱动程序。开发人员在进程上使用本机Windows API函数AdjustTokenPrivileges,然后天真地授予系统托盘应用程序SeLoadDriver权限。开发人员没有注意到的是,如果你可以进入系统托盘应用程序,你现在可以加载或卸载任何你想要的驱动程序,这意味着你可以删除内核模式的rootkit,这意味着游戏结束了。
有趣的特权
请记住,如果我们不能以SYSTEM或管理员的身份运行我们的进程监视器,那么我们需要关注能够监视哪些进程。我们是否可以利用其他特权?以具有错误权限的用户身份运行的进程是访问SYSTEM或在内核中运行代码的绝佳方式。下表列出了作者一直关注的有趣特权,这不是详尽的,但可以作为一个很好的起点
现在我们已经知道要查找哪些特权,让我们利用Python自动检索我们正在监视的进程上启用的特权。我们将使用win32security、win32api和win32con模块。如果遇到无法加载这些模块的情况,请尝试使用ctypes库将以下所有函数转换为本机调用。
get_process_privileges函数
将以下代码添加到process_monitor.py脚本中位于现有log_to_file函数的上方:
def get_process_privileges(pid):
try:
hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid)
htok = win32security.OpenProcessToken(hproc, win32con.TOKEN_QUERY)
privs = win32security.GetTokenInformation(htok, win32security.TokenPrivileges)
privileges = ''
for priv_id, flags in privs:
if flags == (win32security.SE_PRIVILEGE_ENABLED | win32security.SE_PRIVILEGE_ENABLED_BY_DEFAULT):
priv_id += f'{win32security.LookupPrivilegeName(None, priv_id)}|'
except Exception:
privileges = 'N/A'
return privileges
我们使用进程ID来获取目标进程的句柄。接下来,我们破解进程令牌并通过发送win32security. TokenPrivileges结构来请求该进程的令牌信息。函数调用返回一个元组列表,其中元组的第一个成员是特权,第二个成员描述特权是否被启用。因为我们只关心启用的特权,所以我们首先检查启用位,然后查找该特权的人类可读名称。
修改monitor函数并运行脚本
接下来,修改现有代码以正确输出和记录此信息。将monitor函数中的“privileges = 'N/A'
”修改成“privileges = get_process_privileges(pid)
”.
现在我们已经添加了特权跟踪代码,让我们重新运行process_monitor.py脚本并检查输出。这时候我们应该看到权限信息:
可以看到,我们已经成功地记录了这些进程的已启用权限。现在,我们可以很容易地在脚本中添加一些智能化,只记录以非特权用户身份运行但启用了有趣特权的进程。这种进程监视的使用将让我们找到不安全地依赖外部文件的进程。
完整代码
完整代码如下。
import os
import sys
import win32api
import win32con
import win32security
import wmi
def get_process_privileges(pid):
try:
hproc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid)
htok = win32security.OpenProcessToken(hproc, win32con.TOKEN_QUERY)
privs = win32security.GetTokenInformation(htok, win32security.TokenPrivileges)
privileges = ''
for priv_id, flags in privs:
if flags == win32security.SE_PRIVILEGE_ENABLED | win32security.SE_PRIVILEGE_ENABLED_BY_DEFAULT:
privileges += f'{win32security.LookupPrivilegeName(None, priv_id)}|'
except Exception:
privileges = 'N/A'
return privileges
def log_to_file(message):
with open('process_monitor_log.csv', 'a') as fd:
fd.write(f'{message}\r\n')
fd.close()
def monitor():
head = 'CommandLine, Time, Executable, Parent PID, PID, User, Privileges'
log_to_file(head)
c = wmi.WMI()
process_watcher = c.Win32_Process.watch_for('creation')
while True:
try:
new_process = process_watcher()
cmdline = new_process.CommandLine
create_date = new_process.CreationDate
executable = new_process.ExecutablePath
parent_pid = new_process.ParentProcessId
pid = new_process.ProcessId
proc_owner = new_process.GetOwner()
privileges = get_process_privileges(pid)
process_log_message = (
f'{cmdline}, {create_date}, {executable},'
f'{parent_pid}, {pid}, {proc_owner}, {privileges}'
)
print(process_log_message)
print()
log_to_file(process_log_message)
except Exception:
pass
if __name__ == '__main__':
monitor()
赢得比赛
批处理、VBScript和PowerShell脚本通过自动化的任务使系统管理员的生活更轻松。例如,他们可能会不断向中央库存服务注册,或者强制从自己的存储库更新软件。一个常见的问题是对这些脚本文件缺乏适当的访问控制。在许多情况下,在其他安全的服务器上,我们发现SYSTEM用户每天运行一次的批处理或PowerShell脚本,而任何用户都可以全局写入。
如果我们在企业中运行进程监视器的时间足够长(或者您只需安装本章开头提供的示例服务),我们可能会看到如下流程记录:
我们可以看到SYSTEM进程生成了wscript.exe二进制文件并在C:\WINDOWS\TEMP\bhservice_task.vbs参数中传递。在本章开头创建的示例bhservice应该每分钟生成一次这些事件。
但是如果我们列出目录的内容,我们将看不到该文件。这是因为该服务创建一个包含VBScript的文件,然后执行并删除该VBScript。我们在许多案例中看到了商业软件执行的这种操作;通常,软件在临时位置创建文件,将命令写入文件,执行生成的程序文件,然后删除这些文件。
为了利用这种情况,我们必须有效地赢得与执行代码的竞争。当软件或计划任务创建文件时,我们需要能够在进程执行和删除文件之前将自己的代码注入到文件中。诀窍在于方便的Windows接口ReadDirectoryChangesW,它使我们能够监控目录中文件或子目录的任何更改。我们还可以过滤这些事件,以便能够确定文件何时保存。这样,我们可以在代码执行之前快速将代码注入其中。读者可能会发现,在24小时或更长的时间内监视所有临时目录是非常有用的;有时,我们会在潜在的提权之上发现有趣的漏洞或信息泄露。
构建自动注入代码
让我们从创建文件监视器开始,然后我们在此基础上构建自动注入代码。创建并打开名为file_monitor.py的脚本,并输入一下内容:
rom ctypes.wintypes import WIN32_FIND_DATAA
from importlib.resources import contents
from lib2to3.pytree import _Results
import os
import tempfile
import threading
import win32con
import win32file
FILE_CREATED = 1
FILE_DELETED = 2
FILE_MODIFIED = 3
FILE_RENAMED_FROM = 4
FILE_RENAMED_TO = 5
FILE_LIST_DIRECTORY = 0x0001
PATHS = ['c:\\WINDOWS\\Temp', tempfile.gettempdir()]
def monitor(path_to_watch):
h_directory = win32file.CreateFile(
path_to_watch,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None)
while True:
try:
results = win32file.ReadDirectoryChangesW(
h_directory,
1024,
True,
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY |
win32con.FILE_NOTIFY_CHANGE_SIZE,
None,
None)
for action, file_name in results:
full_filename = os.path.join(path_to_watch, file_name)
if action == FILE_CREATED:
print(f'[+] Created {full_filename}')
elif action == FILE_DELETED:
print(f'[-] Deleted {full_filename}')
elif action == FILE_MODIFIED:
print(f'[*] Modified {full_filename}')
try:
print('[vvv] Dumping contents ...')
with open(full_filename) as f:
contents = f.read()
print(contents)
print('[^^^] Dump complete.')
except Exception as e:
print(f'[!!!] Dump failed. {e}')
elif action == FILE_RENAMED_FROM:
print(f'[>] Renamed from {full_filename}')
elif action == FILE_RENAMED_TO:
print(f'[<] Renamed to {full_filename}')
else:
print(f'[?] Unknow action on {full_filename}')
except Exception:
pass
if __name__ == '__main__':
for path in PATHS:
monitor_thread = threading.Thread(target=monitor, args=(path,))
monitor_thread.start()
我们定义了一个要监视的目录列表,在我们的例子中是两个常见的临时文件目录。你可能想关注其他地方,所以根据你的需要编辑这个列表。
对于每个路径,我们将创建一个调用start_monitor函数的监控线程。此函数的第一个任务是获取要监视的目录的句柄。然后,我们调用ReadDirectoryChangesW函数,它在目录发生更改时通知我们。我们接收更改的目标文件的文件名和发生的事件类型。从这里,我们打印出关于该特定文件发生了什么,如果我们检测到该文件已被修改,我们将转储该文件的内容以供参考。
小试牛刀
打开cmd.exe命令行,并运行刚刚写好的脚本。
>python file_monitor.py
接着打开另一个cmd.exe命令窗口,依次执行下图中所示的指令。
这时候,在第一个cmd.exe窗口中将会打印如下图所示的信息。
如果一切按计划进行,我们建议在目标系统上保持文件监视器24小时运行。这可能会看到文件被创建、执行和删除。我们还可以使用进程监视脚本来查找要监视的其他有趣的文件路径,软件更新可能会引起特别的兴趣。
脚本运行几分钟后,发现确实temp下面后有文件生成、删除等等,如下图。
完整代码
附上完整的代码。
import os
import tempfile
import threading
import win32con
import win32file
FILE_CREATED = 1
FILE_DELETED = 2
FILE_MODIFIED = 3
FILE_RENAMED_FROM = 4
FILE_RENAMED_TO = 5
FILE_LIST_DIRECTORY = 0x0001
PATHS = ['c:\\WINDOWS\\Temp', tempfile.gettempdir()]
def monitor(path_to_watch):
h_directory = win32file.CreateFile(
path_to_watch,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None)
while True:
try:
results = win32file.ReadDirectoryChangesW(
h_directory,
1024,
True,
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY |
win32con.FILE_NOTIFY_CHANGE_SIZE,
None,
None)
for action, file_name in results:
full_filename = os.path.join(path_to_watch, file_name)
if action == FILE_CREATED:
print(f'[+] Created {full_filename}')
elif action == FILE_DELETED:
print(f'[-] Deleted {full_filename}')
elif action == FILE_MODIFIED:
print(f'[*] Modified {full_filename}')
try:
print('[vvv] Dumping contents ...')
with open(full_filename) as f:
contents = f.read()
print(contents)
print('[^^^] Dump complete.')
except Exception as e:
print(f'[!!!] Dump failed. {e}')
elif action == FILE_RENAMED_FROM:
print(f'[>] Renamed from {full_filename}')
elif action == FILE_RENAMED_TO:
print(f'[<] Renamed to {full_filename}')
else:
print(f'[?] Unknow action on {full_filename}')
except Exception:
pass
if __name__ == '__main__':
for path in PATHS:
monitor_thread = threading.Thread(target=monitor, args=(path,))
monitor_thread.start()