Windows 남의 프로그램에 코드 올리기
2022년 4월 2일
서론 #
어떤 프로세스의 메모리를 마음대로 조작하기 위해서는 관리자 권한을 사용하거나 프로세스 자기자신이여야 한다.
메모리 조작을 통해 간단한 패치를 할 수도 있고, 악의적인 행동을 수행하도록 할수도 있다.
그저 메모리를 조작하기 위해서라면 메모리 접근 권한만 필요하지만, 조작 대상 프로세스의 실행 흐름을 변경하려면 프로세스의 내부에 코드를 삽입할 필요가 있다.
사실 외부 코드로 실행흐름을 변경하는 External Hook 도 있지만 구현하기 어렵다.
이때 DLL Injection이나 Code Injection을 사용한다.
코드를 올리는 과정에서도 대상 프로세스의 메모리 조작이 일어나기 때문에 당연하게도 관리자 권한이 필요하지만 핵이나 버그 패치나 자기 자신을 위해 사용하는 프로그램은 실행하려는 사람이 임의로 관리자 권한을 얻기위해 버튼 한번만 눌러주면 되기 때문에 문제되지 않는다.
- 시스템별 인젝션방법 : https://ch4njun.tistory.com/141
DLL Injection #
AppInit_DLLs #
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
윈도우에서 기본으로 제공하는 기능 중 레지스트리의 AppInit_DLLs 값에 인젝션을 원하는 DLL의 경로를 적고 재부팅하면 앞으로 실행될 모든 프로세스에 DLL을 로딩시킬 수 있다.
정확히 말하면 user32.dll이 로드된 모든 프로세스에 인젝트 되는것이고 LoadAppInit_DLLs 값도 1로 세팅되어야 동작한다.
※ 수정을 위해서라면 프로그램이 레지스트리 편집 권한(관리자 권한)을 가지고있어야하고, 재부팅후 적용된다는 단점이 있다.
1#include <stdio.h>
2#include <Windows.h>
3
4int main()
5{
6 LONG lResult;
7 HKEY hKey;
8 DWORD dwType;
9 DWORD dwBytes = 100;
10 char buffer[100];
11 char *dll_path = "C:\\users\\kdh\\desktop\\test.dll";
12
13 // 레지스트리 키(디렉터리) 오픈
14 lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
15 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
16 0, KEY_ALL_ACCESS|KEY_WOW64_64KEY, &hKey);
17 if (lResult != ERROR_SUCCESS)
18 MessageBox(NULL, "Register Open Error", "Error", MB_OK);
19
20 // 레지스트리 값 변경
21 RegSetValueEx(hKey, "AppInit_DLLs", 0, REG_SZ, (BYTE*)dll_path, strlen(dll_path));
22
23 // 레지스트리 값 읽기
24 lResult = RegQueryValueExA(hKey, "AppInit_DLLs", 0, &dwType, (LPBYTE)buffer, &dwBytes);
25 if (lResult == ERROR_SUCCESS)
26 MessageBox(NULL, buffer, "Registery", MB_OK);
27 else
28 MessageBox(NULL, "Registry Read Error", "Error", MB_OK);
29
30 RegCloseKey(hKey);
31 system("pause");
32 return (0);
33}
PE 패치로 DLL로딩 #
CreateRemoteThread() 를 이용 #
kernel32.dll의 로딩 주소가 동일하다는 가정으로 사용하는 방법이다.
동일하지 않아도 LoadLibrary 주소를 찾는 방법이 있을까?
1#include <stdio.h>
2#include <stdlib.h>
3#include <windows.h>
4#include <tlhelp32.h>
5
6#define DEF_PROC_NAME ("testprogram.exe")
7#define DEF_DLL_PATH ("C:\\InjectedDll.dll")
8
9DWORD FindProcessID(LPCTSTR szProcessName);
10BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath);
11
12int main(int argc, char *argv[]) {
13 DWORD dwPID = -1;
14 dwPID = FindProcessID(DEF_PROC_NAME);
15 if (dwPID == -1)
16 printf("there is no <%s> process! \n", DEF_PROC_NAME);
17 else
18 InjectDll(dwPID, DEF_DLL_PATH);
19 system("pause");
20 return 0;
21}
22
23// 프로세스 이름으로 PID 가져오는 함수
24DWORD FindProcessID(LPCTSTR szProcessName)
25{
26 DWORD dwPID = -1;
27 HANDLE hSnapShot = INVALID_HANDLE_VALUE;
28 PROCESSENTRY32 pe;
29
30 pe.dwSize = sizeof( PROCESSENTRY32 );
31 hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );
32
33 Process32First(hSnapShot, &pe);
34 do
35 {
36 if (!_stricmp(szProcessName, pe.szExeFile))
37 {
38 dwPID = pe.th32ProcessID;
39 break;
40 }
41 } while (Process32Next(hSnapShot, &pe));
42
43 CloseHandle(hSnapShot);
44 return (dwPID);
45}
46
47BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
48{
49 HANDLE hProcess, hThread;
50 HMODULE hMod;
51 LPVOID pRemoteBuf;
52 DWORD dwBufSize = lstrlen(szDllPath) + 1;
53 LPTHREAD_START_ROUTINE pThreadProc;
54
55 // 1. 프로세스 전체 접근권한을 가진 핸들 얻기
56 if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
57 return (FALSE);
58
59 // 2. 대상 프로세스의 메모리 접근권한 얻고 인젝션할 Dll 경로를 적는다
60 // 경로가 작성된 주소는 pRemoteBuf 에 저장된다. (대상 프로세스의 메모리공간 주소)
61 pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
62 WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
63
64 // 3. LoadLibraryA() API 주소를 구한다
65 hMod = GetModuleHandle("kernel32.dll");
66 pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");
67
68 // 4. 프로세스에 스레드를 실행
69 // pThreadProc: LoadLibraryA() , pRemoteBuf: DLL_PATH in TargetProcess
70 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
71
72 return (TRUE);
73}
LoadLibraryA() 함수의 주소를 구할때 인젝터의 메모리공간에서 kernel32.dll 모듈의 주소를 찾는 코드로 작성되어 이상하다고 느낄 수 있다.
Windows는 주요 dll 파일들은 Relocation이 발생하지 않도록 ImageBase를 겹치게 만들지 않아서 모든 프로세스의 주요 dll이 로딩되는 주소가 같다.
※ 아키텍쳐가 달라도 같은 kernel32.dll 이라면 같은 주소에 로딩된다.
QueueUserAPC() #
CreateRemoteThread()를 사용하는 방법은 OpenProcess로 핸들을 구할때 광범위한 Access 권한을 필요로 하고, 너무 보편화되어 있기 때문에 안티디버깅 도구들이 쉽게 탐지할 수 있다.
https://leehahoon.tistory.com/13