0xEBの彼岸

rce, kernel, vm

東方紅魔郷の自機に当たり判定のマーカーを表示する

東方紅魔郷


東方紅魔郷では、以後の作品のように、低速移動の際に当たり判定を示すマーカーを表示することができません。

そこで、DirectXをフックすることで、マーカーの表示を実現してみます*1


DirectXのフック


ここでは、偽のd3dx8.dllを東方紅魔郷.exeと同じディレクトリから読み込ませ、IAT*2を書き換えることで、自作関数を呼び出すというアプローチをとります。

以下をフックします。



  • IDirect3DDevice8

  • IDirect3D8


それぞれをどう置き換えるか示します。なお、呼び出し規約にしたがって__stdcallを付けておく必要があります。これがないとフリーズしてしまいました。


IDirect3DDevice8


myIDirect3DDevice8::myIDirect3DDevice8(IDirect3DDevice8* pOriginal)
{
m_pIDirect3DDevice8 = pOriginal; // ポインタを保存
draw = false;
hit_marker_texture = NULL;
focused_time = 255;
count = 0;
frame_count = 0;
}

クラスも用意しておきます。


class myIDirect3DDevice8 : public IDirect3DDevice8   
{
private:
IDirect3DTexture8* hitbox_texture;
unsigned int count;
int focused_time;
bool draw;
unsigned int frame_count;
IDirect3DDevice8 *m_pIDirect3DDevice8;
public:

myIDirect3DDevice8(IDirect3DDevice8* pOriginal);
virtual ~myIDirect3DDevice8();

HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObj);
ULONG __stdcall AddRef(void);
ULONG __stdcall Release(void);
HRESULT __stdcall TestCooperativeLevel(void);
UINT __stdcall GetAvailableTextureMem(void);
HRESULT __stdcall ResourceManagerDiscardBytes(DWORD Bytes);
HRESULT __stdcall GetDirect3D(IDirect3D8** ppD3D8);
HRESULT __stdcall GetDeviceCaps(D3DCAPS8* pCaps);
HRESULT __stdcall GetDisplayMode(D3DDISPLAYMODE* pMode);
HRESULT __stdcall GetCreationParameters(D3DDEVICE_CREATION_PARAMETERS *pParameters);
HRESULT __stdcall SetCursorProperties(UINT XHotSpot,UINT YHotSpot,IDirect3DSurface8* pCursorBitmap);
void __stdcall SetCursorPosition(UINT XScreenSpace,UINT YScreenSpace,DWORD Flags);
BOOL __stdcall ShowCursor(BOOL bShow);
HRESULT __stdcall CreateAdditionalSwapChain(D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DSwapChain8** pSwapChain);
HRESULT __stdcall Reset(D3DPRESENT_PARAMETERS* pPresentationParameters);
HRESULT __stdcall Present(CONST RECT* pSourceRect,CONST RECT* pDestRect,HWND hDestWindowOverride,CONST RGNDATA* pDirtyRegion);
HRESULT __stdcall GetBackBuffer(UINT BackBuffer,D3DBACKBUFFER_TYPE Type,IDirect3DSurface8** ppBackBuffer);
HRESULT __stdcall GetRasterStatus(D3DRASTER_STATUS* pRasterStatus);
void __stdcall SetGammaRamp(DWORD Flags,CONST D3DGAMMARAMP* pRamp);
void __stdcall GetGammaRamp(D3DGAMMARAMP* pRamp);
HRESULT __stdcall CreateTexture(UINT Width,UINT Height,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DTexture8** ppTexture);
HRESULT __stdcall CreateVolumeTexture(UINT Width,UINT Height,UINT Depth,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DVolumeTexture8** ppVolumeTexture);
HRESULT __stdcall CreateCubeTexture(UINT EdgeLength,UINT Levels,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DCubeTexture8** ppCubeTexture);
HRESULT __stdcall CreateVertexBuffer(UINT Length,DWORD Usage,DWORD FVF,D3DPOOL Pool,IDirect3DVertexBuffer8** ppVertexBuffer);
HRESULT __stdcall CreateIndexBuffer(UINT Length,DWORD Usage,D3DFORMAT Format,D3DPOOL Pool,IDirect3DIndexBuffer8** ppIndexBuffer);
HRESULT __stdcall CreateRenderTarget(UINT Width,UINT Height,D3DFORMAT Format,D3DMULTISAMPLE_TYPE MultiSample,BOOL Lockable,IDirect3DSurface8** ppSurface);
HRESULT __stdcall CreateDepthStencilSurface(UINT Width,UINT Height,D3DFORMAT Format,D3DMULTISAMPLE_TYPE MultiSample,IDirect3DSurface8** ppSurface);
HRESULT __stdcall CreateImageSurface(UINT Width,UINT Height,D3DFORMAT Format,IDirect3DSurface8** ppSurface);
HRESULT __stdcall CopyRects(IDirect3DSurface8* pSourceSurface,CONST RECT* pSourceRectsArray,UINT cRects,IDirect3DSurface8* pDestinationSurface,CONST POINT* pDestPointsArray);
HRESULT __stdcall UpdateTexture(IDirect3DBaseTexture8* pSourceTexture,IDirect3DBaseTexture8* pDestinationTexture);
HRESULT __stdcall GetFrontBuffer(IDirect3DSurface8* pDestSurface);
HRESULT __stdcall SetRenderTarget(IDirect3DSurface8* pRenderTarget,IDirect3DSurface8* pNewZStencil);
HRESULT __stdcall GetRenderTarget(IDirect3DSurface8** ppRenderTarget);
HRESULT __stdcall GetDepthStencilSurface(IDirect3DSurface8** ppZStencilSurface);
HRESULT __stdcall BeginScene(THIS);
HRESULT __stdcall EndScene(THIS);
HRESULT __stdcall Clear(DWORD Count,CONST D3DRECT* pRects,DWORD Flags,D3DCOLOR Color,float Z,DWORD Stencil);
HRESULT __stdcall SetTransform(D3DTRANSFORMSTATETYPE State,CONST D3DMATRIX* pMatrix);
HRESULT __stdcall GetTransform(D3DTRANSFORMSTATETYPE State,D3DMATRIX* pMatrix);
HRESULT __stdcall MultiplyTransform(D3DTRANSFORMSTATETYPE State,CONST D3DMATRIX* pMatrix);
HRESULT __stdcall SetViewport(CONST D3DVIEWPORT8* pViewport);
HRESULT __stdcall GetViewport(D3DVIEWPORT8* pViewport);
HRESULT __stdcall SetMaterial(CONST D3DMATERIAL8* pMaterial);
HRESULT __stdcall GetMaterial(D3DMATERIAL8* pMaterial);
HRESULT __stdcall SetLight(DWORD Index,CONST D3DLIGHT8* pLight);
HRESULT __stdcall GetLight(DWORD Index,D3DLIGHT8* pLight);
HRESULT __stdcall LightEnable(DWORD Index,BOOL Enable);
HRESULT __stdcall GetLightEnable(DWORD Index,BOOL* pEnable);
HRESULT __stdcall SetClipPlane(DWORD Index,CONST float* pPlane);
HRESULT __stdcall GetClipPlane(DWORD Index,float* pPlane);
HRESULT __stdcall SetRenderState(D3DRENDERSTATETYPE State,DWORD Value);
HRESULT __stdcall GetRenderState(D3DRENDERSTATETYPE State,DWORD* pValue);
HRESULT __stdcall BeginStateBlock(THIS);
HRESULT __stdcall EndStateBlock(DWORD* pToken);
HRESULT __stdcall ApplyStateBlock(DWORD Token);
HRESULT __stdcall CaptureStateBlock(DWORD Token);
HRESULT __stdcall DeleteStateBlock(DWORD Token);
HRESULT __stdcall CreateStateBlock(D3DSTATEBLOCKTYPE Type,DWORD* pToken);
HRESULT __stdcall SetClipStatus(CONST D3DCLIPSTATUS8* pClipStatus);
HRESULT __stdcall GetClipStatus(D3DCLIPSTATUS8* pClipStatus);
HRESULT __stdcall GetTexture(DWORD Stage,IDirect3DBaseTexture8** ppTexture);
HRESULT __stdcall SetTexture(DWORD Stage,IDirect3DBaseTexture8* pTexture);
HRESULT __stdcall GetTextureStageState(DWORD Stage,D3DTEXTURESTAGESTATETYPE Type,DWORD* pValue);
HRESULT __stdcall SetTextureStageState(DWORD Stage,D3DTEXTURESTAGESTATETYPE Type,DWORD Value);
HRESULT __stdcall ValidateDevice(DWORD* pNumPasses);
HRESULT __stdcall GetInfo(DWORD DevInfoID,void* pDevInfoStruct,DWORD DevInfoStructSize);
HRESULT __stdcall SetPaletteEntries(UINT PaletteNumber,CONST PALETTEENTRY* pEntries);
HRESULT __stdcall GetPaletteEntries(UINT PaletteNumber,PALETTEENTRY* pEntries);
HRESULT __stdcall SetCurrentTexturePalette(UINT PaletteNumber);
HRESULT __stdcall GetCurrentTexturePalette(UINT *PaletteNumber);
HRESULT __stdcall DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType,UINT StartVertex,UINT PrimitiveCount);
HRESULT __stdcall DrawIndexedPrimitive(D3DPRIMITIVETYPE Type,UINT minIndex,UINT NumVertices,UINT startIndex,UINT primCount);
HRESULT __stdcall DrawPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType,UINT PrimitiveCount,CONST void* pVertexStreamZeroData,UINT VertexStreamZeroStride);
HRESULT __stdcall DrawIndexedPrimitiveUP(D3DPRIMITIVETYPE PrimitiveType,UINT MinVertexIndex,UINT NumVertexIndices,UINT PrimitiveCount,CONST void* pIndexData,D3DFORMAT IndexDataFormat,CONST void* pVertexStreamZeroData,UINT VertexStreamZeroStride);
HRESULT __stdcall ProcessVertices(UINT SrcStartIndex,UINT DestIndex,UINT VertexCount,IDirect3DVertexBuffer8* pDestBuffer,DWORD Flags);
HRESULT __stdcall CreateVertexShader(CONST DWORD* pDeclaration,CONST DWORD* pFunction,DWORD* pHandle,DWORD Usage);
HRESULT __stdcall SetVertexShader(DWORD Handle);
HRESULT __stdcall GetVertexShader(DWORD* pHandle);
HRESULT __stdcall DeleteVertexShader(DWORD Handle);
HRESULT __stdcall SetVertexShaderConstant(DWORD Register,CONST void* pConstantData,DWORD ConstantCount);
HRESULT __stdcall GetVertexShaderConstant(DWORD Register,void* pConstantData,DWORD ConstantCount);
HRESULT __stdcall GetVertexShaderDeclaration(DWORD Handle,void* pData,DWORD* pSizeOfData);
HRESULT __stdcall GetVertexShaderFunction(DWORD Handle,void* pData,DWORD* pSizeOfData);
HRESULT __stdcall SetStreamSource(UINT StreamNumber,IDirect3DVertexBuffer8* pStreamData,UINT Stride);
HRESULT __stdcall GetStreamSource(UINT StreamNumber,IDirect3DVertexBuffer8** ppStreamData,UINT* pStride);
HRESULT __stdcall SetIndices(IDirect3DIndexBuffer8* pIndexData,UINT BaseVertexIndex);
HRESULT __stdcall GetIndices(IDirect3DIndexBuffer8** ppIndexData,UINT* pBaseVertexIndex);
HRESULT __stdcall CreatePixelShader(CONST DWORD* pFunction,DWORD* pHandle);
HRESULT __stdcall SetPixelShader(DWORD Handle);
HRESULT __stdcall GetPixelShader(DWORD* pHandle);
HRESULT __stdcall DeletePixelShader(DWORD Handle);
HRESULT __stdcall SetPixelShaderConstant(DWORD Register,CONST void* pConstantData,DWORD ConstantCount);
HRESULT __stdcall GetPixelShaderConstant(DWORD Register,void* pConstantData,DWORD ConstantCount);
HRESULT __stdcall GetPixelShaderFunction(DWORD Handle,void* pData,DWORD* pSizeOfData);
HRESULT __stdcall DrawRectPatch(UINT Handle,CONST float* pNumSegs,CONST D3DRECTPATCH_INFO* pRectPatchInfo);
HRESULT __stdcall DrawTriPatch(UINT Handle,CONST float* pNumSegs,CONST D3DTRIPATCH_INFO* pTriPatchInfo);
HRESULT __stdcall DeletePatch(UINT Handle);

};


以下のようにメソッドを書き換えていきます。


IDirect3DDevice8::QueryInterface


HRESULT __stdcall myIDirect3DDevice8::QueryInterface(REFIID riid, void** ppvObj)
{
*ppvObj = NULL;

HRESULT hRes = m_pIDirect3DDevice8->QueryInterface(riid, ppvObj);

if (hRes == NOERROR)
{
*ppvObj = this;
}

return hRes;
}


IDirect3DDevice8::Release


ULONG   __stdcall myIDirect3DDevice8::Release(void)
{
extern myIDirect3DDevice8* gl_pmyIDirect3DDevice8;

if(hit_marker_texture)
hit_marker_texture->Release();
ULONG count = m_pIDirect3DDevice8->Release();

gl_pmyIDirect3DDevice8 = NULL;
delete(this);

return (count);
}


IDirect3DDevice8::EndScene


HRESULT __stdcall myIDirect3DDevice8::EndScene(void)  
{
if(!hit_marker_texture) {
D3DXCreateTextureFromFileEx(m_pIDirect3DDevice8, "hit_marker.png", 0, 0, 9, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &hit_marker_texture);
}

if(draw && hit_marker_texture) {
bool focused = *( (unsigned char*) 0x006CB00B);
int paused = *( (unsigned char*) 0x00481B44);
if(paused == 0) {
if(focused) {
focused_time = max(0, focused_time - 256 / 32);

float half_hit_marker_size = 32.5f * (1.0f - .9f*(focused_time / 255.0f));
float x_border = 32.0;
float y_border = 16.0;

float player_sprite_center_x = *( (float*)0x006CAA80) + x_border + 1.5f;
float player_sprite_center_y = *( (float*)0x006CAA84) + y_border + 1.5f;

float x = player_sprite_center_x;
float y = player_sprite_center_y;

float radians = PI * (count / 128.0f);
count++;

DWORD state_token;
m_pIDirect3DDevice8->CreateStateBlock(D3DSBT_ALL, &state_token);
m_pIDirect3DDevice8->CaptureStateBlock(state_token);

D3DVIEWPORT8 vp; vp.X = 32; vp.Y = 16; vp.Width = 384; vp.Height = 448; vp.MinZ = 0.0; vp.MaxZ = 1.0;

lvert verts[4];
verts[0].x = x + (-half_hit_marker_size * cosf(radians) + -half_hit_marker_size * -sinf(radians)); verts[0].y = y + (-half_hit_marker_size * sinf(radians) + -half_hit_marker_size * cosf(radians)); verts[0].z = 0.0045f; verts[0].q = 1.0f; verts[0].tu = 0.0f; verts[0].tv = 0.0f;
verts[1].x = x + (-half_hit_marker_size * cosf(radians) + half_hit_marker_size * -sinf(radians)); verts[1].y = y + (-half_hit_marker_size * sinf(radians) + half_hit_marker_size * cosf(radians)); verts[1].z = 0.0045f; verts[1].q = 1.0f; verts[1].tu = 0.0f; verts[1].tv = 1.0f;
verts[2].x = x + ( half_hit_marker_size * cosf(radians) + -half_hit_marker_size * -sinf(radians)); verts[2].y = y + ( half_hit_marker_size * sinf(radians) + -half_hit_marker_size * cosf(radians)); verts[2].z = 0.0045f; verts[2].q = 1.0f; verts[2].tu = 1.0f; verts[2].tv = 0.0f;
verts[3].x = x + ( half_hit_marker_size * cosf(radians) + half_hit_marker_size * -sinf(radians)); verts[3].y = y + ( half_hit_marker_size * sinf(radians) + half_hit_marker_size * cosf(radians)); verts[3].z = 0.0045f; verts[3].q = 1.0f; verts[3].tu = 1.0f; verts[3].tv = 1.0f;
verts[0].diffuse = verts[1].diffuse = verts[2].diffuse = verts[3].diffuse = D3DCOLOR_ARGB(focused_time,0x00,0x00,0x00);

m_pIDirect3DDevice8->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
m_pIDirect3DDevice8->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SUBTRACT);
m_pIDirect3DDevice8->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
m_pIDirect3DDevice8->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
m_pIDirect3DDevice8->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
m_pIDirect3DDevice8->SetTexture(0, hit_marker_texture);

m_pIDirect3DDevice8->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
m_pIDirect3DDevice8->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);

m_pIDirect3DDevice8->SetVertexShader(0x144);
m_pIDirect3DDevice8->SetViewport(&vp);

m_pIDirect3DDevice8->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
m_pIDirect3DDevice8->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_pIDirect3DDevice8->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
m_pIDirect3DDevice8->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
m_pIDirect3DDevice8->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
m_pIDirect3DDevice8->SetRenderState(D3DRS_TEXTUREFACTOR, 0x00000000);

m_pIDirect3DDevice8->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, &verts, 28);

m_pIDirect3DDevice8->ApplyStateBlock(state_token);
m_pIDirect3DDevice8->DeleteStateBlock(state_token);

}
else {
focused_time = min(focused_time + 256 / 32, 255);
}
}
}

draw = false;
return (m_pIDirect3DDevice8->EndScene() );
}


当たり判定マーカーの画像を読み込んで自機に描画しています。




IDirect3D8


IDirect3D8も書き換えましょう。


myIDirect3D8::myIDirect3D8(IDirect3D8 *pOriginal)
{
m_pIDirect3D8 = pOriginal; // ポインタを保存
}

例によってクラスです。


class myIDirect3D8 : public IDirect3D8    
{
private:

IDirect3D8 * m_pIDirect3D8;
public:

myIDirect3D8(IDirect3D8 *pOriginal);
virtual ~myIDirect3D8();

HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObj);
ULONG __stdcall AddRef(void);
ULONG __stdcall Release(void);
HRESULT __stdcall RegisterSoftwareDevice(void* pInitializeFunction);
UINT __stdcall GetAdapterCount(void);
HRESULT __stdcall GetAdapterIdentifier(UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER8* pIdentifier);
UINT __stdcall GetAdapterModeCount(UINT Adapter);
HRESULT __stdcall EnumAdapterModes(UINT Adapter,UINT Mode,D3DDISPLAYMODE* pMode);
HRESULT __stdcall GetAdapterDisplayMode( UINT Adapter,D3DDISPLAYMODE* pMode);
HRESULT __stdcall CheckDeviceType(UINT Adapter,D3DDEVTYPE CheckType,D3DFORMAT DisplayFormat,D3DFORMAT BackBufferFormat,BOOL Windowed);
HRESULT __stdcall CheckDeviceFormat(UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat);
HRESULT __stdcall CheckDeviceMultiSampleType(UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType);
HRESULT __stdcall CheckDepthStencilMatch(UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat);
HRESULT __stdcall GetDeviceCaps(UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS8* pCaps);
HMONITOR __stdcall GetAdapterMonitor(UINT Adapter);
HRESULT __stdcall CreateDevice(UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface);

};


それでは、メソッドを書き換えていきます。


IDirect3D8::QueryInterface


HRESULT __stdcall myIDirect3D8::QueryInterface(REFIID riid, void** ppvObj)
{
*ppvObj = NULL;

HRESULT hRes = m_pIDirect3D8->QueryInterface(riid, ppvObj);

if (hRes == NOERROR)
{
*ppvObj = this;
}

return hRes;
}


IDirect3D8::Release


ULONG __stdcall myIDirect3D8::Release()
{
extern myIDirect3D8* gl_pmyIDirect3D8;

ULONG count = m_pIDirect3D8->Release();

if (count == 0)
{
gl_pmyIDirect3D8 = NULL;
delete(this);
}

return(count);
}


IDirect3D8::CreateDevice


HRESULT __stdcall myIDirect3D8::CreateDevice(UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice8** ppReturnedDeviceInterface) 
{
extern myIDirect3DDevice8* gl_pmyIDirect3DDevice8;

HRESULT hres = m_pIDirect3D8->CreateDevice( Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);

gl_pmyIDirect3DDevice8 = new myIDirect3DDevice8(*ppReturnedDeviceInterface);

*ppReturnedDeviceInterface = gl_pmyIDirect3DDevice8;

return(hres);
}


これら以外は本来のポインタを返すようにします。


IAT


フックを行うために、DLL_PROCESS_ATTACHの際、IATを書き換えなければなりません。

以下のようなコードでIATを書き換えられます。


BOOL HookIAT(char *szModule,char *szImportName,void *DummyFunc)
{
HMODULE base = GetModuleHandle(NULL);
DWORD size;
PIMAGE_IMPORT_DESCRIPTOR imgDesc = (PIMAGE_IMPORT_DESCRIPTOR)(ImageDirectoryEntryToData(base, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size));

while(imgDesc->Name) {
char* module = (char*)( (DWORD)(base) + imgDesc->Name);
if(!lstrcmp(module, szModule)) {
break;
}
++imgDesc;
}

if(imgDesc->Name) {

PIMAGE_THUNK_DATA pIAT,pINT;
pIAT = reinterpret_cast(reinterpret_cast(base) + imgDesc->FirstThunk);
pINT = reinterpret_cast(reinterpret_cast(base) + imgDesc->OriginalFirstThunk);
while(pIAT->u1.Function) {
if(IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) {
continue;
}
PIMAGE_IMPORT_BY_NAME pImportName = reinterpret_cast(reinterpret_cast(base)+pINT->u1.AddressOfData);

if(!lstrcmp(reinterpret_cast(pImportName->Name), szImportName)) {
DWORD oldProtect;
VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &oldProtect);
pIAT->u1.Function = reinterpret_cast(DummyFunc);
VirtualProtect(&pIAT->u1.Function,sizeof(DWORD),oldProtect,&oldProtect);
}

++pIAT;
++pINT;
}
}

return TRUE;
}


この関数を以下のように呼び出すことで、IDirect3DDevice8の呼び出し時にmyIDirect3DDevice8を、IDirect3D8呼び出し時にmyIDirect3D8を呼び出すことができます。


HookIAT("d3d8.dll","IDirect3DDevice8",(void *)myIDirect3DDevice8);
HookIAT("d3d8.dll","IDirect3D8",(void *)myIDirect3D8);

おわりに


こうして、東方紅魔郷の自機に当たり判定のマーカーを表示することができました。


f:id:mayahu32:20120617150602p:plain

DirectXのフックはネトゲのWallHackにも使われている*3技術です。

応用するといろいろな楽しいことができるのではないでしょうか。


参考


久々の以下略
Direct3D8 API-Hooking

*1:海外の有志が作成したグラフィック差し替えパッチを用いるという選択肢もあります

*2:インポートアドレステーブル

*3:ただし、チート検出に引っかかるため、d3dx*.dllではなく、nvd3dum.dllやaticfx32.dllをフックしています

PsLoadedModuleListから除外されたカーネルモジュール内ではSEH例外が適切に処理されない件

PsLoadedModuleList

Windowsにおいて、カーネルモジュールはPsLoadedModuleListの双方向リンクリストに紐付けられています。

PsLoadedModuleListを確認すれば、ロードされているカーネルモジュール一覧を得ることが可能です。

そのため、一部のカーネルrootkitはPsLoadedModuleListのリンクリストから自身を除外し、隠蔽を図ろうとします*1

カーネルランドにおけるSEH

SEHはスレッド単位で提供されており、ユーザーランド・カーネルランド両方で利用可能です。

typedef struct _NT_TIB
{
     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
     PVOID StackBase;
     PVOID StackLimit;
     PVOID SubSystemTib;
     union
     {
          PVOID FiberData;
          ULONG Version;
     };
     PVOID ArbitraryUserPointer;
     PNT_TIB Self;
} NT_TIB, *PNT_TIB;

ネイティブAPIにはMmProbeAndLockPages()のように、SEHと併用する必要のある関数があり、SEHを利用したカーネルドライバは多数存在しています。

f:id:mayahu32:20120504104617j:plain

PsLoadedModuleListから除外されたカーネルモジュール内でSEHを呼び出す

さて、PsLoadedModuleListから隠蔽されたカーネルモジュールでSEHを利用しようとする*2と、KeBugCheckEx()が呼び出され、STOPエラーが発生します。

f:id:mayahu32:20120504110025p:plain

原因

STOPエラーが発生した原因は、例外が処理されなかったためであると考えられます。

RtlDispatchException()

Windowsは、例外を処理する際、例外ハンドラのアドレスを検証し、検証に成功したときだけ、その例外ハンドラを実行します。この処理はRtlDispatchException()として実装されています。

BOOLEAN RtlDispatchException(PCONTEXT ContextRecord, PEXCEPTION_RECORD ExceptionRecord) {
    BOOLEAN ret = 0;
	LPVOID StackBase, StackLimit;
	PEXCEPTION_REGISTRATION head;
	DWORD kProcess_Flags;
	DWORD dispatch, highest;
	EXCEPTION_RECORD exRec;
	
	if (RtlCallVectoredExceptionHandlers(ExceptionRecord, ContextRecord)) {
		ret = 1;
	}
	else {
		RtlpGetStackLimits(&StackLimit, &StackBase);
		
		head = RtlpGetRegistrationHead();
		
		highest = 0;
		
		while (head != (PEXCEPTION_REGISTRATION) -1) {
			if (head < StackLimit || head + sizeof(EXCEPTION_REGISTRATION) > StackBase || head & 3) {
				ExceptionRecord->ExceptionFlags |= EH_STACK_INVALID;
				goto exit;
			}
				
			if (head->handler >= StackLimit && head->handler < StackBase) {
				ExceptionRecord->ExceptionFlags |= EH_STACK_INVALID;
				goto exit;
			}
			
			if (!RtlIsValidHandler(head->handler)) {
				ExceptionRecord->ExceptionFlags |= EH_STACK_INVALID;
				goto exit;
			}
			else if (LogExceptions) {
				RtlpLogExceptionHandler(ContextRecord, ExceptionRecord, 0, head, 0x10);
			}
			
			hret = RtlpExecuteHandlerForException(ContextRecord, head, ExceptionRecord, &dispatch, head->handler);
			
			if (LogExceptions) {
				RtlpLogLastExceptionDisposition(highest, hret);
			}
			
			if (head == NULL) {
				ExceptionRecord->ExceptionFlags &= ~EH_NESTED_CALL;
			}
			
			if (hret == DISPOSITION_DISMISS) {
				if (ExceptionRecord->ExceptionFlags & EH_NONCONTINUABLE) {
					exRec.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION;
					exRec.ExceptionFlags = EH_NONCONTINUABLE;
					exRec.ExceptionRecord = ExceptionRecord;
					exRec.NumberParameters = 0;
					RtlRaiseException(&exRec);
					
					if (ExceptionRecord->ExceptionFlags & EH_STACK_INVALID) {
						goto exit;
					}
				}
				else {
					ret = 1;
					break;
				}
			}
			else if (hret == DISPOSITION_CONTINUE_SEARCH) {
				if (ExceptionRecord->ExceptionFlags & EH_STACK_INVALID) {
					goto exit;
				}
			}
			else if (hret == DISPOSITION_NESTED_EXCEPTION) {
				ExceptionRecord->ExceptionFlags |= EH_NESTED_CALL;
				
				if (dispatch > highest) {
					highest = dispatch;
				}
			}
			else { 
					exRec.ExceptionCode = STATUS_INVALID_DISPOSITION;
					exRec.ExceptionFlags = EH_NONCONTINUABLE;
					exRec.ExceptionRecord = ExceptionRecord;
					exRec.NumberParameters = 0;
					RtlRaiseException(&exRec);
			}
			
			head = head->prev;
		}
	}

exit:
	return ret;
}

RtlIsValidHandler()

RtlDispatchException()が呼び出しているRtlIsValidHandler()では、例外が発生したカーネルモジュールが/SafeSEHオプションを付けてコンパイルされているかどうか検証しています。

BOOLEAN RtlIsValidHandler(PEXCEPTION_HANDLER handler) {
	DWORD table_sz;
	LPVOID safeseh_table, base_addr;
	DWORD ret, result_len, exec_flags, high, low;
	MEMORY_BASIC_INFORMATION mbi;
	
	safeseh_table = RtlLookupFunctionTable(handler, &base_addr, &table_sz);

	if (safeseh_table == NULL || table_sz == 0) {
		if (ZwQueryInformationProcess(INVALID_HANDLE_VALUE, 22, &exec_flags, 4, NULL) >= 0) {
			if (!(exec_flags & 0x10)) {
				return 1;
			}
		}
		
		if (NtQueryVirtualMemory(INVALID_HANDLE_VALUE, handler, NULL, &mbi, sizeof(MEMORY_BASIC_INFORMATION), &result_len) < 0) {
			return 1;
		}
		
		if (!(mbi.Protect & PAGE_EXEC_MASK)) {
			RtlInvalidHandlerDetected(handler, -1, -1);
			return 0;
		}
		else if (mbi.Type != SEC_IMAGE) {
			return 1;
		}
		
		RtlCaptureImageExceptionValues(mbi.AllocationBase, &safeseh_table, &table_sz);
		
		if (var_10 == NULL || table_sz == 0) {
			return 1;
		}
		
		return 0;
	}
	else if (safeseh_table == (LPVOID) -1 && table_sz == -1) {
		return 0;
	}
	
	if (table_sz < 0) {
		RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
		return 0;
	}
	
	rel_addr = handler - base_addr;
	high = table_sz;
	low = 0;
	
	do {
		idx = (high + low) / 2;
		
		if (rel_addr < safeseh_table[idx]) {
			if (idx == 0) {
				RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
				return 0;
			}
			
			high = idx - 1;
			
			if (high < low) {
				RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
				return 0;
			}
		}
		else if (rel_addr > safeseh_table[idx]) {
			low = idx + 1;
			
			if (high < low) {
				RtlInvalidHandlerDetected(handler, safeseh_table, table_sz);
				return 0;
			}
		}
		else {
			break;
		}
	} while(1);
	
	return 1;
}

RtlLookupFunctionTable()

RtlIsValidHandler()から呼び出されているRtlLookupFunctionTable()ですが、これはPsLoadedModuleList を順番に辿ることで、どのカーネルモジュール内に例外ハンドラが含まれているか確認しています。

PMODULE_ENTRY base    = PsLoadedModuleList;
PMODULE_ENTRY current = PsLoadedModuleList;
do {
    if (current->ImageBase              > HandlerAddress) goto Next;
    if (current->ImageBase + ImageSize 
    return true; // OK
Next:;
    current = current->Next;
} while(base != current);
return false;    // NG

そのため、PsLoadedModuleListから除外されたカーネルモジュール内で例外が発生した場合、Windowsはどのカーネルモジュールに例外が含まれているか確認できず、例外ハンドラの呼び出しに失敗します。

カーネルrootkitを開発されている方々は要注意ヾ(@⌒ー⌒@)ノ

参考

exception handling routines (xp 32-bit, partial/incomplete) 

Special Thanks

id:egggarden