一.Windows消息钩取:SetWindowsHookEx
HHOOK SetWindowsHookEx(
intidHook,// hook type 就是HOOK的消息种类,如WH_KEYBOARD,WH_MOUSE....见MSDN
HOOKPROClpfn,// hook procedure 钩子过程,安装消息钩子时候,钩子过程一定要位于某个DLL内部
HINSTANCEhMod,// handle to application instance 钩子过程的DLL句柄(为NULL则是局部钩子)
DWORDdwThreadId // thread identifier 想要挂上钩子的线程ID(为NULL则为全局钩子)
);
把SetWindowsHookEx的钩子过程放在DLL中(SetWindowsHookEx本身可以放在DLL内部外部都可以),这样只要有消息事件发生,就会自动把Dll加载至目标进程,这样加载时候DllMain可以获取目标进程的句柄。
不用之后要卸载Hook,调用API, UnhookWindowsHookEx(SetWindowsHookEx返回的句柄)
HookMain:
#include "stdio.h"
#include "conio.h"
#include "windows.h"
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
typedef void(*PFN_HOOKSTART)();
typedef void(*PFN_HOOKSTOP)();
void main()
{
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;
hDll = LoadLibraryA(DEF_DLL_NAME);
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
HookStart();
printf("press 'q' to quit!\n");
while (_getch() != 'q');
HookStop();
FreeLibrary(hDll);
}
HookDll:
#include <stdio.h>
#include <windows.h>
#define DEF_PROCESS_NAME "notepad.exe"
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD dwReason, LPVOID lpvReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDll;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = { 0 };
char *p = NULL;
if (nCode >= 0)
{
if (!(lParam & 0x80000000))
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
if (!_stricmp(p + 1, DEF_PROCESS_NAME))
return 1;
}
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
二.创建远程线程注入Dll(CreateRemoteThread)
dll注入原理就是从外部使目标进程调用LoadLibrary()来加载Dll
步骤:
1.OpenProcess来获取目标进程的进程句柄
2.VirtualAllocEx来在目标进程中分配要注入的DLL路径名长度大小的空间
3.WriteProcessMemory在目标进程分配的空间进行写入DLL路径(相当于)
4.GetModuleHandle + GetProcAddress获取LoadLibrary的地址(因为kernel32.dll加载的地址一样,所以目标进程一样可以加载)
5.CreateRemoteThread在目标进程运行LoadLibrary加载Dll,VirtualAllocEx分配空间为LoadLibrary的参数
6.DllMain中再CreateThread来创建线程干自己想干的事
注入Main程序:
#include<windows.h>
#include<stdio.h>
#include"tchar.h"
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath);
int _tmain(int argc, TCHAR *argv[])
{
if (argc != 3)
{
_tprintf(TEXT("%s参数不足"), argv[0]);
getchar();
return 1;
}
if (InjectDll((DWORD)_tstol(argv[1]), argv[2]))
{
_tprintf(TEXT("InjectDll %s success!!!\n"), argv[2]);
}
else
{
_tprintf(TEXT("InjectDll %s failed!!!\n"), argv[2]);
}
getchar();
return 0;
}
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)((_tcslen(szDllPath) + 1) * sizeof(TCHAR));
LPTHREAD_START_ROUTINE pThreadProc;
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(TEXT("OpenProcess %d PID failed!!! [%d]\n"), dwPID,GetLastError());
return FALSE;
}
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);
hMod = GetModuleHandle(TEXT("kernel32.dll"));
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, TEXT("LoadLibraryA"));
if (NULL == pThreadProc)
{
_tprintf(TEXT("GetLastError = [%d]\n"), GetLastError());
return FALSE;
}
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
if (NULL == hThread)
{
_tprintf(TEXT("GetLastError = [%d]\n"), GetLastError());
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
注入的Dll:
#include<windows.h>
#include "tchar.h"
#pragma comment(lib,"urlmon.lib")
#define DEF_FILE_NAME (TEXT("index.html"))
#define DEF_URL (TEXT("http://www/naver.com/index.html"))
HMODULE g_hMod = NULL;
DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[MAX_PATH] = { 0 };
if (!GetModuleFileName(g_hMod, szPath, MAX_PATH))
return FALSE;
TCHAR *p = _tcsrchr(szPath, '\\');
if (!p)
return FALSE;
_tcscpy_s(p + 1, MAX_PATH, DEF_FILE_NAME);
URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;
g_hMod = (HMODULE)hinstDLL;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL,TEXT("注入成功!"),TEXT("Success!"),NULL);
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
return TRUE;
}
三,使用注册表
原理:User32.dll加载时候会读取AppInit_DLLs,若有值则会LoadLibrary加载用户DLL
在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
1.要注入的DLL路径写入AppInit_DLLs中
2.把LoadAppInit_DLLs注册表项的值设为1
这样DLL就会注入到所有加载user32.dll的进程
四,Dll卸载(只适用于自己注入的DLL,PE记载的不能卸载)
原理:使目标去调用FreeLibrary去卸载DLL
过程:
1.遍历进程查找获得对应程序的PID(CreateToolhelp32Snapshot-》PROCESSENTRY32)
2.提高进程权限(获取进程访问的令牌句柄OpenProcessToken -> 获取对应权限的LUID(LookupPrivilegevalue)->改变令牌权限(AdjustTokenPrivileges))
3.查找目标进程的模板(CreateToolhelp32Snapshot-》MODULEENTRY32),获取对应的DLL的handle
4.用CreateRemoteThread来调用FreeLibrary去卸载注入DLL
#include<stdio.h>
#include<windows.h>
#include<tlhelp32.h>
#include<tchar.h>
DWORD FindProcessID(LPCTSTR szProcessName);
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege);
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName);
#define DEF_PROC_NAME (TEXT("notepad.exe"))
#define DEF_DLL_NAME (TEXT("myhack.dll"))
int _tmain()
{
DWORD dwPID = 0xFFFFFFFF;
dwPID = FindProcessID(DEF_PROC_NAME);
if (dwPID == 0XFFFFFFFF)
{
_tprintf(TEXT("没有找到这个process"));
getchar();
return 1;
}
_tprintf(TEXT("PID of \"%s\" is %d\n"), DEF_PROC_NAME, dwPID);
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
{
getchar();
return 1;
}
if (EjectDll(dwPID, DEF_DLL_NAME))
{
_tprintf(TEXT("EjectDll(%d \"%s\") success!!!\n"), dwPID, DEF_DLL_NAME);
}
else
{
_tprintf(TEXT("EjectDll(%d \"%s\") falied!!!\n"), dwPID, DEF_DLL_NAME);
}
getchar();
return 0;
}
DWORD FindProcessID(LPCTSTR szProcessName)
{
DWORD dwPID = 0XFFFFFFFF;
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL,NULL);
Process32First(hSnapshot,&pe);
do
{
if (!_tcsicmp(pe.szExeFile, DEF_PROC_NAME))
{
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe));
CloseHandle(hSnapshot);
return dwPID;
}
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
_tprintf(TEXT("OpenProcessToken error : %u\n"), GetLastError());
return FALSE;
}
LookupPrivilegeValue(NULL,lpszPrivilege,&luid);
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(TEXT("AdjustTokenPrivileges error : %u\n"), GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
_tprintf(TEXT("The Token does not have the specified privilege. \n"));
return FALSE;
}
return TRUE;
}
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL,dwPID);
bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me))
{
if (!_tcsicmp((LPCTSTR)me.szExePath, szDllName) | !_tcsicmp((LPCTSTR)me.szModule, szDllName))
{
bFound = TRUE;
break;
}
}
if (!bFound)
{
CloseHandle(hSnapshot);
return FALSE;
}
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
_tprintf(TEXT("OpenProcess falied"));
return FALSE;
}
hModule = GetModuleHandle(TEXT("kernel32.dll"));
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule,TEXT("FreeLibrary"));
hThread = CreateRemoteThread(hProcess,NULL,0,pThreadProc,me.modBaseAddr,0,NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);
return TRUE;
}
五,修改PE来加载DLL
原理:修改导入表来实现加载DLL
步骤:
1.查看IDT(IMPORT Directory Table)是否有足够空间来增加一个新的IID
2.有足够空间就直接新增一个IID并修改导入表的SIZE(没有足够空间就转移导入表并修改导入表的RVA以及SIZE)
3.将新增的IID的INT 和 IAT以及NAME都修改
4.删除BOUND IMPORT表
5.修改导入表的节区属性可写
6.不修改导入表节区属性可以将新增的IID的FirstThunk改为IAT的末尾地址
DLL代码:
#include<windows.h>
#include<string.h>
#include<tchar.h>
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL,TEXT("注入成功"),TEXT("Success!"),MB_OK);
break;
default:
break;
}
return TRUE;
}
#ifdef __cplusplus
extern "C" {
#endif
_declspec(dllexport) void Fun()
{
return;
}
#ifdef __cplusplus
}
#endif
强行导入一个Fun是为了保存完整性,能够加载DLL
六,代码注入
与DLL注入不一样,代码注入仅向目标进程注入必要的代码而不是整个DLL
原理:用一个结构体来保存要注入代码使用的API地址(一般是user32.dll中的)以及所需要用到的数据(如MessageBoxA函数所需要的信息框名字和标题),并将这个结构体也注入进目标进程,再将线程过程代码都注入进目标进程。(一样利用OpenProcess -> VirtualAllocEx -> WriteProcessMemory -> CreateRemoteThread)
#include<windows.h>
#include<stdio.h>
#include<string.h>
DWORD WINAPI ThreadProc(LPVOID lParam);
BOOL CodeInject(DWORD dwPID);
typedef struct _THREAD_PARAM
{
FARPROC pFun[2];
char szBuf[4][128];
}THREAD_PARAM, *PTHREAD_PARAM;
typedef HMODULE (WINAPI *PFAR_LOADLIBRARYA)
(
LPCSTR lpLibFileName
);
typedef HMODULE(WINAPI *PFAR_GETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);
typedef int (WINAPI *PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCTSTR lpCaption,
UINT type
);
int main(int argc, char *argv[])
{
DWORD dwPID = 0;
if (argc != 2)
{
printf("请输入要注入程序的PID!\n");
getchar();
return 1;
}
dwPID = (DWORD)atol(argv[1]);
CodeInject(dwPID);
return 0;
}
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
hMod = ((PFAR_LOADLIBRARYA)pParam->pFun[0])(pParam->szBuf[0]);
pFunc = (FARPROC)((PFAR_GETPROCADDRESS)pParam->pFun[1])(hMod,pParam->szBuf[1]);
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}
BOOL CodeInject(DWORD dwPID)
{
HMODULE hMod = NULL;
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
DWORD dwSIZE = 0;
THREAD_PARAM param = {0};
LPVOID pRemoteBuf[2] = {0};
hMod = GetModuleHandleA("kernel32.dll");
param.pFun[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFun[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0],sizeof("user32.dll"),"user32.dll");
strcpy_s(param.szBuf[1], sizeof("MessageBoxA"),"MessageBoxA");
strcpy_s(param.szBuf[2], sizeof("Inject Success!"),"Inject Success!");
strcpy_s(param.szBuf[3], sizeof("Success!"),"Success!");
hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);
dwSIZE = sizeof(THREAD_PARAM);
pRemoteBuf[0] = VirtualAllocEx(hProcess,
NULL,
dwSIZE,
MEM_COMMIT,
PAGE_READWRITE);
WriteProcessMemory(hProcess,
pRemoteBuf[0],
(LPVOID)¶m,
dwSIZE,
NULL);
dwSIZE = (DWORD)CodeInject - (DWORD)ThreadProc;
pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
dwSIZE,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess,pRemoteBuf[1],(LPVOID)ThreadProc,dwSIZE,NULL);
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteBuf[1], pRemoteBuf[0], 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
七,汇编代码注入
原理:将汇编代码的注入部分(CreateRemoteThread的传入的过程代码)用十六进制保存,再传入一个结构体(CreateRemoteThread的传入的参数),结构体有LoadLibrary和GetProcAddress两个API的地址,再在汇编代码中进行调用这个结构体这两个地址。
#include<windows.h>
#include<stdio.h>
BOOL InjectCode(DWORD dwPID);
BYTE g_InjectionCode[] = {
0x55,0x8B,0xEC,0x8B,0x75,0x08,0x68,0x6C,
0x6C,0x00,0x00,0x68,0x33,0x32,0x2E,0x64,
0x68,0x75,0x73,0x65,0x72,0x54,0xFF,0x16,
0x68,0x6F,0x78,0x41,0x00,0x68,0x61,0x67,
0x65,0x42,0x68,0x4D,0x65,0x73,0x73,0x54,
0x50,0xFF,0x56,0x04,0x6A,0x00,0xE8,0x0C,
0x00,0x00,0x00,0x52,0x65,0x76,0x65,0x72,
0x73,0x65,0x43,0x6F,0x72,0x65,0x00,0xE8,
0x14,0x00,0x00,0x00,0x77,0x77,0x77,0x2E,
0x72,0x65,0x76,0x65,0x72,0x73,0x65,0x63,
0x6F,0x72,0x65,0x2E,0x63,0x6F,0x6D,0x00,
0x6A,0x00,0xFF,0xD0,0x33,0xC0,0x8B,0xE5,
0x5D,0xC3
};
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2];
}THREAD_PARAM, *PTHREAD_PARAM;
int main()
{
DWORD dwPID = 12056;
InjectCode(dwPID);
getchar();
return 0;
}
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");
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
dwPID)))
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess,
NULL,
sizeof(THREAD_PARAM),
MEM_COMMIT,
PAGE_READWRITE)))
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if (!WriteProcessMemory(hProcess,
pRemoteBuf[0],
(LPVOID)¶m,
sizeof(THREAD_PARAM),
NULL))
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
sizeof(g_InjectionCode),
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)))
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if (!WriteProcessMemory(hProcess,
pRemoteBuf[1],
(LPVOID)&g_InjectionCode,
sizeof(g_InjectionCode),
NULL))
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if (!(hThread = CreateRemoteThread(hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pRemoteBuf[1],
pRemoteBuf[0],
0,
NULL)))
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
十六进制部分所代表的汇编代码:
/*
004010ED 55 PUSH EBP
004010EE 8BEC MOV EBP,ESP
004010F0 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; ESI = 结构体的地址,地址表示的是API所代表的地址
004010F3 68 6C6C0000 PUSH 6C6C
004010F8 68 33322E64 PUSH 642E3233
004010FD 68 75736572 PUSH 72657375<span style="white-space:pre"> </span>; 三行表示的是"user32.dll"逆序push
00401102 54 PUSH ESP ; ESP表示的是 "user32.dll"字符串的首地址
00401103 FF16 CALL DWORD PTR DS:[ESI] ; 调用LoadLibraryA("user32.dll")
00401105 68 6F784100 PUSH 41786F
0040110A 68 61676542 PUSH 42656761
0040110F 68 4D657373 PUSH 7373654D
00401114 54 PUSH ESP ; - 字符串"MessageBoxA"的首地址
00401115 50 PUSH EAX ; - 前面LoadLibrary所返回的hMod
00401116 FF56 04 CALL DWORD PTR DS:[ESI+4] ; - GetProcAddress(hMod, "MessageBoxA")
00401119 6A 00 PUSH 0 ; - MB_OK (0)
0040111B E8 0C000000 CALL 0040112C<span style="white-space:pre"> </span>; CALL后面的是ASCLL,CALL的意思是PUSH EIP,相当于PUSH了后面字符串的首地址
00401120 <ASCII> ; - "ReverseCore", 0
0040112C E8 14000000 CALL 00401145
00401131 <ASCII> ; - "www.reversecore.com", 0
00401145 6A 00 PUSH 0 ; - hWnd (0)
00401147 FFD0 CALL EAX ; EAX是GetProcAddress所获得的地址存放在eax - MessageBoxA(0, "www.reversecore.com", "ReverseCore", 0)
00401149 33C0 XOR EAX,EAX <span style="white-space:pre"> </span>; API结果要返回0 到EAX中
0040114B 8BE5 MOV ESP,EBP
0040114D 5D POP EBP ; 清除堆栈
0040114E C3 RETN
*/