剑神一笑

怎样的心情

猛将兄ID:pankun
32167次访问,排名3553(1)好友0人,关注者3
pankun的文章
原创 27 篇
翻译 0 篇
转载 1 篇
评论 72 篇
剑神一笑的公告
剑神一笑 男 24 四川人

现客居广州

关注编译器开发,动态语言编译器优化,高性能网络服务器开发,逆向工程

最近评论
bakkill:虽然不太明白!但还要感谢楼主,希望多发些交流学习之用的好帖子!!
ZeroChou:谁说汇编要被淘汰了? 看看你现在用的盗版软件有几个破解过程跟汇编无关的? 看看你中的超级病毒有几个没用到汇编!
rawa459:我也在开发一个编译器。
欢迎访问http://rawapyin.blog.sohu.com
unwritewolf:呵呵
关注!!!
Nocky:我恰巧也写了一篇关于C++模拟C#的属性机制的文章,写完之后找到了你这篇,刚好方法不一样,要以一起讨论下一,在下拙作URL:
http://tiannocky.spaces.live.com/default.aspx
文章分类
收藏
相册
朋友
风焱
framesniper
Skywind
大宝
老李
蓝色光芒
龙子龙孙
我的作品
Pascal编译器
外挂与反外挂
要反复看的书
编译原理 技术与工具
计算机程序的构造和解释
设计模式
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 win32调试API学习心得(三)收藏

新一篇: 在其它进程中建立线程 | 旧一篇: Win32调试API学习心得(二)

要学习如何修改被调试进程,先让我们来了解几个与此有关的函数.
一.读指定进程内存:ReadProcessMemory
  此函数的定义为:function ReadProcessMemory(hProcess: THandle; const lpBaseAddress: Pointer; lpBuffer: Pointer; nSize: DWORD; var lpNumberOfBytesRead: DWORD): BOOL; stdcall;
  hProcess指向被读取内存的进程的句柄,此句柄必须有PROCESS_VM_READ权限.
  lpBaseAddress:指向被读取的内存在进程中基地址的指针.
  lpBuffer:指向用于保存读出数据的缓冲区的指针.
  nSize:指定从指定进程中要读取的字节数.
  lpNumberOfBytesRead:指向读出数据的实际字节数.

二.写指定进程内存:WriteProcessMemory
  此函数的定义为:function WriteProcessMemory(hProcess: THandle; const lpBaseAddress: Pointer; lpBuffer: Pointer; nSize: DWORD; var lpNumberOfBytesWritten: DWORD): BOOL; stdcall;
参数含义同ReadProcessMemory,其中hProcess句柄要有对进程的PROCESS_VM_WRITE和PROCESS_VM_OPERATION权限.lpBuffer为要写到指定进程的数据的指针.

  注意,如果要修改的内存所在的页面的存取保护属性为只读,如代码段,要修改页面的存取保护才能够正常修改.可使用VirtualProtectEx函数,请参考下面的代码片段:
VirtualProtectEx(hPHandle, Address, SizeOf(BYTE), PAGE_READWRITE, OldFlg);
WriteProcessMemory(hPHandle, Address, @BreakCode, SizeOf(BYTE), Read);
VirtualProtectEx(hPhandle, Address, SizeOf(BYTE), OldFlg, OldFlg); // 恢复页码保护属性
  hPHandle为目标进程句柄,Address为要修改的内存的基址,SizeOf(BYTE)表示要修改的区域长度,如果这个长度跨过一个或几个页面边界时,将修改跨过的所有页面的存取保护属性,OldFlg用来存放原来的存取保护属性,以便调用WriteProcessMemory后恢复页面保护属性.

三.得到指定线程的上下文结构:GetThreadContext
  此函数的定义为:function GetThreadContext(hThread: THandle; var lpContext: TContext): BOOL; stdcall;
  hThread:要取得上下文结构的线程的句柄,可以在发生CREATE_THEAD_DEBUG_EVENT调试事件时保存线程ID和线程句柄的关联以便调用GetThreadContext时得到线程句柄.
  lpContext:用来保存指定线程上下文件信息的结构.
  在象Windows这样的多任务操作系统中,同一时间里可能运行着几个程序.Windows分配给每个线程一个时间片,当时间片结束后,Windows将冻结当前线程并切换到下一具有最高优先级的线程.在切换之前,Windows将保存当前进程的寄存器的内容,这样当在该线程再次恢复运行时,Windows可以恢复最近一次线程运行的环境.保存的寄存器内容总称为进程上下文.上下文件的结构取决于CPU的类型.
  在调用GetThreadContext之前,要先设置TContext的ContextFlags标志来指明要检索的寄存器.例如只想得到CPU的段寄存器的值,可以设置ContextFlags标志为CONTEXT_SEGMENTS.其它可能的标志如下:
  CONTEXT_CONTROL:包含CPU的控制寄存器,比如指今指针,堆栈指针,标志和函数返回地址.
  CONTEXT_INTEGER:用于标识CPU的整数寄存器.
  CONTEXT_FLOATING_POINT:用于标识CPU的浮点寄存器.
  CONTEXT_SEGMENTS:用于标识CPU的段寄存器.
  CONTEXT_DEBUG_REGISTER:用于标识CPU的调试寄存器.
  CONTEXT_EXTENDED_REGISTERS:用于标识CPU的扩展寄存器.
  CONTEXT_FULL:相当于CONTEXT_CONTROL or CONTEXT_INTEGER or   CONTEXT_SEGMENTS,即这三个标志的组合.

四.设置指定线程的上下文结构:SetThreadContext
  此函数的定义为:function SetThreadContext(hThread: THandle; const lpContext: TContext): BOOL; stdcall;
  参数同GetThreadContext.
  有了这二个函数可以实现很多功能,比如用WriteProcessMemory在被调试进程的某个函数入口处写一个调试中断(int 3,即$cc),然后在运行到此调试中断时会产生中断,再用GetThreadContext得到当前线程的上下文,就可以根据Esp的值得到函数的参数等信息.你甚至可以修改Eip的值来让被调试程序跳到任何地址来执行,或是修改标志寄存器的值来修改进程的执行方式.

  了解了以上函数后我们就可以用来修改被调试进程了,具体能实现怎样的功能只局限于自己的想像力了,但运用不得当被调试程序通常会当得很惨。当然这几个函数不止可以用于被调试进程,用于其它进程一样适用(可用OpenProcess根据进程标识符得到进程句柄),例如用它们来做出你自己的游戏修改器等等.

下面的例子演示了如何其得线程的上下文并将CPU置为单步模式来运行程序,注意由于单步模式比较慢,运行一个大的被调试程序时可能会等很久时间.
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

 {调试信息处理过程}
procedure DebugPro;
var
  si: _STARTUPINFOA;         (进程启动信息}
  pi: _PROCESS_INFORMATION;  {进程信息}
  Flage: DWORD;
  DebugD: DEBUG_EVENT;   {调试事件}
  Rc: Boolean;
  CodeCount: DWORD;       {运行的指令数}
  ThreadHandle: Thandle;  {主线程句柄}
  Context: TContext;
begin
    {建立调试进程}
  CodeCount := 0;
  ConText.ContextFlags := CONTEXT_CONTROL;
  Flage := DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS;
  GetStartupInfo(si);  {初始化si结构,不然无法正常建立进程}
  if not CreateProcess(nil, Pchar('C:\winnt\NOTEPAD.EXE C:\Boot.ini'), nil, nil,
    False, Flage, nil, nil, si, pi) then
  begin
    MessageBox(Application.Handle, '建立被调试进程失败', '!!!', MB_OK or MB_ICONERROR);
    exit;
  end;
  while WaitForDebugEvent(DebugD, INFINITE) do
  begin    {根据事件代码进行相应处理}
    case DebugD.dwDebugEventCode of
      EXIT_PROCESS_DEBUG_EVENT:
      begin
        MessageBox(Application.Handle, '被调试进程中止', '!!!', MB_OK or MB_ICONERROR);
        Break;
      end;
      CREATE_PROCESS_DEBUG_EVENT:
      begin
        ThreadHandle := DebugD.CreateProcessInfo.hThread;
        MessageBox(Application.Handle, '被调试进程建立', '!!!', MB_OK or MB_ICONERROR);
      end;
      EXCEPTION_DEBUG_EVENT:
      begin
        if (DebugD.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_SINGLE_STEP) and
           (DebugD.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_BREAKPOINT) then
          Rc := False      {如果被调试程序产生了异常,让它自己处理}
        else
        begin
          GetThreadContext(ThreadHandle, Context);
           {将标志寄存器的陷井标志设为TRUE,这样CPU将会处于单步模式}
          Context.EFlags := Context.EFlags or $100;
          Inc(CodeCount);
          Form1.Label1.Caption := IntToStr(CodeCount);
          SetThreadContext(ThreadHandle, Context);
          Rc := True;
        end;
      end;
    end;
    if Rc then
      ContinueDebugEvent(DebugD.dwProcessId, DebugD.dwThreadId,
         DBG_CONTINUE)
    else
      ContinueDebugEvent(DebugD.dwProcessId, DebugD.dwThreadId,
         DBG_EXCEPTION_NOT_HANDLED);
  end;
  CloseHandle(pi.hProcess);
  Closehandle(pi.hThread);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  ThreadHandle, ThreadID: THandle;
begin
  ThreadHandle := CreateThread(nil, 0, @DebugPro, nil, 0, ThreadID);
end;

end.

最后附上其它的调试API.
一. procedure DebugBreak; stdcall;
  该函数在当前进程中产生断点,以便调用的线程能够向调试器发信号.
二. procedure FatalExit(ExitCode: Integer); stdcall;
  该函数把执行控制交给调试器,调试器的行为随后被指定为所用调试器的类型.
三. function FlushInstructionCache(hProcess: THandle; const lpBaseAddress: Pointer; dwSize: DWORD): BOOL; stdcall;
  该函数为指定进程刷新指令高速缓存器,此函数仅在多进程计算机上是有效的.
  hProcess:要刷新的高速缓存器的进程句柄.
  lpBaseAddress:要刷新区域的基地址指针,可以为0
  dwSize:要刷新区域的长度.
四. function isDebuggerPresent; BOOL; stdcall;
  该函数表明调用的进程是否在调试器描述表下运行,此函数从KERNEL32.DLL输出.
五. procedure OutputDebugString(lpOutputString: PChar); stdcall;
  该函数为当前的应用程序发送一个字符串到调试器中,lpOutputString为要发送的字符串.
  在DELPHI中可以通用View->Debug Windows->Event Log打开Event Log窗口查看被调试程序发送的字符串.
六. procedure SetDebugErrorLevel(dwLevel: DWORD); stdcall;
  该函数设置最小错误级别,在该错误级别中系统中将产生调试事件并把它传递给调试器.
  dwLevel:指定调试事件的最小错误调试程序,如果错误相等于或大于此程序,系统产生一个调试事件,此参数必须是下列值中的一个.
  0: 不记录任何错误. SLE_ERROR:仅记录ERROR级别的调试事件.
  SLE_MINORERROR:仅记录MINORERROR级别和ERROR级别的调试事件.
  SLE_WARNING:记录WARNING级别,MINORERROR和ERROR级别的调试事件.

发表于 @ 2003年09月15日 09:25:00|评论(loading...)|编辑

新一篇: 在其它进程中建立线程 | 旧一篇: Win32调试API学习心得(二)

评论

#benben2301 发表于2008-03-18 21:57:43  IP: 218.57.175.*
学习。。。。。
发表评论  


当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
Csdn Blog version 3.1a
Copyright © 剑神一笑