[原创]drv experiment : FOLLOW_JMP parse
在nt!*API中, 有些入口地址处附近有5个字节的JMP代码.
用FOLLOW_JMP宏可以算出JMP到的绝对地址.
#define FOLLOW_JMP( JMPADDR ) ((ULONG)(JMPADDR) + 5 + *(ULONG*)((ULONG)(JMPADDR) + 1))
这个宏不好理解, 且是硬编码, 用函数实现了一个FOLLOW_JMP, 通用一些.
根据给定的内核API地址, 给定JMP命令前面的字节数量, 分析从JMP命令得到JMP到的绝对地址.
/// @file \prjGetJmpCmdAddr\main.cpp
/// @brief FOLLOW_JMP 宏不太好理解, 换成函数来分析FOLLOW_JMP的实现
/**
#define FOLLOW_JMP( JMPADDR ) \
((ULONG)(JMPADDR) + 5 + *(ULONG*)((ULONG)(JMPADDR) + 1))
FOLLOW_JMP 适用于 nt!FsRtlIsPagingFile
*/
#include <Ntddk.h>
/**
nt!FsRtlIsPagingFile:
804ed978 8bff mov edi,edi
804ed97a 55 push ebp
804ed97b 8bec mov ebp,esp
804ed97d 5d pop ebp
804ed97e e907dd0100 jmp nt!MmIsFileObjectAPagingFile (8050b68a)
nt!MmIsFileObjectAPagingFile:
8050b68a 8bff mov edi,edi
*/
/// nt!FsRtlIsPagingFile API入口附近, JMP命令前面有6个字节
#define BYTES_CNT_BEFORE_JMP_ON_FS_RTL_IS_PAGING_FILE 6
/// JMP 命令是5个字节
#define BYTES_CNT_JMP_CMD 5
/// JMP命令字
#define ASM_CMD_JMP 0xE9
BOOLEAN fnGetApiJmpAddr(wchar_t * pcApiName,
ULONG uAddrBeforeJmpCmd,
ULONG * pJmpAddr);
PVOID GetKernelApiAddress(wchar_t * pcApiName);
/// @fn fnGetAddrJmpTo
/// @brief 查找JMP命令要跳到的绝对地址
/// @param ULONG ulAddrJmpCmd, JMP命令地址
/// @return ULONG
/// @retval 0, ulApiAddr地址后面不是JMP指令或者失败
/// @retval 非0, 得到了ulApiAddr之后JMP指令要跳到的地址
ULONG fnGetAddrJmpTo(ULONG ulAddrJmpCmd);
extern "C"
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
ULONG pJmpAddr = 0;
KdPrint((">> DriverEntry\r\n"));
if (fnGetApiJmpAddr(
L"FsRtlIsPagingFile",
BYTES_CNT_BEFORE_JMP_ON_FS_RTL_IS_PAGING_FILE,
&pJmpAddr))
{
DbgPrint("pJmpAddr = 0x%X\r\n", pJmpAddr);
}
else
DbgPrint("Test_GetApiJmpAddr failed\r\n");
KdPrint(("<< DriverEntry\r\n"));
return STATUS_SUCCESS;
}
BOOLEAN fnGetApiJmpAddr(wchar_t * pcApiName,
ULONG uAddrBeforeJmpCmd,
ULONG * pJmpAddr)
{
ULONG ulApiAddr = 0; ///< API地址
ULONG ulAddrJmpCmd = 0; ///< JMP命令地址
if ((NULL == pcApiName) || (NULL == pJmpAddr))
return FALSE;
ulApiAddr = (ULONG) GetKernelApiAddress(pcApiName);
if (!MmIsAddressValid((PVOID)(ulApiAddr)))
{
return FALSE;
}
ulAddrJmpCmd = ulApiAddr + uAddrBeforeJmpCmd;
/**
kd> dv /V ulApiAddr
f7a4cc60 @ebp-0x04 ulApiAddr = 0x804ed978
kd> dv /V ulAddrBeforeJmpCmd
f7a4cc5c @ebp-0x08 ulAddrBeforeJmpCmd = 0x804ed97e
kd> uf nt!FsRtlIsPagingFile
nt!FsRtlIsPagingFile:
804ed978 8bff mov edi,edi
804ed97a 55 push ebp
804ed97b 8bec mov ebp,esp
804ed97d 5d pop ebp
804ed97e e907dd0100 jmp nt!MmIsFileObjectAPagingFile (8050b68a)
nt!MmIsFileObjectAPagingFile:
8050b68a 8bff mov edi,edi
*/
/// 实验可知: ulApiAddr + uAddrBeforeJmpCmd 是JMP命令的地址
if (MmIsAddressValid((PVOID)(ulAddrJmpCmd)))
{
/// *pJmpAddr is 8050b68a ~
*pJmpAddr = fnGetAddrJmpTo(ulAddrJmpCmd);
return TRUE;
}
return FALSE;
}
ULONG fnGetAddrJmpTo(ULONG ulAddrJmpCmd)
{
BOOLEAN bRc = FALSE;
UCHAR ucCmdByte = 0; ///< JMP命令的命令字
ULONG ulAddrJmpCmdContent = 0; ///< JMP命令的内容所在的地址
ULONG ulAddrOffset = 0; ///< JMP命令要跳到的相对地址
ULONG ulAddrFollowJmp = 0; ///< JMP命令要跳到的绝对地址
__try
{
ucCmdByte = *(UCHAR *)(ulAddrJmpCmd);
if (ASM_CMD_JMP == ucCmdByte)
{
ulAddrJmpCmdContent = (ULONG)(ulAddrJmpCmd) + 1;
ulAddrOffset = *(ULONG*)(ulAddrJmpCmdContent);
ulAddrFollowJmp = ulAddrJmpCmd + BYTES_CNT_JMP_CMD + ulAddrOffset;
bRc = TRUE; ///< 全部执行成功, 没有异常发生
}
}
__finally
{
}
return bRc ? ulAddrFollowJmp : 0;
}
PVOID GetKernelApiAddress(wchar_t * pcApiName)
{
UNICODE_STRING str;
if (NULL == pcApiName)
return NULL;
RtlInitUnicodeString(&str, pcApiName);
/// MmGetSystemRoutineAddress
/// If the function name can be resolved,
/// the routine returns a pointer to the function.
/// Otherwise, the routine returns NULL.
return MmGetSystemRoutineAddress(&str);
}
