Behind the scenes of "mine clearance" games

Source: Internet
Author: User
Tags vc runtime

1. Use P/invoke to call Win32 API.

2. Read the memory of another process directly.

NOTE 1: The first part of this article includes some assembly code. If you do not understand it very well, it does not matter. This is not the purpose of this Article. You can skip it as much as possible. However, if you want to ask me questions about the code, you are welcome to write to me.

NOTE 2: This program is tested in Windows XP, so if it cannot run in other systems, please note the system information so that we all know.

Note 2 update: This code can be run in Windows 2000 after modification. Thank you, Ryan Schreiber, for finding the memory address in Win2k.

 

Step 1-explore winmine.exe

If you are not a fan of assembly, you can jump to the end of this step and only read the conclusion.

To better understand what is happening behind "mine clearance", I started with a debugger to open this file. My personal favorite debugger is Olly debugger v1.08, which is a very simple and intuitive debugger. In summary, I open winmine.exe IN THE keystore and view the file. I found that there is the following line in the import area (list the regions of all DLL functions used in the program:

010011b0 8d52c377 dd msvcrt. Rand

This means that "mine clearance" uses the random function of the VC Runtime library, so I think this may be helpful to me. I searched the file to see where the rand () function was called, but I found this function in only one place:

01003940 ff15 b0110001 call dword ptr ds: [<& msvcrt. Rand>]

Next, I inserted a breakpoint in this one-step call and ran the program. I found that every time I click the smiling face icon, a new mine chart is generated. To create a braemap, follow these steps:

1. First, allocate a memory area to the brarex and set all memory bytes to 0x0f, which means there is no mines in the cell.

2. Traverse each mine based on the number of mines:

2.1. randomization X position (value range: 1 to width ).
2.2. randomize the Y position (value range: 1 to height ).
2.3. Set the value of the selected unit in the memory block to 0x8f, which means there is a mine in the unit.

Below is the original code. I have added some comments and added the key parts.

010036a7 mov dword ptr ds: [1005334], eax; [0x1005334] = width (that is, the number of horizontal cells)

010036ac mov dword ptr ds: [1005338], ECx; [0x1005338] = height (I .e., number of vertical cells)

010036b2 call winmine.01002ed5; generate and clear empty memory blocks

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 by number of mines

010036c7 push dword ptr ds: [1005334]; push max width to stack

010036cd call winmine.01003940; mine_width = randomization X position (0 to max width-1) (that is, a random value is selected between 0 and max width-1)

010036d2 push dword ptr ds: [1005338]; push the maximum height to the stack

010036d8 mov ESI, eax

010036da Inc ESI; mine_width = mine_width + 1

010036db call winmine.01003940; mine_height = randomization y position

; (0 to max height-1)

010036e0 Inc eax; mine_height = mine_height + 1

010036e1 mov ECx, eax; Address of the computing unit in the memory block (Ray map)

010036e3 SHL ECx, 5; calculated as follows:

; 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.010036c7; Re-iteration if it is already a mine

010036f0 SHL eax, 5; otherwise, set this unit to 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; perform the next iteration

 

As you can see from the code, I found four points:

Read Memory Address [0x1005334] To get the width of the layout chart.

Read Memory Address [0x1005338] To get the height of the layout chart.

Read Memory Address [0x1005330] to get the number of mines in the Mine Chart.

X and Y are given. They represent a unit in the brarex, which is located in column X and row y. The address [0x1005340 + 32 * Y + x] gives the value of this unit, so that we proceed to the next step.

Step 2-design a solution

You may be wondering which solution will I talk about? Obviously, after discovering that all mine information can be used by me, all I have to do is read data from the memory. I decided to write a small program to read the information and give instructions. It can plot its own Mine Chart to show every detected mine.

So, how to design it? What I do is to put the address in a pointer (yes, it still exists in C #) and read the data it refers to. Is that OK? Well, not exactly. For different scenarios, the memory for storing the data is not in my application. You know, every process has its own address space, so it will not "accidentally" access the memory of other programs. Therefore, to read this data, you must find a way to read the memory of another process. In this example, this process is a "mine clearance" process.

I decided to write a small class library that will receive a process and provide the function of reading the memory address of the process. This is because I have to use it in many programs and there is no need to write the code repeatedly. In this way, you can get this class and use it in the application, and it is free of charge. For example, if you write a debugger, this class will be helpful to you. As far as I know, all debuggers have the ability to read the memory of the program being debugged.

So how can we read the memory of other processes? The answer is an API called readprocessmemory. This API actually allows you to read a specified address in the process memory. However, before this operation, you must open the process in a specific mode. After the operation is completed, you must close the handle to avoid resource leakage. We completed the corresponding operations by using the help instructions of OpenProcess and closehandle APIs.

To use APIs in C #, you must use P/invoke, which means that you must declare the APIS before using them. In general, it is very simple, but if you implement it in. Net mode, it is sometimes not that easy. 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, // base address of the memory area

Lpvoid lpbuffer, // data buffer

Size_t nsize, // number of bytes to read

Size_t * lpnumberofbytesread // Number of read bytes

);

 

Bool closehandle (

Handle hobject // Process Handle

);

 

These statements are converted to the following C # statements:

[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 about type conversion between C ++ and C #, I suggest you search for this topic from the msdn.microsoft.com site: "using aling data with platform invoke ". Basically, if you put a logically correct program there, it can run, but sometimes it still needs a little adjustment.

After declaring these functions, all I need to do is wrap them up with a simple class and use this class. I put the Declaration in a class called processmemoryreaderapi, which is more rational. The main classification class is processmemoryreade. This class has a readprocess attribute, which is derived from the system. Diagnostics. Process type and is used to store the process whose memory you want to read. Class, used to open the process in Read 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, while m_readprocess.id declares what process I want to open.

The most important method in this class is to read 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 uses APIs to read memory. That's easy!

 

Finally, the following method closes the process.

 

 

Public void closehandle ()

 

{

Int iretvalue;

Iretvalue = processmemoryreaderapi. closehandle (m_hprocess );

If (iretvalue = 0)

Throw new exception ("closehandle failed ");

 

}

 

 

Step 3-usage class

Now it's interesting. This class is used to read the "mine clearance" memory and uncover the ray map. To use a class, you must first initialize it:

Processmemoryreaderlib. processmemoryreader preader

= New processmemoryreaderlib. processmemoryreader ();

Then, you must set the process that you want to read its memory. The following is an example of how to obtain a "mine clearance" process. Once a process is loaded, it is set to the readprocess attribute:

System. Diagnostics. Process [] myprocesses

= System. Diagnostics. process. getprocessesbyname ("winmine ");

Preader. readprocess = myprocesses [0];

 

What we need to do now is: Open the process, read the memory, and close it after completion. The following is an example of the operation. It reads the address that represents the width of the layout chart.

Preader. OpenProcess ();

 

Int iwidth;

Byte [] memory;

Memory = preader. readprocessmemory (intptr) 0x1005334,1, out bytesreaded );

Iwidth = memory [0];

 

Preader. closehandle ();

Easy!

 

In the conclusion section, I listed the complete code for displaying a ray chart. Don't forget that all the memory locations I want to access are located in the first part of this article.

// The Data Manager of braretu

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 ");

 

// Obtain the first real column of the "mine clearance" Process

If (myprocesses. Length = 0)

{

MessageBox. Show ("No minesweeper process found! ");

Return;

}

Preader. readprocess = myprocesses [0];

 

// Open the 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 an array of buttons to draw each grid of a Mine Chart.

Buttonarray = new system. Windows. Forms. Button [iwidth, iheight];

 

Int X, Y;

For (y = 0; y <iheight; y ++)

For (x = 0; x <iwidth; 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 = 0x8f) // if there is a mine, the mine bitmap is drawn.

Buttonarray [x, y]. Image = (system. Drawing. Bitmap)

(Resources. GetObject ("button1.image ")));

 

This. Controls. Add (buttonarray [x, y]);

}

 

// Close the Process Handle

Preader. closehandle ();

That's all.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.