惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
Security Archives - TechRepublic
Security Archives - TechRepublic
V
Vulnerabilities – Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Proofpoint News Feed
G
GRAHAM CLULEY
P
Privacy International News Feed
The Hacker News
The Hacker News
Forbes - Security
Forbes - Security
U
Unit 42
N
News and Events Feed by Topic
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Cisco Blogs
A
About on SuperTechFans
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
D
Docker
I
Intezer
Spread Privacy
Spread Privacy
The Last Watchdog
The Last Watchdog
V2EX - 技术
V2EX - 技术
S
Security @ Cisco Blogs
F
Full Disclosure
S
Secure Thoughts
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
W
WeLiveSecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Project Zero
Project Zero
Recorded Future
Recorded Future
Cyberwarzone
Cyberwarzone
S
Security Affairs
AWS News Blog
AWS News Blog
H
Help Net Security
The GitHub Blog
The GitHub Blog
Hacker News: Ask HN
Hacker News: Ask HN
Vercel News
Vercel News
P
Proofpoint News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Register - Security
The Register - Security
S
Schneier on Security
F
Fortinet All Blogs
C
CERT Recently Published Vulnerability Notes
L
LINUX DO - 最新话题
T
Tor Project blog
T
The Exploit Database - CXSecurity.com
MongoDB | Blog
MongoDB | Blog
Webroot Blog
Webroot Blog

博客园 - ScottDa虾米

转:程序员从初级到中级10个秘诀 呵呵,很久没写随笔了,这次带来个新的小问题,在使用win7RTM过程中遇到的 ASP.NET中常用的26个优化性能方法 ERP和SAP的一些名词解释 乱七八糟网址大集合 中小企业成败关键在于老板 五种提高 SQL 性能的方法 Linq本质论 Windows Communication Foundation 详解 Windows Presentation Foundation 详解 .Net中的设计模式——Strategy模式 .Net中的设计模式——Decorator模式 理解C# 3.0新特性之Extension方法浅议 LINQ 的演变及其对 C# 设计的影响 PetShop的系统架构设计 C# 3.0语言新特性(语言规范) .NET企业级应用架构设计的技术选型 比尔盖茨:十条“金口玉言”-- 世界不会在意你的自尊 [转]三五个人十来条枪 如何走出软件作坊成为开发正规军
“扫雷”游戏幕后
ScottDa虾米 · 2008-03-28 · via 博客园 - ScottDa虾米

1. 使用 P/Invoke 调用 Win32 API。

2. 直接读取另一个进程的内存。

注1:本文的第一部分包括一些汇编代码,如果你不是很明白,无关要紧,这不是本文的目的,你尽可以跳过不管。然而,如果你想问我有关这些代码的问题,非常欢迎你写信给我。

注2:本程序是在windows XP下测试的,所以如果它不能运行在其它的系统下,请注明该系统的信息,好让我们大家都知道。

注2之更新: 本代码现在经过修改后也能在Windows 2000下运行。谢谢Ryan Schreiber找到了Win2K下的内存地址。

第一步 – 探索 winmine.exe

如果你不是一个汇编迷,可以跳到这一步的最后,只看结论。

为了更好地了解“扫雷”幕后所发生的一切,我以一个调试器打开此文件作为开端。我个人最喜欢的调试器是olly Debugger v1.08, 这是一个非常简单且直观的调试器。总之,我在调试器中打开winmine.exe,并查看该文件。 我发现在Import区(列出在程序中用到的所有dll函数的区域)有下面一行:

010011b0 8D52C377 DD msvcrt.rand

这就意味着“扫雷”用到了vc运行库的随机函数,因此我认为这对我可能有帮助。我搜索了该文件,看看到底在哪里调用了rand()函数,不过只在一个地方找到了这个函数:

01003940 FF15 B0110001 CALL DWORD PTR DS:[<&msvcrt.rand>]

接着我在这一行单步调用插入了一个断点并运行程序。我发现每当点击笑脸图标时,一个新的布雷图就生成了。布雷图按以下步骤创建:

1. 首先,给布雷图分配一块内存区,并把所有的内存字节都设置成0x0F,说明在该单元(cell)中没有地雷。

2. 其次,按地雷数遍历每一个地雷:

2.1. 随机化 x 位置 (取值在1至宽度之间)。
2.2. 随机化 y 位置 (取值在1至高度之间)。
2.3. 设置内存块中被选中的单元的值为0x8F,这意味着在该单元中有一个地雷。

下面是原码,我已加入了一些注释,并加粗了重点部分。

010036a7 MOV DWORD PTR DS:[1005334],EAX ; [0x1005334] = 宽度(即横向格数)

010036ac MOV DWORD PTR DS:[1005338],ECX ; [0x1005338] = 高度(即纵向格数)

010036b2 CALL winmine.01002ED5 ; 生成空的内存块并进行清除

010036b7 MOV EAX,DWORD PTR DS:[10056A4]

010036bc MOV DWORD PTR DS:[1005160],EDI

010036c2 MOV DWORD PTR DS:[1005330],EAX ; [0x1005330] = 地雷的个数

; 以地雷个数进行循环

010036c7 PUSH DWORD PTR DS:[1005334] ; 把最大宽度(max width)压入栈

010036cd CALL winmine.01003940 ; Mine_Width = 随机化 x 位置 (0 至 max width-1) (即在0和max width-1之间随机选一个值)

010036d2 PUSH DWORD PTR DS:[1005338] ; 把最大高度压入栈

010036d8 MOV ESI,EAX

010036da INC ESI ; Mine_Width = Mine_Width + 1

010036db CALL winmine.01003940 ; Mine_Height =随机化 y 位置

; (0 至 max height-1)

010036e0 INC EAX ; Mine_Height = Mine_Height +1

010036e1 MOV ECX,EAX ;计算单元在内存块(布雷图)中的地址

010036e3 SHL ECX,5 ; 按这样计算:

; 单元内存地址 = 0x1005340 + 32 * height + width

010036e6 TEST BYTE PTR DS:[ECX+ESI+1005340],80 ; [单元内存地址] ==是否已是地雷?

010036ee JNZ SHORT winmine.010036C7 ; 如果已是地雷,则重新迭代

010036f0 SHL EAX,5 ; 否则,设置此单元为地雷

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 ; 进行下一次迭代

正如你从代码所看到的,我发现了4个要点:

读内存地址[0x1005334]得出布雷图的宽度。

读内存地址[0x1005338]得出布雷图的高度。

读内存地址[0x1005330]得出布雷图中地雷的个数。

给出x、y,它们代表布雷图中的一个单元,位于x列,y行。地址 [0x1005340 + 32 * y + x] 给出了该单元的值,这样我们就进入了下一步。

第2 步– 设计一个解决方案

你可能在想,我将会谈到了哪一种解决方案呢?显然,在发现了所有的地雷信息均可为我所用后,我所要做的就是从内存中读取数据。我决定编写读取这些信息的一个小程序,并给予说明。 它能自己绘出布雷图,显示出每一个被发现的地雷。

那么,怎么设计呢?我所做的就是把地址装到一个指针中(是的,它在c#中还存在),并读出其所指的数据,这样行吗?嗯,并不完全如些。因为场合不同,存储这些数据的内存并不在我的应用程序之中。要知道,每一个进程都拥有自己的地址空间,所以它就不会“意外地”访问属于别的程序的内存。因此,为了能读出这此数据,就必须找到一种方法,用来读取另一个进程的内存。 在本例中,这个进程就是“扫雷”进程。

我决定写一个小小的类库,它将接收一个进程,并提供读取该进程内存地址的功能。之所以这样做,是因为我还要在很多程序中用到它,没有必要反反复复地编写这些代码。这样,你就可以得到这个类,并在应用程序中使用它,且是免费的。例如,如果你编写一个调试器,这个类对你会有所帮助。据我所知,所有的调试器都具有读取被调试程序内存的能力。

那么,我们怎么才能读取别的进程的内存呢?答案在于一个叫做readprocessmemory的api。 这个API实际上可以让你读取进程内存中的一个指定地址。但在进行此操作之前,必须以特定的模式打开进程,而在完成操作之后,就必须关闭句柄以避免资源泄漏。我们利用OpenProcess 和 CloseHandle这几个API的帮助说明,完成了相应的操作。

为了在c#中使用api,必须使用p/invoke,这意味着在使用api之前需要先对其进行声明。一般情况下都很简单,但要是让你以.net的方式实现的话,有时就不那么容易了。我在msdn中找到了这些api声明:

handle OpenProcess(

dword dwDesiredAccess, // 访问标志

bool bInheritHandle, // 句柄继承选项

dword dwProcessId // 进程ID

);

bool ReadProcessMemory(

handle hProcess, // 进程句柄

lpcvoid lpBaseAddress, // 内存区基址

lpvoid lpBuffer, // 数据缓冲

size_t nSize, // 要读的字节数

size_t * lpNumberOfBytesRead // 已读字节数

);

bool CloseHandle(

handle hObject // 进程句柄

);

这些声明转换为如下的c#声明:

[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

);

如果你想知道在c++和c#之间有关类型转换的更多信息,我建议你从msdn.microsoft.com站点搜索此话题:“marshaling Data with Platform Invoke”。 基本上, 如果你把逻辑上是正确的程序搁在那儿, 它便能运行, 但有时还需要一点点的调整。

在声明了这些函数之后,我要做的是用一个简单的类把它们包装起来,并使用这个类。我把声明放在一个叫做processmemoryreaderapi的类中,这样做更有条有理。主要的实用类称为processmemoryreade。这个类有一个readprocess属性,它源于system.diagnostics.process类型,用于存放你要读取其内存的进程。类中有一个方法,用来以读模式打开进程。

public void OpenProcess()

{

m_hprocess = ProcessMemoryReaderApi.OpenProcess(

processmemoryreaderapi.process_vm_read, 1,

(uint)m_readprocess.id);

}

process_vm_read 常量告诉系统以读模式打开进程, 而m_ReadProcess.Id 声明了我要打开的是什么进程。

在该类中最重要的是一个方法,它从进程中读取内存:

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;

}

这个函数以所请求的大小声明一个字节数组,并使用api读取内存。就这么简单!

最后,下面这个方法关闭了进程。

public void CloseHandle()

{

int iRetValue;

iretvalue = ProcessMemoryReaderApi.CloseHandle(m_hProcess);

if (iRetValue == 0)

throw new Exception("CloseHandle failed");

}

第三步 – 使用类

现在轮到了有趣的部分。使用这个类就是为了读取“扫雷”的内存并揭开布雷图。要使用类,需要先对其进行初始化:

processmemoryreaderlib.processmemoryreader pReader

= new ProcessMemoryReaderLib.ProcessMemoryReader();

接着,必须设置你想要读取其内存的进程。以下是如何获得“扫雷”进程的例子,这个进程一旦被装入,就被设置为readprocess属性:

system.diagnostics.process[] myProcesses

= System.Diagnostics.Process.GetProcessesByName("winmine");

preader.readprocess = myProcesses[0];

我们现在需要做的是:打开进程,读取内存,并在完成后关闭它。下面还是有关操作的例子,它读取代表布雷图宽度的地址。

preader.openprocess();

int iWidth;

byte[] memory;

memory = pReader.ReadProcessMemory((IntPtr)0x1005334,1,out bytesReaded);

iwidth = memory[0];

preader.closehandle();

简单吧!

在结论部分,我列出了显示布雷图的完整代码。别忘了,我要访问的所有内存位置就是在本文第一部分中所找到位置。

// 布雷图的资料管理器

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

// 获得“扫雷”进程的第一个实列

if (myProcesses.Length == 0)

{

messagebox.show("no MineSweeper process found!");

return;

}

preader.readprocess = myProcesses[0];

// 以读内存模式打开进程

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

// 删除以前的按钮数组

this.controls.clear();

this.controls.addrange(maincontrols);

// 创建一个按钮数组, 用于画出布雷图的每一格

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)//如果有雷,则画出地雷位图

buttonarray[x,y].image = ((System.Drawing.Bitmap)

(resources.getobject("button1.image")));

this.controls.add(buttonarray[x,y]);

}

// 关闭进程句柄

preader.closehandle();

就是这些