본문 바로가기

카테고리 없음

2024.8.15 리버싱 공부(나뭇잎 책)

Code Injection With asm

 

어셈블리를 이용해 Code Injection을 한다.

 

 

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

#define DEF_PROC_NAME (L"conso.exe")
#define DEF_DLL_NAME (L"C:\\Users\\a0102\\source\\repos\\Dll1\\x64\\Release\\Dll1.dll")


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

    // 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;
}

typedef struct _THREAD_PARAM
{
    FARPROC pFunc[2];               // LoadLibraryA(), GetProcAddress()
} THREAD_PARAM, * PTHREAD_PARAM;

BYTE g_InjectionCode[] =
{
 0x53, 0x48, 0x89, 0xCB, 0x48, 0xC7, 0xC0,
0x2E, 0x64, 0x6C, 0x6C, 0x50, 0x48, 0xB8, 0x6B, 0x65, 0x72, 0x6E, 0x65, 0x6C, 0x33, 0x32, 0x50,
0x48, 0x89, 0xE1, 0x48, 0x83, 0xEC, 0x20, 0xFF, 0x13, 0x48, 0x83, 0xC4, 0x20, 0x48, 0x89, 0xC1,
0x48, 0xC7, 0xC0, 0x61, 0x72, 0x79, 0x41, 0x50, 0x48, 0xB8, 0x4C, 0x6F, 0x61, 0x64, 0x4C, 0x69,
0x62, 0x72, 0x50, 0x48, 0x89, 0xE2, 0x48, 0x81, 0xEC, 0x00, 0x01, 0x00, 0x00, 0xFF, 0x53, 0x08,
0x48, 0x81, 0xC4, 0x00, 0x01, 0x00, 0x00, 0x48, 0x89, 0xC3, 0x48, 0xC7, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x50, 0x48, 0xB8, 0x44, 0x6C, 0x6C, 0x31, 0x2E, 0x64, 0x6C, 0x6C, 0x50, 0x48, 0x89, 0xE1,
0x48, 0x83, 0xEC, 0x20, 0xFF, 0xD3, 0x48, 0x83, 0xC4, 0x20, 0x48, 0x83, 0xC4, 0x30, 0x5B, 0xC3
};

BOOL InjectCode(DWORD dwPID)
{
    HMODULE         hMod = NULL;
    THREAD_PARAM    param = { 0, };
    HANDLE          hProcess = NULL;
    HANDLE          hThread = NULL;
    LPVOID          pRemoteBuf[2] = { 0, };

    hMod = GetModuleHandleA("kernel32.dll");

    // set THREAD_PARAM
    param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
    param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");

    // Open Process
    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,               // dwDesiredAccess
        FALSE,                            // bInheritHandle
        dwPID)))                         // dwProcessId
    {
        printf("OpenProcess() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    // Allocation for THREAD_PARAM
    if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess,                  // hProcess
        NULL,                      // lpAddress
        sizeof(THREAD_PARAM),      // dwSize
        MEM_COMMIT,                // flAllocationType
        PAGE_READWRITE)))         // flProtect
    {
        printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    if (!WriteProcessMemory(hProcess,                               // hProcess
        pRemoteBuf[0],                          // lpBaseAddress
        (LPVOID)&param,                         // lpBuffer
        sizeof(THREAD_PARAM),                   // nSize
        NULL))                                 // [out] lpNumberOfBytesWritten
    {
        printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    // Allocation for ThreadProc()
    if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess,                  // hProcess
        NULL,                      // lpAddress
        sizeof(g_InjectionCode),   // dwSize
        MEM_COMMIT,                // flAllocationType
        PAGE_EXECUTE_READWRITE))) // flProtect
    {
        printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    if (!WriteProcessMemory(hProcess,                               // hProcess
        pRemoteBuf[1],                          // lpBaseAddress
        (LPVOID)&g_InjectionCode,               // lpBuffer
        sizeof(g_InjectionCode),                // nSize
        NULL))                                 // [out] lpNumberOfBytesWritten
    {
        printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    printf("%p\n", pRemoteBuf[0]);
    printf("%p\n", pRemoteBuf[1]);
    if (!(hThread = CreateRemoteThread(hProcess,                    // hProcess
        NULL,                        // lpThreadAttributes
        0,                           // dwStackSize
        (LPTHREAD_START_ROUTINE)pRemoteBuf[1],
        pRemoteBuf[0],               // lpParameter
        0,                           // dwCreationFlags
        NULL)))                     // lpThreadId
    {
        printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

int main(int argc, char* argv[])
{
    DWORD dwPID = 0;


    // code injection    
    dwPID = (DWORD)FindProcessID(DEF_PROC_NAME);
    printf("%d\n", dwPID);
    InjectCode(dwPID);

    return 0;
}

 

함수를 설명하면

Main

Process 이름으로 PID를 찾고 InjectCode에 인자로 넣고 호출

 

InjectCode

인자로 넣어줄 메모리 1개와 코드 부분 메모리 1개를 할당해서

 

인자로 LoadLibrary와 GetProceAddress 주소를 넣어주고

코드 부분에는 만들어둔 어셈블리를 넣어주고 원격스레드로 호출한다.

 

 

 

어셈블리 설명

나의 CPU에서는 이런식으로 함수를 호출하길래 예제랑 다르게 바꿔보았다.

 

이 어셈블리의 목적은

 

loadlibaray를 사용해서 kenrel32.dll 로딩 하고
getprocaddress사용해서 loadlibrary 얻고
loadlibrary 사용해서 Dll1.dll 로딩

 

다른것도 할 수 있지만 간단하게 위의 기능을 구현했다.

 

구현하기 위해 함수를 분석했다.

 

원격 스레드를 호출하면 rbx에 인자가 들어온다.

loadlibrary는 [rbx]에 있고 인는 rcx에 문자열 주소를 넣어준다.

그리고 안에서 스텍을 약 20정도 사용해서 호출 전에 할당해주고 호출 후없애준다.

 

getprocaddress는 [rbx+8]에 있고 인자 dll헨들은 rcx에,  문자 주소는 rdx에 넣고 호출한다.

함수 안에서 스텍을 약 100정도 사용해서 호출전에 할당해주고 호출 후 없애준다.

 

문자열을 나는 스택에 저장했는데,

 

kernel32.dll
6b65726e656c3332 2e646c6c

LoadLibraryA
4c6f61644c69627261727941
Dll1.dll
446c6c312e646c6c

 

인데 스택에 넣기때문에 뒤에서부터 push해준다
kernerl32.dll의 경우 6c6c642e ,32336c656e72656b

 

이런식으로 데이터를 코드에 삽입할 수 있다.

 

 

 

 

위 방식의 어셈블리는 문자열을 스택에 집어넣는 방식으로 했지만

 

이처럼 코드 자체를 문자열로 사용할 수도 있다.

call을 하면 return 주소로 그 아래 부분이 스택에 push되기 떄문에

call 다음바이트 부터 문자열을 입력하고 문자열이 끝나는 부분의 주소를 call해주면 

call 다음 바이트 주소는 스택에 들어가고 문자열은 점프하기 때문에

인자인 rcx에 [rsp]를 넣어주면 rcx에 문자열의 주소가 들어가게 된다.