Introduction
Want to know everything that happened behind the "Mine" game? Well, I thought, but also this decided to study it. This article is my research result, now in public.
Main concept
1. Call the Win32 API using P / Invoke.
2. Read the memory of another process directly.
Note 1: The first part of this article consists of some assembly code. If you don't understand it, it is not a need, this is not the purpose of this article, you can jump. However, if you want to ask me about these code, I welcome you to write to me.
Note 2: This program is tested under Windows XP, so if it cannot run in other systems, please indicate the information of the system, let us know.
Note 2 Renew: This code can now be run under Windows 2000 after modification. Thank you Ryan Schreiber found the memory address under Win2k.
First step - explore WINMINE.EXE
If you are not a compiler, you can jump to this step, just see the conclusion.
In order to better understand everything that happened behind the "mine", I opened this file as the beginning. My personal favorite debugger is OLLY Debugger V1.08, which is a very simple and intuitive debugger. In short, I open WinMine.exe in the debugger and view the file. I found that there is a line in the Import area (listed in all DLL functions used in the program):
010011B0 8D
52C
377 DD MSVCRT.RAND
This means that "mine" uses a random function of the VC runtime, so I think this may help me. I searched this file, I saw where to call the rand () function, but I found this function in one place:
01003940 FF15 B0110001 Call DWORD PTR DS: [<& msvcrt.rand>]
Then I insert a breakpoint and run the program in this line. I found that a new Büreu is generated whenever a smile icon is clicked. Britcha is created as follows:
1. First, assign a memory area to Breto and set all memory bytes to 0x.
0F
It is noted that there is no mine in this unit (Cell).
2. Second, according to the mine number, every mine is traversed:
2.. Randomized X position (value between 1 to the width). 2.2. Randomized Y position (between 1 to height). 2.3. Set the value of the selected unit in the memory block is 0X
8f
This means that there is a mine in this unit.
The following is the original code, I have added some comments and add a coarse point.
010036A
7 MOV DWORD PTR DS: [1005334], EAX; [0x1005334] = Width (ie, the number of horizontal grid)
010036AC
MOV DWORD PTR DS: [1005338], ECX; [0x1005338] = height (ie vertical grid)
010036B2 Call Winmine.01002ed5; Generating empty memory blocks and clears
010036B7 MOV EAX, DWORD PTR DS: [
10056A
4]
010036BC MOV DWORD PTR DS: [1005160], EDI
010036C
2 MOV DWORD PTR DS: [1005330], EAX; [0x1005330] = number of mines
; Circulation in mine
010036C
7 Push DWORD PTR DS: [1005334] Press the maximum width (Max Width) into the stack
010036CD Call Winmine.01003940; Mine_Width = Randomized X position (0 to max width-1) (ie randomly selected a value between 0 and Max Width-1) 010036D2 Push DWORD PTR DS: [1005338]; Press the maximum height In the stack
010036D8 MOV ESI, EAX
010036Da incni; mine_width = mine_width 1
010036db Call Winmine.01003940; Mine_Height = Randomized Y position
(0 to MAX HEIGHT-1)
010036E0 Inc Eax; Mine_Height = MINE_HEIGHT 1
010036E1 MOV ECX, EAX; the address of the calculation unit in the memory block (Bremen)
010036E3 SHL ECX, 5; press this calculation:
; Unit memory address = 0x1005340 32 * Height Width
010036E6 TEST BYTE PTR DS: [ECX ESI 1005340], 80; [Unit Memory Address] == Is it already mine?
010036ee Jnz Short Winmine
.010036C
7; if it is already mine, it will re-iteration
010036F
0 SHL EAX, 5; otherwise, set this unit for the mine
010036F
3 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
.010036C
7; carry out the next iteration
As you can see from the code, I found 4 points:
Read memory address [0x1005334] derived the width of Breto.
Read memory address [0x1005338] derived the height of Breto.
Read memory [0x1005330] The number of mines in Breto is obtained.
They give X, Y, which represent a unit in Bremet, located in the X column, Y line. Address [0x1005340 32 * Y X] gives the value of the unit so that we have entered the next step.
Step 2 - Design a solution
You may think, which solution I will talk about? Obviously, after all mines information can be found, what I have to do is read data from memory. I decided to write a small program that read this information and give a description. It can paint Bremen, showing every discovered mine.
So how do you design? What I did is to put the address into a pointer (yes, it still exists in C #), and read the data it refers to, do you do it? Well, it is not complete. Because the occasions are different, the memory stores these data is not in my application. To know, every process has its own address space, so it will not "unexpectedly" access memory belonging to other programs. Therefore, in order to read this data, a method must be found to read the memory of another process. In this example, this process is the "mine" process.
I decided to write a small class library, which will receive a process and provide the ability to read the process memory address. This is because I have to use it in many programs, and there is no need to repeat these codes repeatedly. This allows you to get this class and use it in your application and free. For example, if you write a debugger, this class will help you. As far as I know, all debuggers have the ability to read the memory of the debugged program. So how can we read the memory of other processes? The answer is a API called ReadProcessMemory. This API actually allows you to read a specified address in the process memory. However, before doing this, you must open the process in a specific mode, and after the operation is completed, you must close the handle to avoid resource leakage. We use the help description of the API of OpenProcess and CloseHandle to complete the corresponding operation.
In order to use the API in C #, P / Invoke must be used, which means that it needs to be declared before using the API. Under normal circumstances, it is not so easy to make you realize it in a .NET. I found these API declarations in MSDN:
Handle OpenProcess
DWORD dwdesiredAccess, // Access flag
Bool binherithandle, // handle inheritance option
DWORD DWPROCESSID // Process ID
);
Bool ReadprocessMemory
Handle hprocess, // process handle
LPCVOID LPBASEADDRESS, // memory area base
LPVOID LPBUFFER, // Data buffer
Size_t nsize, // The number of bytes to read
Size_t * lpnumberofBytesRead // The number of read bytes
);
Bool CloseHandle
Handle Hobject // Processes Handle
);
These statements are converted to the following C # declaration:
[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 about type conversion between C and C #, I suggest you search for this topic from the msdn.microsoft.com site: "Marshaling Data with Platform Invoke". Basically, if you put logically proper programs, it can run, but sometimes it needs a little adjustment.
After declaring these functions, I have to do it with a simple class and use this class. I put the statement in a class called ProcessMemoryReaderaPi, which is more reasonable. The main practical class is called ProcessMemoryReade. This class has a ReadProcess property, which is derived from the System.Diagnostics.Process type, which is used to store the process you want to read. There is a method in the class to open the process in reading mode. Public void openprocess ()
{
m_hprocess = processMemoryReadeRapi.OpenProcess
ProcessMemoryReaderapi.Process_VM_READ, 1,
(uint) m_readprocess.id);
}
Process_vm_read constant tells the system to open the process in read mode, and m_readprocess.id declares what process I want to open.
The most important thing in this class is a method that reads 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 with the requested size and reads memory using the API. It's that simple!
Finally, the following method closes the process.
Public void closehandle ()
{
IRetValue;
IRetValue = processMemoryReadeRapi.CloseHandle (M_HPRocess);
IRetValue == 0)
Throw New Exception ("CloseHandle Failed");
}
Step 3 - Use the class
It is now an interesting part. Using this class is to read "mine" memory and unveil Breto. To use the class, you need to initialize it first:
ProcessMemoryReaderlib.ProcessMemoryReader Preader
= New processMemoryReaderlib.ProcessMemoryReader ();
Next, you must set the process you want to read it. The following is an example of how to get the "mine" process, once the process is loaded, is set to the ReadProcess property:
System.Diagnostics.Process [] MyProcesses
= System.Diagnostics.Process.getProcessesbyName ("WinMine");
preader.readprocess = myprocesses [0];
What we need now is: Open the process, read memory, and turn it off after completion. Below or an example of operations, it reads the address representing the width of Bremen.
preader.openprocess ();
Int iWidth;
BYTE [] Memory;
Memory = preader.readprocessmemory ((intptr) 0x1005334, 1, out bytesreaded; iWidth = Memory [0];
preader.closehandle ();
Simple!
In the conclusion, I lists the full code showing the Breto. Don't forget, all memory locations I have to access are in the first part of this article.
// Bremen's data manager
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");
// Get the first list of "mine" process
IF (MyProcesses.Length == 0)
{
Messagebox.show ("No Minesweeper Process Found!");
Return;
}
preader.readprocess = myprocesses [0];
// Open the process by reading a memory mode
preader.openprocess ();
Int bytesreaded;
Int iWidth, Iheight, Imines
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 a button array for drawing each grid of Breto
ButtonArray = new system.windows.Forms.Button [iWidth, Iheight];
INT X, Y;
FOR (y = 0; y FOR (x = 0; x { ButtonArray [x, y] = new system.windows.Forms.Button (); ButtonArray [x, y] .Location = new system.drawing.point (20 x * 16, 70 y * 16); ButtonArray [x, y] .name = ""; ButtonArray [x, y] .size = new system.drawing.size (16,16); iCelladdress = (0x1005340) (32 * (Y 1)) (x 1); Memory = preader.readprocessmemory ((INTPTR) iCelladdress, 1, out bytesReaded; IISMINE = Memory [0]; IF (iismine == 0x 8f ) // If there is a thunder, draw a mine bitmap ButtonArray [x, y] .image = (system.drawing.bitmap) (Resources.getObject ("Button1.image"))))))) This.Controls.Add (ButtonArray [x, y]); } // Close process handle preader.closehandle (); These, I hope you can learn new things.