使用 Visual Basic 通过 32 位 地址访问内存(中英对照)

    技术2022-05-11  69

    使用 Visual Basic 通过 32 位 地址访问内存

    2001年7月6日

    马尼拉,菲律宾

    作者:Chris Vega [gwapo@models.com]

      当我们谈论“真的”指针和内存地址,我们大都会想到 Visual Basic 的局限性,比如,由于 VB 没有作为变量声明的指针数据类型,它不能直接访问内存。当某些场合需要一个变量的“地址”而不是它的值的时候,这一点混淆就显得特别明显。例如,那个变量位于内存(当前进程、其它进程或者动态链接库的虚拟空间)中的何处。

      是的,VB 确实“没有”指针变量,但是你是否曾试过将一个正规的 VB 数据类型转变为一个指针?你是否认为这是不可能的?好吧,还是再想一下,因为在 Visual Basic 中(从发行版本5开始),Microsoft 提供了一系列便利的函数以将你的正规变量转换为指针,它们是:

    1] VarPtr - 返回一个变量或者数组元素的地址   StrPtr - 返回字符串的地址

      Visual Basic 中除字符串以外的变量,都位于它的内存位置上,你可以通过调用 VarPtr 函数获取这个变量的地址。字符串实际上是作为 BSTR 储存的,这是一个指向“字符数组的指针”的指针,这样你就需要 StrPtr 以得到“字符数组的指针”的地址,而不是用 VarPtr 获得 BSTR 的地址。

    范例: Dim ptrMyPointer As Long Dim intMyInteger As Integer Dim strMyString As String * 25

     ' 这就是一个调用 ptrMyPointer = VarPtr(intMyInteger) ' 将内存中 intMyInteger 这个变量的32位地址赋予 ptrMyPointer

     strMyString = "变量的地址:" & Hex(ptrMyPointer) MsgBox strMyString

     ' 这是另一个调用 ptrMyPointer = StrPtr(strMyString) ' 给出字符数组首元素的地址,例如,字符串的第一个字母。

    2] VarPtrArray - 返回变量数组的地址   VarPtrStringArray - 返回字符传数组的地址

      Visual Basic 中数组被包存在 SAFEARRAY 结构中,你需要使用 VarPtrArray 函数以获取数组的地址,但是在使用该函数之前,你必须手工把它从 msvbvm50.dll 中声明到 VB 程序中。

    范例:

     ' 对于 VB 5 ' ======== Declare Function VarPtrArray Lib "msvbvm50.dll" Alias "VarPtr" (Var() as Any) As Long

     ' 对于 VB 6 ' ======== Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (Var() as Any) As Long

     ' 调用

     Dim lngSafeArrayAddress As Long Dim lngArrayOfLongs(6) As Long Dim i As Long

     Randomize For i = 0 to 6  lngArrayOfLongs = Int(Rnd * &HFFFF) Next

     lngSafeArrayAddress = VarPtrArray(lngArrayOfLongs())

     ' 返回数组 lngArrayOfLongs 的安全地址,s of an Array , you ' 你可以将这些地址用于快速排序或其它用途。

      事实上,VarPtrStringArray 更难以用于你的程序中,因为你需要创建一个类型库并手工将该类型库引用到 VB 程序中。要做一个类型库,你需要一个 MIDL 编译器,它是一个用于将 *.odl 文件编译成类型库的命令行工具。  对于 VB5,创建一个文本文件并且保存为 VB5StrPtr.odl,加入以下内容:

    ----------开始剪切--------------------------------------------------#define RTCALL _stdcall[uuid(6E814F00-7439-11D2-98D2-00C04FAD90E7),lcid (0), version(5.0), helpstring("VarPtrStringArray Support for VB5")]library PtrLib{ importlib ("stdole2.tlb"); [dllname("msvbvm50.dll")] module ArrayPtr   {    [entry("VarPtr")]    long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);   }} ----------结束剪切-------------------------------------------------

      用以下命令编译: MIDL /t VB5StrPtr.odl

      对于 VB6,创建一个文本文件并且保存为 VB6StrPtr.odl,加入以下内容:

    -----------开始剪切--------------------------------------------------#define RTCALL _stdcall[uuid(C6799410-4431-11d2-A7F1-00A0C91110C3),lcid (0), version(6.0), helpstring("VarPtrStringArray Support for VB6")]library PtrLib{ importlib ("stdole2.tlb"); [dllname("msvbvm60.dll")] module ArrayPtr   {    [entry("VarPtr")]    long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);   }} ----------结束剪切-------------------------------------------------

      用以下命令编译: MIDL /t VB6StrPtr.odl

      现在,你有了类型库,将该类型库引用到 VB 程序中,然后你可以用以下方式获取字符串数组:

    Dim MyArrayOfStrings(3) As StringDim AddressOfArray As LongMyArrayOfStrings(0)="Chris"MyArrayOfStrings(1)="Vega"MyArrayOfStrings(2)="gwapo@models.com"

    ' 调用AddressOfArray = VarPtrStringArray ( MyArrayOfStrings() )

    ' 给出数组首元素的地址,而且是该首元素的第一个字符,' 例如,这里是字符“C”在内存中的地址

    ' *** 怎样?你没有 MIDL 编译器?或者你不愿麻烦自己创建类型库并手工引用?' 这里有一种足够容易的简单方法。' 因为 StrPtr 函数具有获得字符串地址的能力,而字符串数组的元素全部都是字符串,' 所以你应该清楚了,你可以对数组的首元素进行这个调用

    AddressOfArray = StrPtr ( MyArrayOfStrings(0) )

    ' 返回更上述方法完全相同的结果

     

    3] ObjPtr - 返回一个对象的地址

      面向对象编程由众多对象组成,而这些对象和它的众多属性一起保存在内存中,作为一种结构化的布局。你需要调用 ObjPtr 函数来获取它的位置。

    范例: ' 你要知道 Form1 处于内存何处,本方法告诉你它在线程中的地址

     Dim objMyObject As New Form1 MsgBox "Form1 位于:" & Hex( ObjPtr( objMyObject ) )

     

      好,到此为止你一定在想:无论如何,怎么才能把这些地址变成实际有用的东西呢?其实如果你这样想答案就很清楚了:地址是一个内存中的位置,而你的变量中保存的就是一个内存中的位置,并且有它本身在内存中的位置。搞糊涂了?我们让它简单一点,你可以简单的认为这个地址是保存数据的位置,数据是可读写的,而你需要通过这个地址来读写它。唔,Visual Basic 能够做这种事情吗?

      不能,如果你只是简单的考虑 Visual Basic 的能力的话,但是你的程序可以使用 API 函数。我现在谈到的 API 是从 KERNEL32.DLL 输出的运行库,名为 RtlMoveMemory 和 RtlCopyMemory。

      它太吸引人了。首先我们已经找到了通过把变量转变为指针来得到内存地址的方法,现在我们又有了读写这些地址所指内容的方法。但是只要将这两个声明中的任一个加入你的程序中,而不是全部,因为它们的功能是一样的。我建议使用第二个,因为它被所有的 Windows 系统支持,而 RtlCopyMemory 则不然。

     Private Declare Sub CopyMemory _                     Lib "kernel32" Alias _                     "RtlCopyMemory" _                     (Destination As Any, _                      Source As Any, _                      ByVal length As Long) ' RtlCopyMemory 将一块内存的内容复制到另一块中。

     ' 或者

     Private Declare Sub CopyMemory _                     Lib "kernel32" Alias _                     "RtlMoveMemory" _                     (Destination As Any, _                      Source As Any, _                      ByVal length As Long)

     ' RtlMoveMemory 可以向前或向后移动内存,匹配的或不匹配的, ' 以 4字节的块为单位,后面为所有保留的字节。

    参数:

     Destination   指向要移动的目标。

     Source   指向要复制的内存。

     Length   指定要复制的字节数。

      为了使它更容易使用,你可以把下面内容复制粘贴到 modMemory.bas 中:

    ------------开始剪切------------------------------------------------------------------

     Attribute VB_Name = "modMemory" ' ============================================================================= ' 复制内存 API ' ============================================================================= Private Declare Sub CopyMemory _                     Lib "kernel32" Alias _                     "RtlMoveMemory" _                     (Destination As Any, _                      Source As Any, _                      ByVal length As Long)                      ' ============================================================================= ' 数据长度 ' ============================================================================= Public Enum e_BinaryData     DefineByte = 1                          '  8 位数据     DefineWord = 2                          ' 16 位数据     DefineDoubleWord = 4                    ' 32 位数据     DefineQuadWord = 8                      ' 64 位数据 End Enum

     ' ============================================================================= ' 允许直接读 MemPointer 指向的内存 ' 用和 Asm 一样的字节数定义 (DB, DW, DD, DX) ' ============================================================================= Function ReadMem(ByVal MemPointer As Long, _                  SizeInBytes As e_BinaryData)     Select Case SizeInBytes         Case DefineByte             Dim DB As Byte             CopyMemory DB, ByVal MemPointer, 1             ReadMem = DB         Case DefineWord             Dim DW As Integer             CopyMemory DW, ByVal MemPointer, 2             ReadMem = DW         Case DefineDoubleWord             Dim DD As Long             CopyMemory DD, ByVal MemPointer, 4             ReadMem = DD         Case DefineQuadWord             Dim DX As Double             CopyMemory DX, ByVal MemPointer, 8             ReadMem = DX     End Select End Function

     ' ============================================================================= ' 允许直接写 MemPointer 指向的内存 ' 用和 Asm 一样的字节数定义 (DB, DW, DD, DX) ' ============================================================================= Sub WriteMem(ByVal MemPointer As Long, _              SizeInBytes As e_BinaryData, _              ByVal DataToWrite)     CopyMemory ByVal MemPointer, VarPtr(DataToWrite), SizeInBytes End Sub

    ------------结束剪切---------------------------------------------------------------

    用例:

    通过内存为变量赋值:

     Dim ptrVariable As Long Dim xCounter As Long

     ptrVariable = VarPtr(ptrVariable) WriteMem ptrVariable, DefineWord, &HFFFF  ' 与 ptrVariable = &HFFFF 等价

    读内存的内容,使用:

     ptrVariable = ReadMem(ptrVariable, DefineWord)

      现在我们能够获得指针并访问它们了。但是如果你一步步跟着以上步骤看下来,你可能奇怪一条原本的 Visual Basic 赋值操作比这里介绍的直接内存赋值操作快得多。然而本文旨在指出可以使用 Visual Basic 访问内存,而这一点的主要意义不仅在于读取和分析变量,接下来,你可以通过获得内存地址简单地处理运行的 DLL。同时利用 modMemory.bas 和 PE (Portable Executable) 文件格式的知识,你可以分析 DLL 主体,看看它们是如何处理的。最好的是,可以获取它所有输出函数的列表;差点忘记,可以把它们 spy 出来或者干脆获取函数体的副本进行反汇编,比低级语言访问更多的内容,这也是 C 语言被称为工业标准的原因;现在你可以书写跟 C 表现相同的 Visual Basic 程序,祝你好运!

    - Chris Vega [gwapo@models.com]

     

    Accessing Memory by 32-bit Addresing in Windows using Visual Basic

    July 6, 2001

    Manila, Philippines

    By: Chris Vega [gwapo@models.com]

    When we talk about *real* Pointer and Memory Addressing, most of us thinks of Visual Basic limitations, ie, VB cannot access memory because VB has no pointer datatype for a variable declarations. This confusion grow even larger when a scenarios needed one *address* of a variable instead of its value, ie, from where in memory was that variable located into a virtual space of currently running process or a process or dynamic library.

    Yes, there is actually *no* pointer variable for VB, but have you ever tried to turn a regular VB Datatype into a Pointer? do you think its not possible? well, think again, cause in Visual Basic (starting from release version 5), a serries of handy funtions is presented by Microsoft to turn this regular variables of yours into a pointer, these are:

     1] VarPtr - Returns the Address of a Variable or Array Element               StrPtr - Returns the Address of String

      Variables in Visual Basic, except Strings are located into its   Memory Location, you can get the Address of this variable by   calling VarPtr Function. Strings however are stored as BSTR's,  a pointer to a "pointer on array of characters", where you need  StrPtr to have the address of "pointer to the array of characters"  instead an address to BSTR if you used VarPtr in String.

      ex.   Dim ptrMyPointer As Long   Dim intMyInteger As Integer   Dim strMyString As String * 25

       ' A call

       ptrMyPointer = VarPtr(intMyInteger)

       ' gives ptrMyPointer a 32-bit Address of the Variable   ' intMyInteger in Memory

       strMyString = "Address of Variable : " & Hex(ptrMyPointer)

       MsgBox strMyString

       ' Next, a call

       ptrMyPointer = StrPtr(strMyString)

       ' gives the Address of the First Element of the Array of    ' Character, ie, First letter of the String.

     2] VarPtrArray - Returns the Address of an Array of Variables    VarPtrStringArray - Returns the Address of an Array of Strings

      Arrays in Visual Basic are store in SAFEARRAYs, and you need to  use the function VarPtrArray to get the address of this array, but  before you can use the function, you need to manually declare the   function from msvbvm50.dll to your VB Application.

      ex.

       ' for VB 5   ' ========   Declare Function VarPtrArray _                                         Lib "msvbvm50.dll" Alias "VarPtr" _                    (Var() as Any) As Long

       ' for VB 6   ' ========   Declare Function VarPtrArray _                                         Lib "msvbvm60.dll" Alias "VarPtr" _                    (Var() as Any) As Long

       ' The Call

       Dim lngSafeArrayAddress As Long   Dim lngArrayOfLongs(6) As Long   Dim i As Long

       Randomize   For i = 0 to 6    lngArrayOfLongs = Int(Rnd * &HFFFF)   Next

       lngSafeArrayAddress = VarPtrArray(lngArrayOfLongs())

       ' Returns the Safe Address of an Array lngArrayOfLongs, you   ' can simply use 'em for *fast* sorting or many more!

      VarPtrStringArray however are more difficult to incorporate into  you application since you need to create a TypeLibrary and manually  refference the Library into VB Application. 

      To make a Type Library, you need a MIDL compiler, a CommandLine tool  that compiles *.odl file into a Type Library,

      For VB5 Create a Text File and Save it to VB5StrPtr.odl with content:

      -------------Cut here--------------------------------------------------  #define RTCALL _stdcall  [  uuid(6E814F00-7439-11D2-98D2-00C04FAD90E7),  lcid (0), version(5.0), helpstring("VarPtrStringArray Support for VB5")  ]  library PtrLib  {  importlib ("stdole2.tlb");  [dllname("msvbvm50.dll")]  module ArrayPtr     {     [entry("VarPtr")]     long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);     }  }   ----------End Cut here-------------------------------------------------

      And compile it with:   MIDL /t VB5StrPtr.odl

      For VB6 Create a Text File and Save it to VB6StrPtr.odl with content:

      -------------Cut here--------------------------------------------------  #define RTCALL _stdcall  [  uuid(C6799410-4431-11d2-A7F1-00A0C91110C3),  lcid (0), version(6.0), helpstring("VarPtrStringArray Support for VB6")  ]  library PtrLib  {  importlib ("stdole2.tlb");  [dllname("msvbvm60.dll")]  module ArrayPtr     {     [entry("VarPtr")]     long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);     }  }   ----------End Cut here-------------------------------------------------

      And compile it with:   MIDL /t VB6StrPtr.odl

      Now, you have the Type Library, and Referrenced the Library to your VB  Application, you can get the Array of Strings in this way:

      Dim MyArrayOfStrings(3) As String  Dim AddressOfArray As Long  MyArrayOfStrings(0)="Chris"  MyArrayOfStrings(1)="Vega"  MyArrayOfStrings(2)="gwapo@models.com"

      ' A call  AddressOfArray = VarPtrStringArray ( MyArrayOfStrings() )

      ' gives you the Address of the first element in the Array and First  ' character of this element, ie, Address where "C" is located in  ' Memory

      ' *** How about it, you dont have MIDL compiler? or dont want to go  ' into a process of creating Type Library and Referencing it manually,  ' a simple approach of using StrPtr will be handly enough for you, since  ' this function has the capability of getting the Address of a String, and  ' each element in an Array of Strings is non other than String, so you  ' get the picture clear, you have to point your call to the first element  ' of the Array of String and call

      AddressOfArray = StrPtr ( MyArrayOfStrings(0) )

      ' returns the same result as the above call

     3] ObjPtr - Returns the Address of an Object

      Object Oriented Programming consist of Objects, and these objects also  stored into Memory together with all of its properties, as a structured  layout, and to obtain its location you need to call ObjPtr Funtion

      ex.   ' You want to know where is your Form1 resides in Memory, this   ' Method gives you the Address, in Thread

       Dim objMyObject As New Form1

       MsgBox "Form1 located at : " & Hex( ObjPtr( objMyObject ) )

    Ok, from this point, you are thinking on, How in the world should this Address becomesuseful in anyways? well the answer is very clear if you think this way, an Address isa Location in Memory, and your Variables is a Location in Memory with its own Locationin Memory, confused? well, to make it simple, you can simply think that this Address isa Location where Datas are stored, and Datas are READABLE and WRITABLE, but you need theAddress to have it Written or Read the Data on it, Hmmm, Is Visual Basic Capable ofdoing these things?

    Well, not, if you think plain as in Visual Basic Capability, but APIs are functions thatare ready for use by you application, the APIs im blabing about is a RunTime Libarariescalled RtlMoveMemory and RtlCopyMemory, exported by KERNEL32.DLL.

    Aint it charming? First we have found a way to achieve a Memory Address by converting aVariable into a Pointer, Now we have ways to Read and Write to anf from these addresses,but how you may ask? By adding either one of this Declarations to your Application, but notboth, since they funtion the same, i suggest use the second one since it supported by allWindows System, while RtlCopyMemory is not.

     Private Declare Sub CopyMemory _                     Lib "kernel32" Alias _                     "RtlCopyMemory" _                     (Destination As Any, _                      Source As Any, _                      ByVal length As Long) ' RtlCopyMemory copies the contents of one buffer to another.

     ' OR

     Private Declare Sub CopyMemory _                     Lib "kernel32" Alias _                     "RtlMoveMemory" _                     (Destination As Any, _                      Source As Any, _                      ByVal length As Long)

     ' RtlMoveMemory moves memory either forward or backward, aligned or unaligned,  ' in 4-byte blocks, followed by any remaining bytes.

    Parameters:

     Destination   Points to the destination of the move.

     Source   Points to the memory to be copied.

     Length   Specifies the number of bytes to be copied.

    To make it more easy to Use, Included the File modMemory.bas for Copy and Pastein this Article:

    ------------cut here------------------------------------------------------------------

     Attribute VB_Name = "modMemory" ' ============================================================================= ' Copy Memory API ' ============================================================================= Private Declare Sub CopyMemory _                     Lib "kernel32" Alias _                     "RtlMoveMemory" _                     (Destination As Any, _                      Source As Any, _                      ByVal length As Long)                      ' ============================================================================= ' Data Sizes ' ============================================================================= Public Enum e_BinaryData     DefineByte = 1                          '  8 Bits Data     DefineWord = 2                          ' 16 Bits Data     DefineDoubleWord = 4                    ' 32 Bits Data     DefineQuadWord = 8                      ' 64 Bits Data End Enum

     ' ============================================================================= ' Allows Direct Reading from Memory Pointed by MemPointer ' with definition of bytes used as in Asm (DB, DW, DD, DX) ' ============================================================================= Function ReadMem(ByVal MemPointer As Long, _                  SizeInBytes As e_BinaryData)     Select Case SizeInBytes         Case DefineByte             Dim DB As Byte             CopyMemory DB, ByVal MemPointer, 1             ReadMem = DB         Case DefineWord             Dim DW As Integer             CopyMemory DW, ByVal MemPointer, 2             ReadMem = DW         Case DefineDoubleWord             Dim DD As Long             CopyMemory DD, ByVal MemPointer, 4             ReadMem = DD         Case DefineQuadWord             Dim DX As Double             CopyMemory DX, ByVal MemPointer, 8             ReadMem = DX     End Select End Function

     ' ============================================================================= ' Allows Direct Writing to Memory Pointed by MemPointer ' with definition of bytes used as in Asm (DB, DW, DD, DX) ' ============================================================================= Sub WriteMem(ByVal MemPointer As Long, _              SizeInBytes As e_BinaryData, _              ByVal DataToWrite)     CopyMemory ByVal MemPointer, VarPtr(DataToWrite), SizeInBytes End Sub

    ------------end cut here---------------------------------------------------------------

    Usage:

     To assign to a variable using memory:

      Dim ptrVariable As Long  Dim xCounter As Long

      ptrVariable = VarPtr(ptrVariable)  WriteMem ptrVariable, DefineWord, &HFFFF    ' Same as ptrVariable = &HFFFF

     To read from a Memory, use:

      ptrVariable = ReadMem(ptrVariable, DefineWord)

    Wow, we got a Pointer and we can access them now, but if you time instructions, youmay be amazed that a raw Visual Basic Assignment Operations is much faster thatof the Direct Memory Assignment Operation, but what i am pointing out here is thatMemory can now be Accessed Using Visual Basic, and the global Idea of this is toRead and Analyse not only the Variable, from this Downward, you can simply run throughrunning DLL by acquiring their memory addresses, equiped with modMemory.bas withknowledge of Portable Executable format, Bingo, you can play with the DLL Body, Seehow they process and best of all, Get a List of all of its Exported Functions; andbefore i forgot, why not spy em out or even get a copy of their function body toDisassembly purpose, all of which and more are accessible on Low Level Languages, thats why C was declared as Industry Standard; Now you can write Visual Basic Application that can performs like C, goodluck

     

    - Chris Vega [gwapo@models.com] 


    最新回复(0)