使用 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 String
Dim AddressOfArray As Long
MyArrayOfStrings(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 becomes
useful in anyways? well the answer is very clear if you think this way, an Address is
a Location in Memory, and your Variables is a Location in Memory with its own Location
in Memory, confused? well, to make it simple, you can simply think that this Address is
a Location where Datas are stored, and Datas are READABLE and WRITABLE, but you need the
Address to have it Written or Read the Data on it, Hmmm, Is Visual Basic Capable of
doing these things?
Well, not, if you think plain as in Visual Basic Capability, but APIs are functions that
are ready for use by you application, the APIs im blabing about is a RunTime Libararies
called RtlMoveMemory and RtlCopyMemory, exported by KERNEL32.DLL.
Aint it charming? First we have found a way to achieve a Memory Address by converting a
Variable 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 not
both, since they funtion the same, i suggest use the second one since it supported by all
Windows 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 Paste
in 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, you
may be amazed that a raw Visual Basic Assignment Operations is much faster that
of the Direct Memory Assignment Operation, but what i am pointing out here is that
Memory can now be Accessed Using Visual Basic, and the global Idea of this is to
Read and Analyse not only the Variable, from this Downward, you can simply run through
running DLL by acquiring their memory addresses, equiped with modMemory.bas with
knowledge of Portable Executable format, Bingo, you can play with the DLL Body, See
how they process and best of all, Get a List of all of its Exported Functions; and
before i forgot, why not spy em out or even get a copy of their function body to
Disassembly 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]