当前位置: 首页 > news >正文

《Windows PE》9.2 动态加载技术-获取kernel32.dll基址

上一节中我们介绍了如何动态调用DLL的方法。首先调用LoadLibrary函数动态加载DLL,然后调用GetProcAddress通过函数名获取DLL内的函数地址。但是LoadLibrary和GetProcAddress函数的调用仍然依赖于PE文件内的导入表和函数地址表的存在。在操作系统加载PE文件到内存时获取这两个函数的地址。本节我们将介绍一种完全不依赖于导入表和函数地址表的方法,实现对DLL动态链接库中函数的调用。简单来说,需要经过以下三步:

步骤1获取kernel32.dll的基地址。

步骤2获取GetProcAddress函数的地址(进一步获取LoadLibrary函数的地址)。

步骤3在代码中使用获取的函数地址编程。

本节必须掌握的知识点:

        获取kernel32.dll基址

        获取GetProcAddress 地址

        获取API函数地址

        自定义资源

9.2.1 获取kernel32.dll基址

       动态加载技术中必须获取的LoadLibrary和GetProcAddress函数都是kernel32.dll的导出函数。因此,我们要获取这两个函数地址的前提是必须获取kernel32.dll基址。接下来我们分别介绍4种不同的获取kernel32.dll基址方法。

       ■方法一:暴力搜索

几乎所有的win32可执行文件(pe格式文件)运行的时候都加载kernel32.dll,可执行文件进入入口点执行后esp中存放的一般是Kernel32. dll中的某个地址,沿着这个地址向上查找就可以找到kernel32的基地址。当系统服务进程调用CreateProcess函数在完成创建应用程序后,会先将一个返回地址压入到堆栈顶端,而这个返回地址恰好在Kernel32.dll中,利用这个原理我们可以顺着这个返回地址按64KB大小往地址搜索,那么我们一定可以找到Kernel32模块的基地址。WinMain可以看做是Windows调用CreateProcess创建的一个子程序,其压入的返回地址就是Ret返回地址。从堆栈中的 Ret 地址转换 Kernel32.dll 的基址,并在 Kernel32.dll的导出表中查找 GetProcAddress 函数的入口地址。

       【注】这种方法在win 64位系统下获取地址错误。

       我们通过实验来检测这种方法的有效性。

      

实验六十五:获取kernel32.dll基址方法一

       利用CreateProcess函数的返回地址获取Kernel32.dll基址。

       ●C语言实现

KernelBase1.c
/*------------------------------------------------------------------------FileName:KernelBase1.c实验65:方法一(暴力搜索)仅支持Windows 32位系统(c) bcdaren, 2024
-----------------------------------------------------------------------*/ #include <windows.h>BOOL CALLBACK ProcDlgMain(HWND, UINT, WPARAM, LPARAM);
DWORD _GetKernelBase(DWORD _dwKernelRet);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)//默认填写
{HANDLE hDllKernel32;DWORD address;const TCHAR szCaption[] = TEXT("kernal32.dll");TCHAR szText[256];//方法1:CreateProcess函数在完成创建应用程序后,会先将一个返回地址压入到堆栈顶端,_asm {mov eax,dword ptr ss:[esp]mov address,eax}//调用_GetKernelBase获取Kernel32.dll 的基址hDllKernel32 = (HANDLE)_GetKernelBase(address); //测试wsprintf(szText, TEXT("kernal32.dll的基址为:%p"), hDllKernel32);MessageBox(NULL, szText, szCaption, MB_OK);return 0;
}//在内存中扫描 Kernel32.dll 的基址
DWORD _GetKernelBase(DWORD _dwKernelRet)
{DWORD dwReturn = 0;//查找Kernel32.dll的基地址//对于windows loader来说,装载exe, dll等文件的粒度是64KB对齐DWORD address = _dwKernelRet & 0xffff0000;while (TRUE){if (*(PWORD)address == IMAGE_DOS_SIGNATURE){if (*(PWORD)(address + *(PDWORD)(address + 0x003c)) == 
IMAGE_NT_SIGNATURE){dwReturn = address;break;}}address -= 0x010000;	//每次一页间隔搜寻//特殊的系统DLL:采用Copy - On - Write策略,相对固定的基址,//这些基址值在各个不同系统中都满足的条件是 > 0x70000000if (address < 0x070000000)break;}return dwReturn;
}

运行:

图9-2 获取kernel32.dll基址方法一

  总结

       这个结果显然是错误的。我们来分析一下错误的原因:

       笔者使用VS2017编译,版本为X86 Debug版,使用内联汇编,获取栈顶返回地址address的值为0x0122123a,这个地址似乎不是很合理,通常kernel32.dll的地址处于较高处。调用_GetKernelBase函数获取PE文件头部的起始地址,返回值为0,这肯定是一个错误的结果。

       如果我们编译X86 release版本,获取栈顶返回地址address的值为,肯定是错误的地址。

       因为我们使用了VS编译工具,编译后的程序会添加CRT运行时库的一些代码,而且内联汇编中的ESP寄存器的值也并非我们想要的结果——存储CreateProcess函数返回地址。

       X64版本不支持内联汇编,而且并不是固定的基址,因此使用这种方法一定是错误的。有兴趣的读者可以自己测试一下。

       为了排除VS编译工具的干扰,我们测试一下纯汇编代码实现。

●汇编语言实现

;------------------------
;FileName:KernelBase1.asm
;实验65:方法一(暴力搜索)
;仅支持Windows 32位系统
;(c) bcdaren, 2024
;------------------------.386.model flat,stdcalloption casemap:noneinclude    windows.inc
include    user32.inc
includelib user32.lib
include    kernel32.inc
includelib kernel32.lib;数据段.data
szText      	db  256 dup(0)
szCaption  	db  'kernal32.dll',0
szBuffer		db "kernal32.dll的基址为:%p",0
kernel32Base	dd  ?;代码段.code
;------------------------------------
; 根据kernel32.dll中的一个地址获取它的基地址
;------------------------------------
_getKernelBase  proc _dwKernelRetAddresslocal @dwRetpushadmov @dwRet,0mov edi,_dwKernelRetAddressand edi,0ffff0000h  ;查找指令所在页的边界,以1000h对齐.repeat.if word ptr [edi]==IMAGE_DOS_SIGNATURE  ;找到kernel32.dll的dos头mov esi,ediadd esi,[esi+003ch].if word ptr [esi]==IMAGE_NT_SIGNATURE ;找到kernel32.dll的PE头标识mov @dwRet,edi.break.endif.endifsub edi,010000h.break .if edi<070000000h.until FALSEpopadmov eax,@dwRetret
_getKernelBase  endp   ;===================================
start:; 从堆栈中的 Ret 地址转换 Kernel32.dll 的基址;Ret地址是ExitThread函数,;然后ExitThread函数又会自动去调用ExitProcess终止程序执行;因此Ret地址肯定位于Kernel32.dll模块中mov eax,dword ptr [esp]invoke _getKernelBase,eaxmov kernel32Base,eaxinvoke wsprintf,addr szText,addr szBuffer,eaxinvoke MessageBox,NULL,addr szText,addr szCaption,MB_OKret	;注意使用ret返回end start# makefile文件# 宏定义
NAME = KernelBase1
EXE = $(NAME).exe				#输出文件
OBJS = $(NAME).obj				#目标文件
ML_FLAG = /c /coff				#编译选项
LINK_FLAG = /subsystem:windows	#链接选项# 定义依赖关系和执行命令
$(EXE):$(OBJS)link $(LINK_FLAG) $(OBJS)# 定义汇编编译和资源编译默认规则
.asm.obj:ml $(ML_FLAG) $<
.rc.res:rc $<# 清除临时文件
clean:del *.objdel *.res

运行:

图9-3 方法一的汇编语言实现

  总结

       上述汇编版本为X86汇编的实现,当系统进程调用CreateProcess函数创建子进程(本示例程序)时,加载并初始化示例进程后执行,当执行完毕后,可以调用ExitProcess函数返回操作系统。

【注意】示例程序并没有这样做,而是直接使用ret指令返回,ret指令返回的地址就是当初CreateProcess函数调用时压入堆栈的返回地址。这个ret地址是ExitThread函数地址,然后ExitThread函数又会自动去调用ExitProcess终止程序执行。因此Ret地址肯定位于Kernel32.dll模块中。

       由于虚拟内存空间以4KB为单位对齐,因此我们将ret返回地址取整,然后以4KB(1000h)为单位遍历内存空间,当遍历到DOS头特征和PE头特征时,就可以确定该地址为kernel32.dll基址。

       请读者编写程序,并在OD调试器的内存映射窗口核对64位和32位Windows系统环境中获取的kernel32.dll基址是否正确。

       此外,请读者尝试编译X64版本的汇编程序,并测试其正确性(获取ret返回地址失败)。

结论:利用ret返回地址的方法获取kernel32.dll的方法仅针X86 32位汇编程序有效。

方法二:使用SEH的链表来查找

实验六十六:方法二(使用SEH的链表来查找)

       ●内联汇编

/*------------------------------------------------------------------------FileName:KernelBase2.c实验66:方法二(使用SEH的链表来查找)(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>BOOL CALLBACK ProcDlgMain(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)//默认填写
{HANDLE hDllKernel32;DWORD address;const TCHAR szCaption[] = TEXT("kernal32.dll");TCHAR szText[256];/*方法2:使用SEH的链表来查找原理:在SEH中默认的unhandled exception hander 是利用kernel32.DLL中的一个函数设置的。因此可以历遍所有的exception hander,找到最后一个成员,该成员的前4个字节是0ffffffffh, 后4个字节是kernel32.dll中的函数。	注:这种方法仅支持Windows 32位系统,Windows 64位系统获取的是ntdll.dll的基址*/_asm{mov esi, fs:[0]lodsdL1 :inc eaxjz L2dec eaxxchg esi, eaxlodsdjmp L1L2 :lodsdL3 :dec eaxxor ax, axcmp WORD ptr[eax], 0x05A4Djnz L3mov address, eaxmov hDllKernel32, eax}wsprintf(szText, TEXT("kernal32.dll的基址为:%p"), hDllKernel32);MessageBox(NULL, szText, szCaption, MB_OK);return 0;
}

运行:

      

       图9-4 使用SEH的链表来查找

  总结

异常处理链表就是Windows系统提供的处理异常的机制,当系统遇到一个不知道如何处理的异常时就会查找异常处理链表,找到对应的异常处理程序,把保存的处理程序地址赋给eip,并执行处理程序,避免系统崩溃,异常处理链表的最后一项是默认异常处理函数UnhandledExceptionFilter,因为UnhandledExceptionFilter在kernel32.dll中,所以从UNhandledExceptionFilter地址向上搜索即可找到kernel32的基地址。

在32位系统中,kernel32.dll是一个核心系统库,包含了大量的系统函数和API接口,包括异常处理等。而在64位系统中,ntdll.dll库更加底层,包含了操作系统内部的核心函数和服务,包括异常处理、线程管理、内存管理等。

随着Windows操作系统从32位逐渐迁移到64位架构,一些核心功能和服务被移到了更底层的ntdll.dll库中,以更好地支持64位体系结构的特性和需求。这也包括异常处理函数UnhandledExceptionFilter的迁移。

【注】 fs:[0]为SEH异常处理链的首地址。SEH异常处理链表搜索的方法仅适用于Windows 32位系统。

方法三:利用PEB结构来查找

实验六十七:方法三(利用PEB结构来查找)

●32位程序

/*------------------------------------------------------------------------FileName:KernelBase3.c实验67:方法三(利用PEB结构来查找)(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>BOOL CALLBACK ProcDlgMain(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)//默认填写
{HANDLE hDllKernel32;DWORD address;const TCHAR szCaption[] = TEXT("kernal32.dll");TCHAR szText[256];/*方法2:利用PEB结构来查找原理:FS段寄存器作为选择子指向当前的TEB结构,在TEB偏移0x30处是PEB指针。而在PEB偏移的0x0c处是指向PEB_LDR_DATA结构的指针,位于 PEB_LDR_DATA结构偏移0x1c处,是一个叫InInitialzationOrderModuleList的成员,它是指向LDR_MODULE链表结构中相应的双向链表头部的指针,该链表加载的DLL的顺序是ntdll.dll,kernel32.dll.因此该成员所指的链表偏移0x08处为kernel32.dll地址。注:支持win32和64位系统下的X86程序。*//*1.加载顺序的方式:*/_asm{mov  eax, fs:[0x30]		; 指向PEB的指针mov  eax, [eax + 0x0c]	; 指向PEB_LDR_DATA的指针
; 根据PEB_LDR_DATA得出InLoadOrderModuleList的Flink字段mov  esi, [eax + 0x0c]	lodsdmov eax, [eax]; 指向下一个节点mov eax, [eax + 18h]	; Kernel32.dll的基地址mov address, eaxmov hDllKernel32, eax}/*2.内存顺序的方式:_asm{mov  eax, fs:[0x30]		; 指向PEB的指针mov  eax, [eax + 0x0c]	; 指向PEB_LDR_DATA的指针
; 根据PEB_LDR_DATA得出InMemoryOrderModuleList的Flink字段mov  esi, [eax + 0x14]	lodsdmov eax, [eax]			; 指向下一个节点mov eax, [eax + 10h]	; Kernel32.dll的基地址mov address, eaxmov hDllKernel32, eax}*//*3.初始化顺序的方式:KernelBase.dll的基地址_asm{mov  eax, fs:[0x30]		; 指向PEB的指针mov  eax, [eax + 0x0c]	; 指向PEB_LDR_DATA的指针
; 根据PEB_LDR_DATA得出InInitializationOrderModuleList的Flink字段mov  esi, [eax + 0x1c]	lodsdmov eax, [eax + 0x08]	; Kernel.dll的基地址mov address,eaxmov hDllKernel32,eax}*/wsprintf(szText, TEXT("kernal32.dll的基址为:%p"), hDllKernel32);MessageBox(NULL, szText, szCaption, MB_OK);return 0;
}

运行:

       图9-5 利用PEB结构来查找

  总结

       在32位程序中,分别使用了3种不同的方法在PEB进程环境块中获取kernel32.dll的基址。3种方式分别为:

       1.加载顺序的方式。

       2.内存顺序的方式。

       3.初始化顺序的方式。

       在NT内核系统中fs寄存器指向TEB结构,TEB+0x30处指向PEB结构,PEB+0x0c处指向PEB_LDR_DATA结构。

       此方法是通过TEB获得PEB结构地址,然后再获得PEB_LDR_DATA结构地址,然后遍历模块列表,查找kernel32.dll模块的基地址。

TEB是线程环境块(Thread Environment Block)结构, 我们的fs段选择子所对应的段指向TEB,也就是fs:[0]指向TEB.那么TEB的ProcessEnvironmentBlock结构成员指向我们的PEB进程环境块结构(Process Environment Block),然后通过PEB结构来获得PEB_LDR_DATA。 接下来我们通过windbg来查看下相关结构。

●查看TEB结构,通过windbg的dt命令。

lkd> dt _TEBnt!_TEB+0x000 NtTib            : _NT_TIB+0x01c EnvironmentPointer : Ptr32 Void+0x020 ClientId         : _CLIENT_ID+0x028 ActiveRpcHandle  : Ptr32 Void+0x02c ThreadLocalStoragePointer : Ptr32 Void+0x030 ProcessEnvironmentBlock : Ptr32 _PEB

可以看到TEB结构的0x30偏移处存储的我们的PEB结构的地址

●PEB和TEB结构

1.TEB结构

 //// Thread Environment Block (TEB)//typedef struct _TEB{NT_TIB Tib;                             /* 00h */PVOID EnvironmentPointer;               /* 1Ch */CLIENT_ID Cid;                          /* 20h */PVOID ActiveRpcHandle;                 /* 28h */PVOID ThreadLocalStoragePointer;          /* 2Ch */struct _PEB *ProcessEnvironmentBlock;        /* 30h */ULONG LastErrorValue;                     /* 34h */ULONG CountOfOwnedCriticalSections;      /* 38h */PVOID CsrClientThread;                  /* 3Ch */struct _W32THREAD* Win32ThreadInfo;      /* 40h */ULONG User32Reserved[0x1A];             /* 44h */ULONG UserReserved[5];                 /* ACh */PVOID WOW32Reserved;                    /* C0h */LCID CurrentLocale;                       /* C4h */ULONG FpSoftwareStatusRegister;         /* C8h */PVOID SystemReserved1[0x36];            /* CCh */LONG ExceptionCode;                     /* 1A4h */struct _ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer; /* 1A8h */UCHAR SpareBytes1[0x28];                  /* 1ACh */GDI_TEB_BATCH GdiTebBatch;               /* 1D4h */CLIENT_ID RealClientId;                     /* 6B4h */PVOID GdiCachedProcessHandle;            /* 6BCh */ULONG GdiClientPID;                      /* 6C0h */ULONG GdiClientTID;                      /* 6C4h */PVOID GdiThreadLocalInfo;               /* 6C8h */ULONG Win32ClientInfo[62];              /* 6CCh */PVOID glDispatchTable[0xE9];               /* 7C4h */ULONG glReserved1[0x1D];                 /* B68h */PVOID glReserved2;                       /* BDCh */PVOID glSectionInfo;                      /* BE0h */PVOID glSection;                          /* BE4h */PVOID glTable;                            /* BE8h */PVOID glCurrentRC;                       /* BECh */PVOID glContext;                          /* BF0h */NTSTATUS LastStatusValue;                 /* BF4h */UNICODE_STRING StaticUnicodeString;       /* BF8h */WCHAR StaticUnicodeBuffer[0x105];         /* C00h */PVOID DeallocationStack;                  /* E0Ch */PVOID TlsSlots[0x40];                      /* E10h */LIST_ENTRY TlsLinks;                       /* F10h */PVOID Vdm;                              /* F18h */PVOID ReservedForNtRpc;                  /* F1Ch */PVOID DbgSsReserved[0x2];                /* F20h */ULONG HardErrorDisabled;                 /* F28h */PVOID Instrumentation[14];                 /* F2Ch */PVOID SubProcessTag;                     /* F64h */PVOID EtwTraceData;                      /* F68h */PVOID WinSockData;                      /* F6Ch */ULONG GdiBatchCount;                    /* F70h */BOOLEAN InDbgPrint;                     /* F74h */BOOLEAN FreeStackOnTermination;          /* F75h */BOOLEAN HasFiberData;                   /* F76h */UCHAR IdealProcessor;                     /* F77h */ULONG GuaranteedStackBytes;              /* F78h */PVOID ReservedForPerf;                    /* F7Ch */PVOID ReservedForOle;                  /* F80h */ULONG WaitingOnLoaderLock;             /* F84h */ULONG SparePointer1;                    /* F88h */ULONG SoftPatchPtr1;                     /* F8Ch */ULONG SoftPatchPtr2;                     /* F90h */PVOID *TlsExpansionSlots;                  /* F94h */ULONG ImpersionationLocale;                /* F98h */ULONG IsImpersonating;                   /* F9Ch */PVOID NlsCache;                          /* FA0h */PVOID pShimData;                        /* FA4h */ULONG HeapVirualAffinity;                 /* FA8h */PVOID CurrentTransactionHandle;           /* FACh */PTEB_ACTIVE_FRAME ActiveFrame;           /* FB0h */PVOID FlsData;                            /* FB4h */UCHAR SafeThunkCall;                     /* FB8h */UCHAR BooleanSpare[3];                    /* FB9h */} TEB, *PTEB;

2.PEB结构 

typedef struct _PEB{UCHAR InheritedAddressSpace; // 00hUCHAR ReadImageFileExecOptions; // 01hUCHAR BeingDebugged; // 02hUCHAR Spare; // 03hPVOID Mutant; // 04hPVOID ImageBaseAddress; // 08hPPEB_LDR_DATA Ldr; // 0ChPRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10hPVOID SubSystemData; // 14hPVOID ProcessHeap; // 18hPVOID FastPebLock; // 1ChPPEBLOCKROUTINE FastPebLockRoutine; // 20hPPEBLOCKROUTINE FastPebUnlockRoutine; // 24hULONG EnvironmentUpdateCount; // 28hPVOID* KernelCallbackTable; // 2ChPVOID EventLogSection; // 30hPVOID EventLog; // 34hPPEB_FREE_BLOCK FreeList; // 38hULONG TlsExpansionCounter; // 3ChPVOID TlsBitmap; // 40hULONG TlsBitmapBits[0x2]; // 44hPVOID ReadOnlySharedMemoryBase; // 4ChPVOID ReadOnlySharedMemoryHeap; // 50hPVOID* ReadOnlyStaticServerData; // 54hPVOID AnsiCodePageData; // 58hPVOID OemCodePageData; // 5ChPVOID UnicodeCaseTableData; // 60hULONG NumberOfProcessors; // 64hULONG NtGlobalFlag; // 68hUCHAR Spare2[0x4]; // 6ChLARGE_INTEGER CriticalSectionTimeout; // 70hULONG HeapSegmentReserve; // 78hULONG HeapSegmentCommit; // 7ChULONG HeapDeCommitTotalFreeThreshold; // 80hULONG HeapDeCommitFreeBlockThreshold; // 84hULONG NumberOfHeaps; // 88hULONG MaximumNumberOfHeaps; // 8ChPVOID** ProcessHeaps; // 90hPVOID GdiSharedHandleTable; // 94hPVOID ProcessStarterHelper; // 98hPVOID GdiDCAttributeList; // 9ChPVOID LoaderLock; // A0hULONG OSMajorVersion; // A4hULONG OSMinorVersion; // A8hULONG OSBuildNumber; // AChULONG OSPlatformId; // B0hULONG ImageSubSystem; // B4hULONG ImageSubSystemMajorVersion; // B8hULONG ImageSubSystemMinorVersion; // C0hULONG GdiHandleBuffer[0x22]; // C4hPVOID ProcessWindowStation; // ???} PEB, *PPEB;

原理:在NT内核系统中fs寄存器指向TEB结构,TEB+0x30处指向PEB结构,PEB+0x0c处指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x1c处存放一些动态链接库地址,第一个指向ntdl.dll,第二个就是kernel32.dll的基地址了。

【注意】fs:[0x30]仅限Windows 32位系统。

●64位程序

1.内联汇编函数kernelBase.asm

public KernelHandleCPUX64 = 1.code
KernelHandle proc
IFDEF CPUX64mov rax, gs:[60h]mov rax, [rax + 18h]mov rax, [rax + 30h]mov rax, [rax]mov rax, [rax]mov rax, [rax + 10h]
ENDIFret
KernelHandle endp
end

UniversalkernelBase.c

/*------------------------------------------------------------------------FileName:UniversalkernelBase.c实验67:方法三(利用PEB结构来查找)获取X64 kernel32.dll的基址(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#define CPUX64 
EXTERN_C PVOID64 _cdecl KernelHandle();int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{const TCHAR szText[] = TEXT("X64 kernel32.dll的基地址为%p");static TCHAR szBuffer[256];HMODULE hKernel64 = NULL;#ifdef CPUX64hKernel64 = (HMODULE)(KernelHandle());//输出模块基地址wsprintf(szBuffer, szText, hKernel64);MessageBox(NULL, szBuffer, NULL, MB_OK);
#endifExitProcess(0);return 0;
}

运行:

      

       图9-6 利用PEB结构来查找X64 kernel32.dll基址

  总结

       在Windows 64位系统下,gs:[60h]指向PEB进程环境块。

       由于Winodws 64位系统不再支持内联汇编,因此需要改成内联函数的形式。将汇编代码改为独立的kernelBase.asm模块。

●asm文件编译VS配置

1.asm文件右键--属性,配置如下,点击应用。

                                   图9-7 X64内联函数VS配置一

2.自定义生成工具,设置命令行和输出项

                                               图9-8 X64内联函数VS配置二

命令行:ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm

输出:$(IntDir)%(fileName).obj 

3、内联汇编函数调用 

EXTERN_C PVOID64 _cdecl KernelHandle();

hKernel64 = (HMODULE)(KernelHandle()); 


http://www.mrgr.cn/news/56634.html

相关文章:

  • TwinCAT3下位机配置EAP通讯传递与接收变量
  • javaweb-mybatis之动态sql
  • C# 文档打印详解与示例
  • C语言 | Leetcode C语言题解之第493题翻转对
  • PGCM是什么证书?有什么作用?
  • 文件误删并清空回收站:全面解析与高效恢复策略
  • 自动化运维概述
  • 推荐一款测试软硬件系统信息的工具:AIDA64
  • 字符串拼接在Python中的最佳实践
  • C# 异常处理与调试技巧
  • 一键找出图像中物体的角点(论文复现)
  • 搭建微信AI机器人
  • Python流程控制专题:while、break与continue
  • C++学习笔记----9、发现继承的技巧(四)---- 多态继承(3)
  • 信号处理入门与实战指南
  • 数据结构修炼——树?二叉树?堆?从入门到代码实现,第二弹!!!
  • 【Spring MVC】请求参数的获取
  • C++ | Leetcode C++题解之第501题二叉搜索树中的众数
  • Construmart借力SNP全面升级SAP S/4HANA和 SAP CAR 改进零售业务流程
  • 【Linux 从基础到进阶】性能测试工具使用(sysbench、fio等)
  • 出现 master -> master (non-fast-forward) error: failed to push some ref 解决方法
  • 【前端】如何制作一个自己的网页(17)无序列表
  • MYSQL-查看创建的事件event语法(十)
  • 推荐一个开源非线性视频编辑器:Kdenlive
  • TwinCAT3下位机配置EAP通讯传递与接收变量
  • jEasyUI 创建自定义视图