在使用OD的过程中碰到不懂的汇编指令经常会查下帮助文档,每次都要找到文档的所在目录,在打开好麻烦,还有win7居然不能打开hlp格式文件(当然要想打开需要去装个补丁),却可以打开chm格式的。Win7支持chm格式OD却不支持,刚好我用到的帮助文档是chm格式和一个doc的文档。好吧,自己动手对OD的帮助文档格式进行扩展,在添加一个F1打开帮助文档。
效果如下:
微软的Win32 API reference,己转成CHM:http://www.pediy.com/document/api/win32chm.rar
一、扩展OD帮助文档打开格式
1-1:定位格式字符串
选择帮助文档是一个对话框,GetOpenFileName这个API的功能就是打开文件选择对话框。查下MSDN看下它的参数是一个指针,指向的是OPENFILENAME类型,该类型中第四个成员就是指向过滤的字符串,下面在OD中定位这个API看有没有使用。
断下来后单步跑到0x764AA2C4位置处,对话框还没打开,然后按p跑起来,对话框打开了,证明0x764AA2C7位置的CALL里面会打开对话框。那么EBP+8的地方就是OPENFILENAME类型的指针。观察EBP+8指向的内存值。
反复打开几次文件选择对话框文件过滤字符串都是在0x0018C64C位置中,对话框关闭后这个值会改变(这个是栈空间所以值会变),那么跟下这个值的最终来源位置。通过栈回溯一直找,最后找到_Browsefilename这个函数,在这个函数下断点并看0x0018B5F4中的第4个int位置的值是不是过滤字符串的地址,如果不是就对它下内存写入断点,如果已经是了就还需要往上回溯。一直找到它不是为止。通过内存写入断点找到是下面这行指令把过滤字符地址复制到对应位置:
现在只要hook这行指令即可。
所有代码都是以OD插件方式开发,开发OD需要的两个文件:
头文件:Plugin.h
lib文件:OLLYDBG.LIB
插件开发帮助文档中说了连个必须函数ODBG_Plugindata和ODBG_Plugininit,这两个函数的功能帮助文档有说,使用方式我提供的工程中也有。
1-2:Hook关键一行代码扩展文件选择类型
把0x00476AD2位置的jmp修改为CALL(修改为call是懒得算跳回去的地址,在我的函数中直接用ret回去就行了),跳到我准备的函数中,我的函数如下:
//新过滤字符串
PCHAR g_lpszbuf = NULL;
_export __declspec(naked) void MyOpenHelpDocument()
{
__asm
{
mov eax, g_lpszbuf
mov dword ptr ss:[ebp - 0x2B68], eax
ret
}
}
Hook代码
unsigned int g_pfilter = 0x476AD2;
VOID SetFilter()
{
int i;
FILTER Filter;
unsigned int *pFilterAddress = (unsigned int*)g_pfilter;
DWORD dwOld;
int nCount;
PCHAR pTemp;
g_lpszbuf = (PCHAR)malloc(300);
if (g_lpszbuf == NULL)
{
return;
}
memset(g_lpszbuf, 0, 300);
//新过滤字符串
PCHAR Temp[10];
Temp[0] = "chm帮助文件(*.chm)";
Temp[1] = "*.chm";
Temp[2] = "hlp帮助文件(*.hlp)";
Temp[3] = "*.hlp";
Temp[4] = "文本文件(*.txt)";
Temp[5] = "*.txt";
Temp[6] = "图片(*.jmpg)";
Temp[7] = "*.jmpg";
Temp[8] = "所有(*.*)";
Temp[9] = "*.*";
nCount = 0;
pTemp = g_lpszbuf;
//拷贝准备好的过滤字符串到malloc开辟出来的空间中
for (i = 0; i < 10; i++)
{
do
{
strcpy(pTemp, Temp[i]);
nCount = strlen(Temp[i]) + 1;
} while (FALSE);
if (i != 9)
{
pTemp += nCount;
}
}
//准备Hook指令 (Filter是一个全局变量,自定义的一个6字节结构体)
Filter.m_Comm = 0xE8;
Filter.m_Offset = (int)MyOpenHelpDocument - g_pfilter - 5;
//原始指令是6个字节,我们修改为JMP后是5字节,所以最后一字节修改为NOP
Filter.m_Nop = 0x90;
dwOld = 0;
//打开内存保护并使用memcpy拷贝指令到目标位置,然后还原内存属性
if (VirtualProtect((void*)g_pfilter, 6, PAGE_EXECUTE_READWRITE, &dwOld) == 0)
{
MessageBox(NULL, "Error", "SetHookFail", MB_OK);
return;
}
memcpy((void*)g_pfilter, (void*)&Filter, sizeof(Filter));
VirtualProtect((void*)g_unTargetAddress, 6, dwOld, &dwOld);
}
二、修复不能打开chm格式文件
找到WinHelpA对它进行Hook使用ShellExecute来打开,代码如下:
_export __declspec(naked) void __stdcall OpenHelp()
{
char *pFilePath;
__asm
{
push eax
mov eax , [esp+0xc]
mov pFilePath, eax
pop eax
}
ShellExecute(0, "open", pFilePath/*"calc"*/, "", "", SW_SHOWNORMAL);
__asm
{
retn 16
}
}
Hook代码:
BOOL SetHookWinHelp()
{
DWORD dwOld;
HMODULE hModule = LoadLibrary("user32");
if (hModule == NULL)
{
return FALSE;
}
DWORD pfnAddress = (DWORD)GetProcAddress(hModule, "WinHelpA");
if (pfnAddress == NULL)
{
return FALSE;
}
JMP Jmp;
Jmp.m_Comm = 0xE9;
Jmp.m_offset = (int)OpenHelp - (int)pfnAddress - 5;
dwOld = 0;
if (VirtualProtect((void*)pfnAddress, 5, PAGE_EXECUTE_READWRITE, &dwOld) == 0)
{
MessageBox(NULL, "Error", "SetHookFail", MB_OK);
return FALSE;
}
memcpy((void*)pfnAddress, (void*)&Jmp, sizeof(Jmp));
VirtualProtect((void*)pfnAddress, 5, dwOld, &dwOld);
return TRUE;
}
三、对Od添加一个F1打开帮助文档
Hook窗口过程函数,在我们的函数中判断是不是按下F1,如果是就使用PostMessage发送一个WM_COMMAND消息来给打开帮助文档这个菜单。具体看下面代码:
操作过程
首先要查看“打开帮助文档菜单”的ID号,给OD主窗口下个键盘按下断点,然后点击"打开帮助文档"断在过程函数中,看栈如下图:
得到该菜单的ID号为0x9c8。
实现代码
typedef LRESULT (__stdcall *CALLWINDOWPROC)(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
//新的过程函数
LRESULT MyCallWindowProc(
WNDPROC lpPrevWndFunc, // pointer to previous procedure
HWND hWnd, // handle to window
UINT Msg, // message
WPARAM wParam, // first message parameter
LPARAM lParam
)
{
DWORD wNotifyCode = HIWORD(wParam);
DWORD wID = LOWORD(wParam);
if (wID == 0xe090)
{
OutputDebugString("F1");
//
PostMessage(G_hWin, WM_COMMAND, 0x9C8, NULL);
return TRUE;
}
return pfnNextProcCall(lpPrevWndFunc, hWnd, Msg, wParam, lParam);
}
劫持过程函数的代码写在了OD插件的一个必须函数中,写在这里面主要是有窗口句柄方便。代码如下:
//OD必须函数
extc int _export cdecl ODBG_Plugininit(int ollydbgversion, HWND hw,
ulong *features)
{
G_hWin = hw;
if(ollydbgversion < PLUGIN_VERSION)
{
return -1;
}
pfnNextProcCall = (CALLWINDOWPROC)GetWindowLong(hw, GWL_WNDPROC);
if (pfnNextProcCall == 0)
{
return -1;
}
//Hook窗口过程
if (SetWindowLong(hw, GWL_WNDPROC, (long)MyCallWindowProc) == 0)
{
return -1;
}
return 0;
}
到这里F1和帮助文档格式扩展就写完了,来科锐差不多一年了,从C到C++、汇编、逆向,学习逆向这段时间知道了编译器对C++代码的生成,对面向对象这块又有种豁然开朗的感觉。非常感谢钱老师。最后祝科锐越办越好。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)