반응형

지뢰찾기.exe
0.06MB
hack.dll
0.04MB

 

InjectDll.exe
0.04MB
ejectDll_x86.exe
0.04MB
ejectDll_x64.exe
0.04MB

 

DLL Injection : https://sean.tistory.com/299

 

지뢰 찾기.exe 다운로드 : https://down10.software/download-minesweeper/download/

Process Explorer : https://download.sysinternals.com/files/ProcessExplorer.zip

 

ejectDll 파일 빌드 환경 : Windows 10 -> Visual Studio 2022 -> Debug x86

ejectDll_x86 실습 환경 : VirtualBox -> Windows 7 Pro 32bit

ejectDll_x64 실습 환경 : Windows 10 64bit

 

위의 ejectDll 파일의 소스 코드는 모두 "리버싱 핵심 원리" 라는 서적에서 제공된 소스 코드로 약간의 수정이 있을 수 있습니다.

 

프로그램 실습 시 주의 : ejectDll_x86 프로그램을 64bit 환경에서 실행하면 권한 설정 함수인 SetPrivilege() 함수 부분에서 에러가 발생한다.

32bit 환경에서 실행하면 잘 동작될 것이다.

 


ejectDll_x86

 

EjectDll_x86 프로그램은 32bit 운영 체제에서 동작하는 프로그램으로, hack.dll 파일을 InjectDll로 지뢰찾기.exe 프로그램에 삽입한 뒤 해당 hack.dll 파일을 빼낼 때 사용하는 프로그램이다.

 

// ejectDll_x86.exe

#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"

#define DEF_PROC_NAME	(_T("지뢰찾기.exe"))
#define DEF_DLL_NAME	(_T("hack.dll"))

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe = { 0, };

    // Get the snapshot of the system
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    } while (Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}

// 프로세스 권한 관련 설정 함수
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
    }

    if (!LookupPrivilegeValue(NULL,           // lookup privilege on local system
        lpszPrivilege,  // privilege to lookup 
        &luid))        // receives LUID of privilege
    {
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError());
        return FALSE;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if (!AdjustTokenPrivileges(hToken,
        FALSE,
        &tp,
        sizeof(TOKEN_PRIVILEGES),
        (PTOKEN_PRIVILEGES)NULL,
        (PDWORD)NULL))
    {
        _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError());
        return FALSE;
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        _tprintf(L"The token does not have the specified privilege. \n");
        return FALSE;
    }

    return TRUE;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad 프로세스 ID
    // TH32CS_SNAPMODULE 파라미터를 이용해서 지뢰찾기 프로세스에 로딩된 DLL 이름을 얻음
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for (; bMore; bMore = Module32Next(hSnapshot, &me))
    {
        _tprintf(_T("DLL Name : %s\n"), me.szModule);
        if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
        {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound)
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        _tprintf(_T("OpenProcess(%d) failed!!! [%d]\n"), dwPID, GetLastError());
        return FALSE;
    }

    hModule = GetModuleHandle(_T("kernel32.dll"));
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0,
        pThreadProc, me.modBaseAddr,
        0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

int _tmain(int argc, TCHAR* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;

    // find process
    dwPID = FindProcessID(DEF_PROC_NAME);
    if (dwPID == 0xFFFFFFFF)
    {
        _tprintf(_T("There is no <%s> process!\n"), DEF_PROC_NAME);
        return 1;
    }

    _tprintf(_T("PID of \"%s\" is %d\n"), DEF_PROC_NAME, dwPID);
    
    // change privilege
    if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
        return 1;

    // eject dll
    if (EjectDll(dwPID, DEF_DLL_NAME))
        _tprintf(_T("EjectDll(% d, \"%s\") success!!!\n"), dwPID, DEF_DLL_NAME);
    else
        _tprintf(_T("EjectDll(%d, \"%s\") failed!!!\n"), dwPID, DEF_DLL_NAME);

    return 0;
}

 


ejectDll_x64

 

EjectDll_x64 프로그램은 64bit 운영 체제에서 동작하는 프로그램으로, hack.dll 파일을 InjectDll로 지뢰찾기.exe 프로그램에 삽입한 뒤 해당 hack.dll 파일을 빼낼 때 사용하는 프로그램이다.

 

// ejectDll_x64.exe

#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"

#define DEF_PROC_NAME	(_T("지뢰찾기.exe"))
#define DEF_DLL_NAME	(_T("hack.dll"))

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe = { 0, };

    // Get the snapshot of the system
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    } while (Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad 프로세스 ID
    // TH32CS_SNAPMODULE 파라미터를 이용해서 지뢰찾기 프로세스에 로딩된 DLL 이름을 얻음
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for (; bMore; bMore = Module32Next(hSnapshot, &me))
    {
        _tprintf(_T("DLL Name : %s\n"), me.szModule);
        if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
        {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound)
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        _tprintf(_T("OpenProcess(%d) failed!!! [%d]\n"), dwPID, GetLastError());
        return FALSE;
    }

    hModule = GetModuleHandle(_T("kernel32.dll"));
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0,
        pThreadProc, me.modBaseAddr,
        0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

int _tmain(int argc, TCHAR* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;

    // find process
    dwPID = FindProcessID(DEF_PROC_NAME);
    if (dwPID == 0xFFFFFFFF)
    {
        _tprintf(_T("There is no <%s> process!\n"), DEF_PROC_NAME);
        return 1;
    }

    _tprintf(_T("PID of \"%s\" is %d\n"), DEF_PROC_NAME, dwPID);

    // eject dll
    if (EjectDll(dwPID, DEF_DLL_NAME))
        _tprintf(_T("EjectDll(% d, \"%s\") success!!!\n"), dwPID, DEF_DLL_NAME);
    else
        _tprintf(_T("EjectDll(%d, \"%s\") failed!!!\n"), dwPID, DEF_DLL_NAME);

    return 0;
}

지뢰찾기.exe에 InjectDll로 hack.dll 파일 삽입하고 ejectDll.exe 프로그램으로 Ejection 하기

 

Windows 7 32bit 환경

 

먼저, 위와 같이 hack.dll 파일을 C 드라이브에 위치시킨다.(hack.dll 파일 복붙)

(소스코드를 보면 알겠지만, C 드라이브 밑에 있는 hack.dll 파일을 대상으로 하기 때문이다.)

 

 

그리고 InjectDll과 ejectDll_x86 그리고 지뢰찾기.exe 파일을 한 폴더(폴더명 : work)에 넣어둔다.

 

 

지뢰찾기.exe 파일을 실행하고, Process Explorer에서 지뢰찾기.exe 프로세스의 PID를 확인한다.

(위의 사진을 참고하면 PID는 100이다.)

 

 

 

Process Explorer -> View -> Lower Pane View -> DLLs 선택하여 DLL 파일들을 볼 수 있게 설정

 

 

지뢰찾기.exe 프로세스를 선택하면 위와 같이 지뢰찾기.exe 프로세스 내에 포함된 DLL 파일들이 출력되는데, 현재는 hack.dll 파일이 삽입되지 않았다.

 

 

CMD를 관리자 모드로 열어 work 폴더(위에서 실행 파일들을 한 곳에 모아둔 폴더)로 이동 후 InjectDll.exe 파일을 실행하여 Injection을 수행한다.

 

그러면 위와 같이 지뢰찾기.exe 프로세스에 hack.dll 파일이 삽입되었다.

 

 

다시 CMD로 돌아와 위와 같이 ejectDll_x86.exe 프로그램을 실행하면 Ejection에 성공했다고 메시지가 뜬다.

 

그리고 ProcessExplorer에서 지뢰찾기.exe 프로세스의 DLL 파일 리스트를 보면 위와 같이 사라진 것을 확인할 수 있다.

 

 

Windows 10 64bit 환경

 

 

먼저, 위와 같이 hack.dll 파일을 C 드라이브에 위치시킨다.(hack.dll 파일 복붙)

(소스코드를 보면 알겠지만, C 드라이브 밑에 있는 hack.dll 파일을 대상으로 하기 때문이다.)

 

 

그리고 InjectDll과 ejectDll_x64 그리고 지뢰찾기.exe 파일을 한 폴더(폴더명 : work)에 넣어둔다.

 

 

지뢰찾기.exe 파일을 실행하고, Process Explorer에서 지뢰찾기.exe 프로세스의 PID를 확인한다.

(위의 사진을 참고하면 PID는 21184이다.)

 

 

Process Explorer -> View -> Lower Pane View -> DLLs 선택하여 DLL 파일들을 볼 수 있게 설정

 

 

 

지뢰찾기.exe 프로세스를 선택하면 위와 같이 지뢰찾기.exe 프로세스 내에 포함된 DLL 파일들이 출력되는데, 현재는 hack.dll 파일이 삽입되지 않았다.

 

 

powershell을 열어 work 폴더(위에서 실행 파일들을 한 곳에 모아둔 폴더)로 이동 후 InjectDll.exe 파일을 실행하여 Injection을 수행한다.

 

그러면 위와 같이 지뢰찾기.exe 프로세스에 hack.dll 파일이 삽입되었다.

 

 

다시 powershell로 돌아와 위와 같이 ejectDll_x64.exe 프로그램을 실행하면 Ejection에 성공했다고 메시지가 뜬다.

 

그리고 ProcessExplorer에서 지뢰찾기.exe 프로세스의 DLL 파일 리스트를 보면 위와 같이 사라진 것을 확인할 수 있다.

 


코드 분석

 

코드 분석은 권한 설정 관련 함수까지 포함된 ejectDll_x86의 소스코드를 기반으로 진행한다.

 

int _tmain(int argc, TCHAR* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;

    // find process
    dwPID = FindProcessID(DEF_PROC_NAME);
    if (dwPID == 0xFFFFFFFF)
    {
        _tprintf(_T("There is no <%s> process!\n"), DEF_PROC_NAME);
        return 1;
    }

    _tprintf(_T("PID of \"%s\" is %d\n"), DEF_PROC_NAME, dwPID);
    
    // change privilege
    if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
        return 1;

    // eject dll
    if (EjectDll(dwPID, DEF_DLL_NAME))
        _tprintf(_T("EjectDll(% d, \"%s\") success!!!\n"), dwPID, DEF_DLL_NAME);
    else
        _tprintf(_T("EjectDll(%d, \"%s\") failed!!!\n"), dwPID, DEF_DLL_NAME);

    return 0;
}

 

main()과 _tmain()의 차이점은 https://sean.tistory.com/13 에서 확인한다.

 

1. dwPID 변수의 값을 0xFFFFFFFF으로 초기화한다.

 

2. FindProcessID() 함수를 호출하는데, hack.dll 파일이 삽입되어 있는 프로세스의 이름을 인자로 넘기고, FindProcessID() 함수의 반환값이 0xFFFFFFFF이면 종료한다.

 

3. FindProcessID() 함수가 성공적으로 수행됐다면 프로세스 이름과 PID를 출력한다.

 

4. SetPrivilege() 함수를 호출하는데, SE_DEBUG_NAME을 인자로 넘기고, 반환값이 0이면 프로그램을 종료한다.

 

5. EjectDll() 함수를 호출하는데, PID 값과 Ejection 할 dll 파일의 이름을 인자로 넘기고, 반환값이 1이면 Ejection에 성공, 반환값이 0이면 Ejection에 실패이다.

 


FindProcessID()

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe = { 0, };

    // Get the snapshot of the system
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

    // find process
    Process32First(hSnapShot, &pe);
    do
    {
        if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    } while (Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}
// https://learn.microsoft.com/ko-kr/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];
} PROCESSENTRY32;

 

1. PID가 담길 변수, CreateToolhelp32Snapshot() 함수의 반환값이 담길 변수, PROCESSENTRY32 구조체 변수까지 하여 총 3개의 변수가 선언되고 각각 초기화된다.

 

 

2. 구조체 변수 pe의 멤버 변수인 dwSize에 PROCESSENTRY32 구조체의 크기만큼 초기화한다.
(이 작업을 안해주면 Process32First() 함수 호출에 실패한다.)

 

3. CreatToolhelp32Snapshot() 함수를 호출하고 반환값을 hSnapshot 변수에 담는다.

 

4. Process32First() 함수를 호출하여 스냅샷 목록에서 첫 번째 프로세스 정보를 가져온다.

 

5. 인자로 넘어온 hack.dll 파일이 삽입되어 있는 프로세스의 이름과 pe.szExeFile 멤버 변수의 이름과 비교하여 일치하면 dwPID 변수에 해당 프로세스의 PID를 담고 반복문을 나온 뒤 종료한다.
일치하지 않으면 Process32Next() 함수를 호출하여 다음 프로세스 정보를 가져와서 같은지 비교한다.

 


SetPrivilege()

// 프로세스 권한 관련 설정 함수
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
    }

    if (!LookupPrivilegeValue(NULL,           // lookup privilege on local system
        lpszPrivilege,  // privilege to lookup 
        &luid))        // receives LUID of privilege
    {
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError());
        return FALSE;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if (!AdjustTokenPrivileges(hToken,
        FALSE,
        &tp,
        sizeof(TOKEN_PRIVILEGES),
        (PTOKEN_PRIVILEGES)NULL,
        (PDWORD)NULL))
    {
        _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError());
        return FALSE;
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        _tprintf(L"The token does not have the specified privilege. \n");
        return FALSE;
    }

    return TRUE;
}

 

SetPrivilege 함수는 https://learn.microsoft.com/ko-kr/windows/win32/secauthz/enabling-and-disabling-privileges-in-c-- 에서 제공하는 함수로 프로세스 권한과 관련된 설정을 하는 함수 예제이다.

 

1. OpenProcessToken() 함수로 프로세스의 토큰 핸들을 얻는다.

여기서 얻은 핸들을 통해 권한을 변경한다.

BOOL OpenProcessToken(
  [in]  HANDLE  ProcessHandle, // 접근하고자 하는 프로세스의 핸들
  [in]  DWORD   DesiredAccess, // 토큰에 접근을 위한 접근 권한 설정
  [out] PHANDLE TokenHandle // 토큰 핸들
);

 

2. GetCurrentProcess() 함수로 현재 실행되고 있는 프로세스(이 함수를 호출한 프로세스)의 핸들을 반환값을 통해 얻는다.

(하지만 이 함수로 구해지는 핸들은 의사(가짜) 핸들로, 핸들 테이블에 등록되지 않은 핸들이며, 실제 반환값은 현재 실행 중인 프로세스를 참조하기 위한 용도로 정의해 놓은 상수값이다.)

HANDLE GetCurrentProcess();

 

즉, 1번과 2번 내용을 합치면 현재 실행 중인 eject_x86.exe 프로세스의 핸들에 가져와 접근 권한을 설정하고 토큰 핸들을 가져오는 것이다.

 

3. LookupPrivilegeValue() 함수로 로컬 시스템에서 권한 상수에 해당하는 LUID를 검색하는 함수로, 이 LUID는 특정 권한을 표현하는 구조체이다.

 

LookupPrivilegeValue() 함수의 두 번째 인자는 lpszPrivilege인데, lpszPrivilege는 매개변수 명이고, 인자로 넘어온 값이 SE_DEBUG_NAME이니 즉, 다른 계정이 소유한 프로세스의 메모리를 디버그하고 조정하는 데 필요한 권한에 대한 상수로써 시스템 레벨 디버깅을 위한 권한의 상수값이며 로컬 관리자 계정에만 주어지는 권한이다.

 

LookupPrivilegeValue() 함수 호출에 실패하면 false를 반환하고 프로그램을 종료하는데, 함수 호출에 성공하고 이 프로세스가 권한을 사용할 수 있으면 권한을 사용하도록 tp 구조체 변수의 멤버들에 값을 세팅한다.

BOOL LookupPrivilegeValueA(
  [in, optional] LPCSTR lpSystemName, // 명시된 특권을 찾기 위한 시스템 이름
  [in]           LPCSTR lpName, // 특권 이름
  [out]          PLUID  lpLuid // 검색된 LUID
);

 

// https://learn.microsoft.com/ko-kr/windows/win32/secauthz/privilege-constants?redirectedfrom=MSDN
// Process와 Thread가 가질 수 있는 권한에 대해 64bit 크기의 식별자이고, Access Token의 정보에 포함된다.
typedef struct _LUID { 
   DWORD LowPart;
   LONG  HighPart; 
} LUID, *PLUID;

#define SE_CREATE_TOKEN_NAME              TEXT("SeCreateTokenPrivilege")
#define SE_ASSIGNPRIMARYTOKEN_NAME        TEXT("SeAssignPrimaryTokenPrivilege")
#define SE_LOCK_MEMORY_NAME               TEXT("SeLockMemoryPrivilege")
#define SE_INCREASE_QUOTA_NAME            TEXT("SeIncreaseQuotaPrivilege")
#define SE_UNSOLICITED_INPUT_NAME         TEXT("SeUnsolicitedInputPrivilege")
#define SE_MACHINE_ACCOUNT_NAME           TEXT("SeMachineAccountPrivilege")
#define SE_TCB_NAME                       TEXT("SeTcbPrivilege")
#define SE_SECURITY_NAME                  TEXT("SeSecurityPrivilege")
#define SE_TAKE_OWNERSHIP_NAME            TEXT("SeTakeOwnershipPrivilege")
#define SE_LOAD_DRIVER_NAME               TEXT("SeLoadDriverPrivilege")
#define SE_SYSTEM_PROFILE_NAME            TEXT("SeSystemProfilePrivilege")
#define SE_SYSTEMTIME_NAME                TEXT("SeSystemtimePrivilege")
#define SE_PROF_SINGLE_PROCESS_NAME       TEXT("SeProfileSingleProcessPrivilege")
#define SE_INC_BASE_PRIORITY_NAME         TEXT("SeIncreaseBasePriorityPrivilege")
#define SE_CREATE_PAGEFILE_NAME           TEXT("SeCreatePagefilePrivilege")
#define SE_CREATE_PERMANENT_NAME          TEXT("SeCreatePermanentPrivilege")
#define SE_BACKUP_NAME                    TEXT("SeBackupPrivilege")
#define SE_RESTORE_NAME                   TEXT("SeRestorePrivilege")
#define SE_SHUTDOWN_NAME                  TEXT("SeShutdownPrivilege")
#define SE_DEBUG_NAME                     TEXT("SeDebugPrivilege")
#define SE_AUDIT_NAME                     TEXT("SeAuditPrivilege")
#define SE_SYSTEM_ENVIRONMENT_NAME        TEXT("SeSystemEnvironmentPrivilege")
#define SE_CHANGE_NOTIFY_NAME             TEXT("SeChangeNotifyPrivilege")
#define SE_REMOTE_SHUTDOWN_NAME           TEXT("SeRemoteShutdownPrivilege")
#define SE_UNDOCK_NAME                    TEXT("SeUndockPrivilege")
#define SE_SYNC_AGENT_NAME                TEXT("SeSyncAgentPrivilege")
#define SE_ENABLE_DELEGATION_NAME         TEXT("SeEnableDelegationPrivilege")
#define SE_MANAGE_VOLUME_NAME             TEXT("SeManageVolumePrivilege")
#define SE_IMPERSONATE_NAME               TEXT("SeImpersonatePrivilege")
#define SE_CREATE_GLOBAL_NAME             TEXT("SeCreateGlobalPrivilege")
#define SE_TRUSTED_CREDMAN_ACCESS_NAME    TEXT("SeTrustedCredManAccessPrivilege")
#define SE_RELABEL_NAME                   TEXT("SeRelabelPrivilege")
#define SE_INC_WORKING_SET_NAME           TEXT("SeIncreaseWorkingSetPrivilege")
#define SE_TIME_ZONE_NAME                 TEXT("SeTimeZonePrivilege")
#define SE_CREATE_SYMBOLIC_LINK_NAME      TEXT("SeCreateSymbolicLinkPrivilege")

 

4. AdjustTokenPrivileges() 함수는 지정된 액세스 토큰에서 권한을 사용하거나 사용하지 않도록 설정하는 함수인데, 쉽게 말해 세팅한 권한을 실제로 목표로 하는 프로세스에 적용시키는 것이다.

 

AdjustTokenPrivileges() 함수의 반환값이 ERROR_NOT_ALL_ASSIGNED 이라면 토큰에 newState 맴개 변수에 지정된 하나 이상의 권한이 없을 때나 권한이 조정되지 않았을 때이며, PreviousStatus 매개 변수는 조정된 권한을 나타낸다고 한다.

 

즉, 권한이 없거나 조정되지 않았다면 AdjustTokenPrivileges() 함수는 ERROR_NOT_ALL_ASSIGNED를 반환하고, 그러면 SetPrivilege 함수는 FALSE를 반환하고 종료한다.

BOOL AdjustTokenPrivileges(
  [in]            HANDLE            TokenHandle, // 토큰 핸들
  [in]            BOOL              DisableAllPrivileges, // 명시된 특권을 0과 1로 표현하여 disable / enable을 결정하낟.
  [in, optional]  PTOKEN_PRIVILEGES NewState, // 새로 설정할 권한
  [in]            DWORD             BufferLength, // PreviousState의 버서 사이즈
  [out, optional] PTOKEN_PRIVILEGES PreviousState, // 이전 권한을 저장
  [out, optional] PDWORD            ReturnLength // 이전 권한의 사이즈를 저장
);

 


EjectDll

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad 프로세스 ID
    // TH32CS_SNAPMODULE 파라미터를 이용해서 지뢰찾기 프로세스에 로딩된 DLL 이름을 얻음
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for (; bMore; bMore = Module32Next(hSnapshot, &me))
    {
        _tprintf(_T("DLL Name : %s\n"), me.szModule);
        if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
        {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound)
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        _tprintf(_T("OpenProcess(%d) failed!!! [%d]\n"), dwPID, GetLastError());
        return FALSE;
    }

    hModule = GetModuleHandle(_T("kernel32.dll"));
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0,
        pThreadProc, me.modBaseAddr,
        0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

 

실제로 Ejection이 진행될 함수이다.

 

1. CreateToolhelp32Snapshot() 함수를 이용해 프로세스에 로딩된 DLL 정보를 가져온다.

(TH32CS_SNAPMODULE 파라미터를 이용하면 지뢰찾기 프로세스에 로딩된 DLL 이름을 얻을 수 있다.)

 

그리고  CreateToolhelp32Snapshot() 함수의 반환값이 담긴 hSnapshot 핸들을 Module32First() 함수와 Module32Next() 함

수에 넘겨주면 MODULEENTRY32 구조체에 해당 모듈의 정보가 세팅된다.

 

아래는 MODULEENTRY32 구조체의 정의인데, szModule 멤버는 DLL 파일의 이름이고, modBaseAddr 멤버가 해당 DLL이 로딩된 주소(프로세스 가상 메모리)이다.

 

szModule의 값과 Ejection을 원하는 DLL 파일의 이름을 비교하면 정확한 모듈 정보를 찾을 수 있다.

// https://learn.microsoft.com/ko-kr/windows/win32/api/tlhelp32/ns-tlhelp32-moduleentry32
typedef struct tagMODULEENTRY32 {
  DWORD   dwSize;
  DWORD   th32ModuleID; // This module
  DWORD   th32ProcessID; // owning process
  DWORD   GlblcntUsage; // Global usage count on the module
  DWORD   ProccntUsage; // Module usage count in th32ProcessID's context
  BYTE    *modBaseAddr; //Base address of module in th32ProcessID's context
  DWORD   modBaseSize; // Size in bytes of module starting at modBaseAddr
  HMODULE hModule; // The hModule of this module in th32ProcessID's context
  char    szModule[MAX_MODULE_NAME32 + 1];
  char    szExePath[MAX_PATH];
} MODULEENTRY32;

 

2. OpenProcess() 함수로 Ejection 할 대상 프로세스(지뢰찾기)의 핸들을 구한다.

 

 

3. GetModuleHandle() 함수와 GetProcAddress() 함수를 이용해 DLL 파일 안에 있는 FreeLibrary() 함수의 주소를 알아낸다.

 

다만, 주의할 점은 지뢰찾기.exe 프로세스의 kernel32.dll 안에 있는 FreeLibrary() 함수가 아니라 ejectDll_x86.exe 프로세스에 로딩된 kernel32.dll 안에 있는 FreeLibrary() 함수의 주소를 얻어온다.

 

 

4.  CreateRemoteThread() 함수로 대상 프로세스(지뢰찾기.exe)에 스레드를 실행시킨다.

 

CreateRemoteThread() 함수의 스레드 함수를 적는 매개변수에는 FreeLibrary() 함수의 주소이고, 

CreateRemoteThread() 함수의 스레드 함수의 매개변수를 적는 매개변수에는 me.modBaseAddr 값을 주어 Ejection 하길 원하는 DLL의 로딩 주소를 적는다.

 

즉, 스레드 함수로 FreeLibrary() 함수를 지정하고 스레드 파라미터에 DLL 로딩 주소를 넘겨주면, 결국 대상 프로세스에서는 FreeLibrary() API가 성공적으로 호출된다.

 


토큰의 권한 변경 내용 : https://learn.microsoft.com/ko-kr/windows/win32/secbp/changing-privileges-in-a-token?redirectedfrom=MSDN 

 

권한 사용 및 사용 안함 예제 코드 : https://learn.microsoft.com/ko-kr/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--

 

Windows 권한 문제 파일

Openprocess 권한 문제.pdf
0.59MB

 

LookupPrivilegeValueA() 함수 : https://learn.microsoft.com/ko-kr/windows/win32/api/winbase/nf-winbase-lookupprivilegevaluea

 

AdjustTokenPrivileges() 함수 및 ERROR_NOT_ALL_ASSIGNED 반환값 : https://learn.microsoft.com/ko-kr/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges

 

프로세스 메모리 읽어오기 : https://hwainoreversing-17.tistory.com/23

 

GetCurrentProcess() : 현재 실행되고 있는 프로세스(이 함수를 호출한 프로세스)의 핸들을 반환값을 통해 얻는다.

(하지만 이 함수로 구해지는 핸들은 가짜 핸들로, 핸들 테이블에 등록되지 않은 핸들이며, 반환값은 현재 실행 중인 프로세스를 참조하기 위한 용도로 정의해 놓은 상수값이다.)

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess

 

DuplicateHandle() : 현재 프로세스의 진짜 핸들을 얻고 싶을 때 이 함수를 사용한다.

HANDLE hProcess;
 
DuplicateHandle(GetCurrentProcess(),GetCurrentProcess(),GetCurrentProcess(),&hProcess,0,TRUE,DUPLICATE_SAME_ACCESS);

 

권한 상승 코드 분석 : https://jeep-shoes.tistory.com/11

 

PROCESSENTRY32 구조체 : https://learn.microsoft.com/ko-kr/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32

반응형

+ Recent posts