|
|
| | Terms of Agreement:
By using this article, you agree to the following terms...
1) You may use
this article in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
2) You MAY NOT redistribute this article (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.
3) You may link to this article from another website, but ONLY if it is not wrapped in a frame.
4) You will abide by any additional copyright restrictions which the author may have placed in the article or article's description. | Introduction
Ever wanted to know what is happening in the
Minesweeper game behind the scenes?
Well, I did, and I decided to investigate it.
This article is the result of my research,
brought here just for you.
Main Concepts
1. Using P/Invoke to invoke Win32 API's.
2. Direct reading another process memory.
NOTE: the first part of this article involves
some assembly code, if there something you don't
understand it's NOT important to the goal of this
article. you CAN skip it. Nevertheless, if you
want to ask me about it, you're more then welcome
to mail me your question.
NOTE 2: The program was tested on Windows XP, so
if you got some other system it might not work..
if you got some other system, and it did worked,
comment this article with your system info so we
all could enjoy that knowledge.
Update to NOTE 2: the code is now fixed in order
to work on Windows 2000 as well. thanks goes to
Ryan Schreiber for finding the addresses in
Win2K.
Step 1 - Investigate winmine.exe
If you're not an assembly fan, you might want to
skip to the end of this step, to the
conclusions..
So, in order to know better what is happening
behind the scenes of Minesweeper I've started by
opening the file in a debugger. My personally
favorite debugger is Olly Debugger v1.08, this is
a very simple and intuitive debugger. Anyway,
I've open winmine.exe in the debugger and looked
a bit on the file. I've found in the Import
section (a section that lists all the dll
functions that are used in the program) the
following entry:
010011B0 8D52C377 DD msvcrt.rand
which means that the Minesweeper uses the
randomize function of the MicroSoft Visual C
RunTime dll, So I thought it might help me. I've
search the file to find where the rand() function
is being called, I've found only one such place:
01003940 FF15 B0110001 CALL DWORD PTR DS:
[<&msvcrt.rand;>]
Then I've put a breakpoint on this single call
and ran the program. I've discovered that every
time you click the smiley a new mines map is
generated. the mines map is created as follows:
1. Allocate a block of memory for the mines map
and set all the memory bytes to 0x0F, which means
that there is no mine in that "cell".
2. second, loop over the number of mines and for
each mine:
2.1. randomize x position (1 .. width).
2.2. randomize y position (1 .. height).
2.3. set the correct cell in the memory block to
0x8F, which represents a mine in this cell.
here is the original code, I've added some
remarks, and bolded the important parts:
//
010036A7 MOV DWORD PTR DS:[1005334],EAX
; [0x1005334] = Width
010036AC MOV DWORD PTR DS:[1005338],ECX
; [0x1005338] = Height
010036B2 CALL winmine.01002ED5
; Generate empty block of memory and clears it
010036B7 MOV EAX,DWORD PTR DS:[10056A4]
010036BC MOV DWORD PTR DS:[1005160],EDI
010036C2 MOV DWORD PTR DS:[1005330],EAX
; [0x1005330] = number of mines
; loop over the number of mines
010036C7 PUSH DWORD PTR DS:[1005334]
; push Max Width into the stack
010036CD CALL winmine.01003940
; Mine_Width = randomize x position
; (0 .. max width-1)
010036D2 PUSH DWORD PTR DS:[1005338]
; push Max Height into the stack
010036D8 MOV ESI,EAX
010036DA INC ESI
; Mine_Width = Mine_Width + 1
010036DB CALL winmine.01003940
; Mine_Height = randomize y position
; (0 .. max height-1)
010036E0 INC EAX
; Mine_Height = Mine_Height +1
010036E1 MOV ECX,EAX
; calculate the address of the cell in the memory
; block
; (the map)
010036E3 SHL ECX,5
; the calculation goes:
; cell_memory_address = 0x1005340 + 32 *
; height + width
010036E6 TEST BYTE PTR DS:[ECX+ESI+1005340],80
; [cell_memory_address] == is already mine?
010036EE JNZ SHORT winmine.010036C7
; if already mine start over this iteration
010036F0 SHL EAX,5
; otherwise, set this cell as mine
010036F3 LEA EAX,DWORD PTR DS:[EAX+ESI+1005340]
010036FA OR BYTE PTR DS:[EAX],80
010036FD DEC DWORD PTR DS:[1005330]
01003703 JNZ SHORT winmine.010036C7
; go to next iteration
As you can see from the code I've discovered 4
important things:
Reading the memory in address [0x1005334] gives
me the Width of the map.
Reading the memory in address [0x1005338] gives
me the Height of the map.
Reading the memory in address [0x1005330] gives
me the number of mines in the map.
Given x,y that represents a cell in the map, in
column x, row y, the address [0x1005340 + 32 * y
+ x] gives me the cell value.
Which brings us to the next step..
Step 2 - Design a solution
You might wonder, what kind of solution am I
talking about? Well, after discovering that all
the mine information is available for me, and all
I need to do is read the data from the memory
I've decided to write a small app that reads this
information and present it for you. It can even
draw a map of its own, with a picture of a mine
wherever I find one..
So, what is to design about that? all I need to
do is put the address into a pointer (yes, they
exist even in C#) and read the pointed data,
right? well, not exactly, the reason why this is
not the case is that the memory where this data
is stored is not in my application. You see, each
process has its own address space so it could not
access "by accident" memory that belongs to
another program, so in order to read this data I
need to find a way to read the memory of another
process, in this case, its the Minesweeper
process.
I've decided to write a small class library, that
will receive a process and will give me the
functionality of reading a memory address from
this process. the reason I've decided to make it
a class library is because there is allot of
cases you might want to use it so why develop it
again and again? That way, you could easily take
the class and use it in your own application, and
you are free to do so. An example for where this
class can help is if you write a debugger. All
the debuggers I know have the ability to read
memory of the debugged application..
So, how do we read another process memory? the
answer resides in an API called
ReadProcessMemory. this API actually let you read
a process memory at a specific address. Only
before you do it you must open the process in a
special read mode and when you finish you must
close the process handle, to avoid resource
leaks. The following operations are performed
with the help of two more API called OpenProcess
and CloseHandle.
In order to use API's with C# you must use the
P/Invoke, meaning you need to declare the API
you're going to use, which is basically quite
simple, but you need to do it in the .NET way,
which is not that simple sometimes.. I've found
the API declarations in the MSDN:
HANDLE OpenProcess(
DWORD dwDesiredAccess, // access flag
BOOL bInheritHandle, // handle inheritance
//option
DWORD dwProcessId // process identifier
);
BOOL ReadProcessMemory(
HANDLE hProcess, // handle to the process
LPCVOID lpBaseAddress,// base of memory area
LPVOID lpBuffer, // data buffer
SIZE_T nSize,// number of bytes to read
SIZE_T * lpNumberOfBytesRead // number of
// bytes read
);
BOOL CloseHandle(
HANDLE hObject // handle to object
);
Well this declaration transformed to the
following C# declarations:
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(
UInt32 dwDesiredAccess,
Int32 bInheritHandle,
UInt32 dwProcessId
);
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[In, Out] byte[] buffer,
UInt32 size,
out IntPtr lpNumberOfBytesRead
);
[DllImport("kernel32.dll")] public static extern Int32 CloseHandle(
IntPtr hObject
);
If you want to know more information on the types
conversions between c++ and c#, I suggest you
searched msdn.microsoft.com for the
topics: "Marshaling Data with Platform Invoke".
Basically, if you put there what is logically
true, it will work. sometimes, a bit tuning is
requested.
After I have these functions declared, all I need
to do is wrap them with a simple class and use
it. I've put the declarations in a separate class
called ProcessMemoryReaderApi, only to be more
organized. The main utility class is called
ProcessMemoryReader. This class has a property
named ReadProcess, this property is from the type
System.Diagnostics.Process, this is where you put
the process you want to read its memory.
The class has a method which opens the process in
the read memory mode:
public void OpenProcess()
{
m_hProcess = ProcessMemoryReaderApi.OpenProcess(
ProcessMemoryReaderApi.PROCESS_VM_READ, 1,
(uint)m_ReadProcess.Id);
}
The PROCESS_VM_READ constant tell the system to
open the process in read mode, and the
m_ReadProcess.Id states what process do I want to
open.
The most important method in the class is the one
that reads the memory from the process:
public byte[] ReadProcessMemory(
IntPtr MemoryAddress, uint bytesToRead,
out int bytesReaded)
{
byte[] buffer = new byte[bytesToRead];
IntPtr ptrBytesReaded;
ProcessMemoryReaderApi.ReadProcessMemory
(m_hProcess,MemoryAddress,buffer,
bytesToRead,out ptrBytesReaded);
bytesReaded = ptrBytesReaded.ToInt32();
return buffer;
}
This function declares a byte array in the
requested size and read the memory with the API.
Simple as that.
And finally, the method that close it all:
public void CloseHandle()
{
int iRetValue;
iRetValue =
ProcessMemoryReaderApi.CloseHandle
(m_hProcess);
if (iRetValue == 0)
throw new Exception("CloseHandle failed");
}
Step 3 - Using the class
Well, here comes the fun part. Using the class
in order to read Minesweeper memory and reveal
the map. In order to use the class you must
first instantiate it:
ProcessMemoryReaderLib.ProcessMemoryReader
pReader = new
ProcessMemmoryReaderLib.ProcessMemoryReader();
then, you need to set the process you want to
read its memory, here is an example of how to get
the Minesweeper progress, once it is loaded, and
set it as the ReadProcess property:
System.Diagnostics.Process[] myProcesses
= System.Diagnostics.Process.GetProcessesByName
("winmine");
pReader.ReadProcess = myProcesses[0];
and all we need to do now is Open the process,
read the memory, and close it when we finish.
Again, here is an example of how its been done.
Here I read the memory address that represent
the Width of the map.
pReader.OpenProcess();
int iWidth;
byte[] memory;
memory = pReader.ReadProcessMemory(
(IntPtr)0x1005334,1,out bytesReaded);
iWidth = memory[0];
pReader.CloseHandle();
that simple!
In conclusion I present you the full code that
reveals the map of mines. Don't forget, all the
memory places I'm accessing are places found in
the first section of this article..
// resource manager for the picture of t
// he mine
System.Resources.ResourceManager resources =
new System.Resources.ResourceManager(
typeof(Form1));
ProcessMemoryReaderLib.ProcessMemoryReader
pReader = new
ProcessMemoryReaderLib.ProcessMemoryReader();
System.Diagnostics.Process[] myProcesses =
System.Diagnostics.Process.GetProcessesByName
("winmine");
// take first instance of minesweeper yo
// u find
if (myProcesses.Length == 0)
{
MessageBox.Show(
"No MineSweeper process found!");
return;
}
pReader.ReadProcess = myProcesses[0];
// open process in read memory mode
pReader.OpenProcess();
int bytesReaded;
int iWidth, iHeight, iMines;
int iIsMine;
int iCellAddress;
byte[] memory;
memory = pReader.ReadProcessMemory(
(IntPtr)0x1005334,1,out bytesReaded);
iWidth = memory[0];
txtWidth.Text = iWidth.ToString();
memory = pReader.ReadProcessMemory(
(IntPtr)0x1005338,1,out bytesReaded);
iHeight = memory[0];
txtHeight.Text = iHeight.ToString();
memory = pReader.ReadProcessMemory(
(IntPtr)0x1005330,1,out bytesReaded);
iMines = memory[0];
txtMines.Text = iMines.ToString();
// delete the previous button array
this.Controls.Clear();
this.Controls.AddRange(MainControls);
// create new button array, for drawing
//
// the mine map
ButtonArray = new System.Windows.Forms.Button
[iWidth,iHeight];
int x,y;
for (y=0 ; y// close process handle
pReader.CloseHandle();
That's it. Hope you leaned something new.
| | Download article
Note: Due to the size or complexity of this submission, the author has submitted it as a .zip file to shorten your download time. Afterdownloading it, you will need a program like Winzip to decompress it.
Virus note:All files are scanned once-a-day by Planet Source Code for viruses,but new viruses come out every day, so no prevention program can catch 100% of them.
FOR YOUR OWN SAFETY, PLEASE: 1)Re-scan downloaded files using your personal virus checker before using it. 2)NEVER, EVER run compiled files (.exe's, .ocx's, .dll's etc.)--only run source code.
If you don't have a virus scanner, you can get one at many places on the net including:McAfee.com
| Terms of Agreement:
By using this article, you agree to the following terms...
1) You may use
this article in your own programs (and may compile it into a program and distribute it in compiled format for languages that allow it) freely and with no charge.
2) You MAY NOT redistribute this article (for example to a web site) without written permission from the original author. Failure to do so is a violation of copyright laws.
3) You may link to this article from another website, but ONLY if it is not wrapped in a frame.
4) You will abide by any additional copyright restrictions which the author may have placed in the article or article's description. | | Report Bad Submission | | | Your Vote! |
See Voting Log | | Other User Comments | 4/2/2003 3:57:54 PM:arikp In the attached file is the sample
program to use the code and the article
in formatted html
| 4/3/2003 1:24:42 AM:Bart Goossens You get 5 stars
(ref CodeProject -
Hope you get as much points here ...)
| 4/3/2003 2:14:45 AM:{ pHroZeN GeeK } A real great article. Very useful... 5
from me. Also try submitting this in
gotdotnet.com Maybe u might win
article of the month award there...
| 4/8/2003 6:37:12 PM:Poltergeist really great, nice asm research as
well, and the process reader library is
usefull too, 5 globes for me ;)
| 4/9/2003 7:22:14 AM:Carlos Bomtempo no comments... 5 globes....
| 4/18/2003 11:00:00 AM:joyprakash saikia
I simply want to say quote from
HBO
"Simply the Best".
Great work
with MS.NET.
| 6/12/2003 3:17:42 PM:Chris Myers Excellent code tid bit
| | Add Your Feedback! | Note:Not only will your feedback be posted, but an email will be sent to the code's author in your name.
NOTICE: The author of this article has been kind enough to share it with you. If you have a criticism, please state it politely or it will be deleted.
For feedback not related to this particular article, please click here. | | |