InjectEjectDll.exe 코드
// InjectEjectDll.exe
#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"
// Debug API를 사용하기 위해 엑세스 토큰의 privilege 변경
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, lpszPrivilege, &luid)) {
_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;
if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) {
_tprintf(L"AdjuestTokenPrivileges 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 InjectDll(DWORD dwPID, LPCWSTR szDllName) {
HANDLE hProcess = NULL, hThread = NULL;
LPVOID pRemoteBuf = NULL;
LPTHREAD_START_ROUTINE pThreadProc;
TCHAR szExePath[MAX_PATH] = {0,};
TCHAR* ptr;
if( !(hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) {
wprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
// dll 이름만 건네준 경우 (동일 디렉토리 내에 있다는 가정 아래 full path로 만들어줌)
if(!wcsrchr(szDllName, L'\\')) {
GetModuleFileName(NULL, szExePath, MAX_PATH);
ptr = wcsrchr(szExePath, L'\\');
wcscpy(ptr+1, szDllName);
szDllName = szExePath;
}
DWORD dwBufSize = (DWORD)(wcslen(szDllName)+1) * sizeof(TCHAR);
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
BOOL EjectDll(DWORD dwPID, LPCWSTR szDllName) {
HANDLE hSnapshot, hProcess, hThread;
MODULEENTRY32 me;
LPTHREAD_START_ROUTINE pThreadProc;
BOOL bFound=FALSE;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
me.dwSize = sizeof(MODULEENTRY32);
if(Module32First(hSnapshot, &me)) {
do {
// dll 모듈 경로 + 모듈 이름 둘 다 비교
if(_wcsicmp((LPCWSTR)me.szExePath, szDllName)==0 || _wcsicmp((LPCWSTR)me.szModule,szDllName)==0) {
bFound = TRUE;
break;
}
} while(Module32Next(hSnapshot, &me));
}
if(!bFound) {
CloseHandle(hSnapshot);
return FALSE;
}
if( !(hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) {
wprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);
return TRUE;
}
DWORD FindPIDFromName(LPCTSTR procName) {
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
pe.dwSize = sizeof(PROCESSENTRY32);
if(Process32First(hSnapshot, &pe)) {
do {
if(!_wcsicmp(procName, pe.szExeFile)) {
dwPID = pe.th32ProcessID;
break;
}
} while(Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return dwPID;
}
int _tmain(int argc, TCHAR* argv[]) {
if(argc!=4) {
wprintf(L"USAGE : %s [i,e] [Process Name] [Dll Name] \n", argv[0]);
return 1;
}
if(!SetPrivilege(SE_DEBUG_NAME, TRUE)) {
wprintf(L"Failed to set privilege! \n");
return 1;
}
DWORD dwPID = FindPIDFromName(argv[2]);
if(dwPID==0xFFFFFFFF) {
wprintf(L"Can't find any process from given name : <%s> !!\n", argv[2]);
return 1;
}
wprintf(L"Process found!! PID is <%d>!!\n", dwPID);
if(_wcsicmp(argv[1], L"i") == 0) {
if(InjectDll(dwPID, argv[3])) {
wprintf(L"InjectDll(\"%s\") success!!\n", argv[3]);
} else {
wprintf(L"InjectDll(\"%s\") failed!!\n", argv[3]);
}
}
else if(_wcsicmp(argv[1], L"e") == 0) {
if(EjectDll(dwPID, argv[3])) {
wprintf(L"EjectDll(\"%s\") success!!\n", argv[3]);
} else {
wprintf(L"EjectDll(\"%s\") failed!!\n", argv[3]);
}
}
else {
wprintf(L"Invalid option!! \n");
return 1;
}
return 0;
}
hook_iat.dll 코드
// hook_iat.dll
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
typedef BOOL(WINAPI* PFWRITEFILE)(HANDLE,LPCVOID,DWORD,LPDWORD,LPOVERLAPPED);
typedef LPVOID(WINAPI* PFMAPVIEWOFFILE)(HANDLE,DWORD,DWORD,DWORD,SIZE_T);
typedef HANDLE(WINAPI* PFCREATEFILEMAPPINGW)(HANDLE,LPSECURITY_ATTRIBUTES,DWORD,DWORD,DWORD,LPCWSTR);
typedef HANDLE(WINAPI* PFCREATEFILEW)(LPCWSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
// global variable
FARPROC g_pWriteOrg, g_pMapViewOrg, g_pCreateMappingOrg, g_pCreateFileOrg;
BOOL WINAPI MyWriteFile(HANDLE hFile, LPVOID lpBuffer, DWORD nBytesToWrite, LPDWORD nBytesWritten, LPOVERLAPPED lpOverlapped) {
// lpBuffer 조작
LPSTR buffer = (LPSTR)lpBuffer;
SIZE_T n = strlen(buffer);
for(SIZE_T i=0; i<n; i++) {
buffer[i] = buffer[i]^0x0f;
}
return ((PFWRITEFILE)g_pWriteOrg)(hFile,(LPCVOID)lpBuffer,nBytesToWrite,nBytesWritten,lpOverlapped);
}
LPVOID WINAPI MyMapViewOfFile(HANDLE hFileMappingObj, DWORD dwDesiredAccess, DWORD dwFileOffsetH, DWORD dwFileOffsetL, SIZE_T nBytesToMap) {
LPSTR buffer = (LPSTR)((PFMAPVIEWOFFILE)g_pMapViewOrg)(hFileMappingObj,dwDesiredAccess|FILE_MAP_WRITE,dwFileOffsetH,dwFileOffsetL,nBytesToMap);
// 리턴값 조작하기
DWORD oldProtect;
SIZE_T n;
for(n=0; buffer[n]!='\x0'; n++);
VirtualProtect(buffer, n, PAGE_READWRITE, &oldProtect);
for(SIZE_T i=0; i<n; i++) {
buffer[i] = buffer[i]^0x0f;
}
VirtualProtect(buffer, n, oldProtect, &oldProtect);
return (LPVOID)buffer;
}
HANDLE WINAPI MyCreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttr, DWORD flProtect, DWORD dwMaxSizeH, DWORD dwMaxSizeL, LPCWSTR lpName) {
// change 'page protection' of file mapping object to PAGE_READWRITE
return ((PFCREATEFILEMAPPINGW)g_pCreateMappingOrg)(hFile, lpAttr, PAGE_READWRITE, dwMaxSizeH, dwMaxSizeL, lpName);
}
HANDLE WINAPI MyCreateFileW(LPCWSTR lpFileName, DWORD dwAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpAttr, DWORD dwCreationDisposition, DWORD dwFlagsAndAttr, HANDLE hTempFile) {
// change 'file access privilege' to GENERIC_READ | GENERIC_WRITE
return ((PFCREATEFILEW)g_pCreateFileOrg)(lpFileName, dwAccess|GENERIC_WRITE, dwShareMode, lpAttr, dwCreationDisposition, dwFlagsAndAttr, hTempFile);
}
BOOL hook_iat(LPCSTR dllName, FARPROC pOrgFunc, FARPROC pNewFunc) {
HANDLE hMod;
DWORD dwRVA, dwOldProtect;
PBYTE pFile;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pIID; // DataDirectory[1]이 가리키는 배열(IDT)의 원소
PIMAGE_THUNK_DATA pITD; // INT, IAT가 가리키는 배열의 원소
// pFile = VA of mapped file
hMod = GetModuleHandle(NULL);
pFile = (PBYTE)hMod;
// pFile = VA to IMAGE_NT_HEADER
dwRVA = *((DWORD*)(pFile+0x3C));
pFile += dwRVA;
// VA to IDT(Array of IMAGE_IMPORT_DESCRIPTOR)
dwRVA = *((DWORD*)(pFile+0x80));
pIID = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA);
for( ; pIID->Name ; pIID++) {
// Library 이름 비교
szLibName = (LPCSTR)((DWORD)hMod + pIID->Name);
if(!_stricmp(dllName, szLibName)) {
// IAT 확보
pITD = (PIMAGE_THUNK_DATA)((DWORD)hMod + pIID->FirstThunk);
for( ; pITD->u1.Function ; pITD++) {
// IAT에서 pOrgFunc 발견 시 pNewFunc으로 교체
if(pITD->u1.Function == (DWORD)pOrgFunc) {
VirtualProtect(&(pITD->u1.Function), 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);
pITD->u1.Function = (DWORD)pNewFunc;
VirtualProtect(&(pITD->u1.Function), 4, dwOldProtect, &dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdReason, LPVOID lpReserved) {
HMODULE hMod;
switch(fdReason) {
case DLL_PROCESS_ATTACH:
hMod = GetModuleHandleA("kernel32.dll");
g_pWriteOrg = GetProcAddress(hMod, "WriteFile");
g_pMapViewOrg = GetProcAddress(hMod, "MapViewOfFile");
g_pCreateMappingOrg = GetProcAddress(hMod, "CreateFileMappingW");
g_pCreateFileOrg = GetProcAddress(hMod, "CreateFileW");
// hook
hook_iat("kernel32.dll", g_pWriteOrg, (FARPROC)MyWriteFile);
hook_iat("kernel32.dll", g_pMapViewOrg, (FARPROC)MyMapViewOfFile);
hook_iat("kernel32.dll", g_pCreateMappingOrg, (FARPROC)MyCreateFileMappingW);
hook_iat("kernel32.dll", g_pCreateFileOrg, (FARPROC)MyCreateFileW);
break;
case DLL_PROCESS_DETACH:
// unhook
hook_iat("kernel32.dll", (FARPROC)MyWriteFile, g_pWriteOrg);
hook_iat("kernel32.dll", (FARPROC)MyMapViewOfFile, g_pMapViewOrg);
hook_iat("kernel32.dll", (FARPROC)MyCreateFileMappingW, g_pCreateMappingOrg);
hook_iat("kernel32.dll", (FARPROC)MyCreateFileW, g_pCreateFileOrg);
break;
}
return TRUE;
}
실행 결과
- hook_iat.dll이 inject된 notepad.exe에서 저장 시 암호화되어 저장된다
- hook_iat.dll이 inject된 notepad.exe에서 암호화된 파일을 불러오면 복호화되어 보여진다 (파일 자체가 복호화된다, 복호화하여 불러오기/저장하기 동시에 되는 느낌)
IMAGE_IMPORT_DESCRIPTOR, IMAGE_THUNK_DATA 메모
// defined in winnt.h
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
// defined in winnt.h
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PULONGLONG
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
#ifdef _WIN64
typedef IMAGE_THUNK_DATA64 IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA64 PIMAGE_THUNK_DATA;
#else
typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA;
typedef PIMAGE_THUNK_DATA32 PIMAGE_THUNK_DATA;
#endif
- windows.h는 winnt.h를 포함한다
- IMAGE_THUNK_DATA의 function은 pThunk->u1.Function 으로 접근해야한다
수많은 시행착오들 (hook_iat.dll 만들며...)
1. __stdcall vs. __cdecl (WINAPI는 __stdcall)
증상: 파일을 저장하거나(WriteFile API), 열려고 하면(MapViewOfFile API) 프로그램 crash
분석: Ollydbg로 분석해보니 MyMapViewOfFile / MyWriteFile 호출 종료 후 리턴하는 주소가 이상했음
왜 그런지 살펴보니 MyWriteFile / MyMapViewOfFile 후킹 함수들과 원 API들 간의 호출 규약 차이 때문이었음.
BOOL MyWriteFile(HANDLE hFile, LPVOID lpBuffer, DWORD nBytesToWrite, LPDWORD nBytesWritten, LPOVERLAPPED lpOverlapped)
LPVOID MyMapViewOfFile(HANDLE hFileMappingObj, DWORD dwDesiredAccess, DWORD dwFileOffsetH, DWORD dwFileOffsetL, SIZE_T nBytesToMap)
- Microsoft Visual C++ / Visual Studio로 c++ 코드를 빌드할 때 사용자 정의 함수들의 호출 규약을 따로 명시하지 않으면, default calling convention은 '__cdecl'로 정해진다.
- 따라서 후킹 함수들에 'WINAPI' 키워드를 통해 __stdcall 호출 규약을 사용하도록 명시해야 한다 (후킹 함수에서 호출하는 원 window API는 __stdcall 호출규약을 이미 사용하고 있으므로)
- __stdcall: callee에서 stack cleanup / __cdecl: caller에서 stack cleanup
2. MapViewOfFile이 리턴하는 Mapped View는 READ ONLY
- Ollydbg로 MapViewOfFile이 리턴한 주소의 데이터를 조작하려 했더니 에러 메시지 뜸
- 아니나 다를까 해당 mapped 영역은 Read-only 였다
2-1. VirtualProtect를 이용해 Mapped View 영역을 READWRITE로 ?
- MyMapViewOfFile 함수 내에서 VirtualProtect(buffer, n, PAGE_READWRITE, &oldProtect);를 이용해 page protection을 바꾸려고 했지만 여전히 buffer에 값을 쓸 때 access violation error 떴다
- 바로 VirtualProtect(buffer, n, PAGE_READWRITE, &oldProtect); 가 실패해서였다
- Error code: 0x57 (ERROR_INVALID_PARAMETER) // PAGE_READWRITE를 넣을 수 없다??
2-2. VirtualProtect(buffer,n,PAGE_READWRITE,&oldProtect) 왜 실패?
- MSDN의 VirtualProtect 관련 문서에 따르면 3번째 인자 flNewProtect(메모리 보호 옵션)는 MapViewOfFile에 의해 매핑된 뷰의 경우 뷰가 매핑될 때 지정된 액세스 보호와 호환되어야 한다
- VirtualProtect function can be used only to change the protection of the memory from the initial protection set when the view was mapped
- If you create a mapped view with a specific protection level, you can only tighten the protection level, not loosen it.
- MapViewOfFile에 의해 READ_ONLY mapped view가 생성되었기 때문에 VirtualProtect를 이용해 protection을 readwrite으로 바꿀 수 (loosen) 없다
=> MyMapViewOfFile에서 원 API를 호출할 때 FILE_MAP_WRITE access까지 추가해서 전달하자
(PFMAPVIEWOFFILE)g_pMapViewOrg)(hFileMappingObj,dwDesiredAccess|FILE_MAP_WRITE,dwFileOffsetH,dwFileOffsetL,nBytesToMap)
2-3. CreateFileMappingW도 후킹 필요?
- 위와 같이 FILE_MAP_WRITE access까지 추가해서 전달하니 MapViewOfFile 함수 호출에 실패하였다
- 왜 그런지 살펴보았더니 FILE_MAP_WRITE access를 사용하려면 아래와 같은 제약이 있다
- CreateFileMapping의 세 번째 인자(flProtect)는 파일 매핑 개체의 보호를 지정하는데, 개체의 모든 매핑된 뷰는 이 보호화 와 호환되어야 한다
- 하지만 CreateFileMapping API의 세번째 인자로는 PAGE_READONLY(2)가 고정된 값으로 들어간다
=> MyCreateFileMapping 후킹 함수를 만들어 CreateFileMapping API를 후킹한다. 세번째 인자로 PAGE_READWRITE(0x4)를 넘겨주어 file mapping object를 생성한다
HANDLE WINAPI MyCreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpAttr, DWORD flProtect, DWORD dwMaxSizeH, DWORD dwMaxSizeL, LPCWSTR lpName) {
// change 'page protection' of file mapping object to PAGE_READWRITE
return ((PFCREATEFILEMAPPINGW)g_pCreateMappingOrg)(hFile, lpAttr, PAGE_READWRITE, dwMaxSizeH, dwMaxSizeL, lpName);
}
2-4. CreateFileW도 후킹 필요?
- 위 사진처럼 CreateMappingW 함수 호출에 실패해 리턴값이 0이다
- Error code=0x5로 ERROR_ACCESS_DENIED이다. 즉, 충분한 권한이 존재하지 않는다는 것이다
- 왜 그런지 살펴보니 파일 매핑 개체의 페이지 보호를 PAGE_READWRITE로 하려면 제약 사항이 존재한다
- GENERIC_READ | GENERIC_WRITE 엑세스 권한으로 생성된(CreateFile) 파일 핸들(hFile)을 매개변수로 받아야 한다
- 하지만 CreateFile의 DesiredAccess에는 고정된 값 80000000(GENERIC_READ)만 들어간다
=> MyCreateFileW 후킹 함수를 만들어 CreateFileW 후킹한다. 엑세스 권한으로 GENERIC_WRITE도 넘겨준다
HANDLE WINAPI MyCreateFileW(LPCWSTR lpFileName, DWORD dwAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpAttr, DWORD dwCreationDisposition, DWORD dwFlagsAndAttr, HANDLE hTempFile) {
// change 'file access privilege' to GENERIC_READ | GENERIC_WRITE
return ((PFCREATEFILEW)g_pCreateFileOrg)(lpFileName, dwAccess|GENERIC_WRITE, dwShareMode, lpAttr, dwCreationDisposition, dwFlagsAndAttr, hTempFile);
}
2-5. 정리하자면
- MapViewOfFile에 의해 리턴된 mapped view는 read-only이므로 버퍼 조작이 불가능하다
- CreateFileW, CreateFileMappingW, MapViewOfFile을 후킹해 엑세스/보호 권한에 READ/WRITE를 추가하면
- VirtuaProtect 호출 필요 없이 해당 주소가 writable 하므로 버퍼 수정하면 된다!
'security > 리버싱핵심원리' 카테고리의 다른 글
API 코드 패치를 이용한 API hooking - notepad.exe 프로세스 은폐하기 (2) (0) | 2023.04.03 |
---|---|
API 코드 패치를 이용한 API hooking - notepad.exe 프로세스 은폐하기 (1) (0) | 2023.04.01 |
IAT 조작하여 API 후킹 - 한글이 출력되는 계산기 (0) | 2023.03.30 |
[미완][API 후킹] 디버그(Debug) 기법을 이용한 후킹 - 메모장 암호화/복호화 (0) | 2023.03.25 |
[미완][API 후킹] 디버그(Debug) 기법을 이용한 메모장 WriteFile() 후킹 (0) | 2023.03.25 |