program Loader;
(***********************************)
(*                                 *)
(* Memory Access Breakpoint Loader *)
(*            by tt.t              *)
(*                                 *)
(*                                 *)
(***********************************)

{
目标:拦截对指定地址的写操作,nop掉写地址的指令。
难点:在要补丁地址设Memory Write Breakpoint,查了半天没找到现成的东西,只好自己写。
思路:将要补丁的地址设为不可写,当写时会发生AV错误,然后进行Patch。但VirtualProtectEx会将整

个Page设为不可写属性,所有写Page的操作都会产生Access violation,无法直接找到要Patch的代码。
解决方法:要保存地址所在Page的地址范围,发生AV错误时判断是否因为VirtualProtect产生的Exceptio

n,如不是将Page的属性设为可写,同时设置单步标志,写操作完成后恢复Page为不可写属性,继续执

行,直至找到需Patch代码。
不知道有没有更好的方法。
}

uses
  JwaWinType,
  JwaWinNt,
  JwaWinBase,
  JwaWinUser;

{$R *.res}

var
  si: STARTUPINFO;
  pi: PROCESS_INFORMATION;

function MyExtractFilePath(f: String): String;
var
  i, l: integer;
begin
  l := Length(f);
  for i := l downto 1 do
    if f[i] = '' then Break;
  result := copy(f, 0, i);
end;

procedure CreateVictimProcess(Path: String);
const
  Nop: PChar = Chr($90) + Chr($90) + Chr($90);
var
  DbgEvent: TDebugEvent;
  DbgParam: DWORD;
  OldPrt, NewPrt: DWORD;
  pPatch: PByte;
  PgMin, PgMax: DWORD;
  MemInfo: TMemoryBasicInformation;
  WExpAddr: DWORD;
  DbgContext: TContext;
  rm: Boolean;
  hThread: DWORD;
begin    
  ZeroMemory(@si, sizeof(STARTUPINFO));
  si.cb := sizeof(STARTUPINFO);
  if not CreateProcess(PChar(Path), nil, nil, nil, False, CREATE_SUSPENDED or 

CREATE_DEFAULT_ERROR_MODE, nil,
                       PChar(MyExtractFilePath(Path)), si, pi) then
  begin
    MessageBox(0, 'CreateProcess failed! ', 'Error!', 0);
    exit;
  end;
  ResumeThread(pi.hThread);
  if WaitForInputIdle(pi.hProcess, INFINITE) <> 0 then
  begin
    MessageBox(0, 'WaitForInputIdle failed! ', 'Error!', 0);
    exit;
  end;
  if not DebugActiveProcess(pi.dwProcessId) then
  begin
    MessageBox(0, 'DebugActiveProcess failed! ', 'Error!', 0);
    exit;
  end; 
  if VirtualQueryEx(pi.hProcess, Pointer($9c66BC), MemInfo, SizeOf(MemInfo)) = 0 then
  begin
    MessageBox(0, 'VirtualQueryEx failed! ', 'Error!', 0);
    exit;
  end;
  PgMin := DWORD(MemInfo.BaseAddress);
  pgMax := PgMin + MemInfo.RegionSize;
  {VirtualProtect会将整个Page设为不可写属性,所有写Page的操作都会产生Access violation,要保存

整个Page的地址范围,
   后面可以判断是否因为VirtualProtect产生的Exception}      
  if not VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @OldPrt) then
  begin
    MessageBox(0, 'VirtualProtectEx failed! ', 'Error!', 0);
    exit;
  end;
  {改写Page属性为不可写}
  rm := false;
  while WaitForDebugEvent(DbgEvent, INFINITE) do
  begin
    DbgParam := DBG_CONTINUE;
    case DbgEvent.dwDebugEventCode of
      EXCEPTION_DEBUG_EVENT:
      begin
        if DbgEvent.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_BREAKPOINT then
          case DbgEvent.Exception.ExceptionRecord.ExceptionCode of
            EXCEPTION_SINGLE_STEP: {单步中断}
            begin
              if rm then {由于EXCEPTION_ACCESS_VIOLATION产生的单步中断,恢复Page为不可写属性}
              begin
                rm := false;
                VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @NewPrt);
              end;
            end;
            EXCEPTION_ACCESS_VIOLATION: {AV中断}
            begin
              DbgParam := DBG_EXCEPTION_NOT_HANDLED;
              if DbgEvent.Exception.ExceptionRecord.ExceptionInformation[0] = 1 then {写操作}
              begin
                WExpAddr := DbgEvent.Exception.ExceptionRecord.ExceptionInformation[1]; {写操作的目标地

址}
                if (WExpAddr >= PgMin) and (WExpAddr <= PgMax) then {目标地址在Page范围}
                begin
                  DbgParam := DBG_CONTINUE;
                  if(WExpAddr <> $9c66BC) then {不是写指定地址}
                  begin
                    VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, OldPrt, @NewPrt);
                    DbgContext.ContextFlags := CONTEXT_CONTROL;
                    hThread := OpenThread(THREAD_ALL_ACCESS, false, DbgEvent.dwThreadId);
                    GetThreadContext(hThread, DbgContext);
                    DbgContext.EFlags := DbgContext.EFlags or $100; 

{设单步标志,会触发EXCEPTION_SINGLE_STEP}
                    SetThreadContext(hThread, DbgContext);
                    rm := true; {标志,表明是EXCEPTION_ACCESS_VIOLATION产生的单步中断}
                  end
                  else
                  begin {Patch}
                    pPatch := DbgEvent.Exception.ExceptionRecord.ExceptionAddress;
                    VirtualProtectEx(pi.hProcess, pPatch, 3, PAGE_READWRITE, @NewPrt);
                    WriteProcessMemory(pi.hProcess, pPatch, Nop, 3, nil);
                    VirtualProtectEx(pi.hProcess, pPatch, 3, NewPrt, @NewPrt);
                  end;
                end;
              end;
            end;
            else
              DbgParam := DBG_EXCEPTION_NOT_HANDLED;
          end;
      end;
      EXIT_PROCESS_DEBUG_EVENT:
      begin
        ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
        Break;
      end;
    end;
    ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
  end;
end;

var
  Victim: string;
begin
  Victim := MyExtractFilePath(ParamStr(0)) + 'Wise***.exe'; //受害程序
  CreateVictimProcess(Victim);
  halt;
end.

  • 标 题: 答复
  • 作 者:ttui
  • 时 间:2005-10-28 10:09

ft,既然看不懂那就那就详细注释下。
记得要大体了解下deubg api的用法和使用流程先.

procedure CreateVictimProcess(Path: String);
const
  Nop: PChar = Chr($90) + Chr($90) + Chr($90);
var
  DbgEvent: TDebugEvent;
  DbgParam: DWORD;  //ContinueDebugEvent用,标志如何处理调试消息
  OldPrt, NewPrt: DWORD;
  pPatch: PByte;
  PgMin, PgMax: DWORD;
  MemInfo: TMemoryBasicInformation;
  WExpAddr: DWORD;
  DbgContext: TContext;
  rm: Boolean;
  hThread: DWORD;
begin    
  ZeroMemory(@si, sizeof(STARTUPINFO));
  si.cb := sizeof(STARTUPINFO);
  if not CreateProcess(PChar(Path), nil, nil, nil, False, CREATE_SUSPENDED or CREATE_DEFAULT_ERROR_MODE, nil,
                       PChar(MyExtractFilePath(Path)), si, pi) then
  begin
    MessageBox(0, 'CreateProcess failed! ', 'Error!', 0);
    exit;
  end;
  //建立目标进程
  ResumeThread(pi.hThread);
  //恢复进程执行.其实CreateProcess时不加CREATE_SUSPENDED标志就可以省掉这句
  if WaitForInputIdle(pi.hProcess, INFINITE) <> 0 then
  begin
    MessageBox(0, 'WaitForInputIdle failed! ', 'Error!', 0);
    exit;
  end;
  //等待目标进程完全运行至其开始等待用户输入.
  //因为目标程序是加过壳的,壳的部分会检查调试器,所以等到壳运行结束在去debug它.
  if not DebugActiveProcess(pi.dwProcessId) then
  begin
    MessageBox(0, 'DebugActiveProcess failed! ', 'Error!', 0);
    exit;
  end; 
  //挂上目标程序
  if VirtualQueryEx(pi.hProcess, Pointer($9c66BC), MemInfo, SizeOf(MemInfo)) = 0 then
  begin
    MessageBox(0, 'VirtualQueryEx failed! ', 'Error!', 0);
    exit;
  end;
  //查询需要设内存断点的地址($9c66BC,也就是0x9c66BC,后面简称addr)所在page的情况
  PgMin := DWORD(MemInfo.BaseAddress);
  pgMax := PgMin + MemInfo.RegionSize;
  //得到addr所在的page的始末地址.
  {因为VirtualProtect会将addr所在整个Page设为不可写属性(见VirtualProtect的API说明),所有写Page的操作,即使不是写我们感兴趣(但位于那个page上)的地址的操作,都会因为将addr设为不可写属性而产生Access violation.所以这里要保存整个Page的地址范围,以便后面判断AV是不是因写addr而产生的}  
  if not VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @OldPrt) then
  begin
    MessageBox(0, 'VirtualProtectEx failed! ', 'Error!', 0);
    exit;
  end;
  {改写addr属性为不可写.这里会将addr所在的整个Page设为不可写属性}
  rm := false;  {标志是不是因处理AV设置的单步中断产生的中断}
  while WaitForDebugEvent(DbgEvent, INFINITE) do  //等待调试消息
  begin
    DbgParam := DBG_CONTINUE;  //默认处理调试消息
    case DbgEvent.dwDebugEventCode of
      EXCEPTION_DEBUG_EVENT:
      begin
        if DbgEvent.Exception.ExceptionRecord.ExceptionCode <> EXCEPTION_BREAKPOINT then
        //判断是不是int 3产生的中断.因为DebugActiveProcess挂上程序后会向debuger发出一个EXCEPTION_BREAKPOINT调试消息,这里忽略它
          case DbgEvent.Exception.ExceptionRecord.ExceptionCode of  //判断异常类型
            EXCEPTION_ACCESS_VIOLATION: {AV异常}
            begin
              DbgParam := DBG_EXCEPTION_NOT_HANDLED; //表示由目标程序的SEH处理异常
              if DbgEvent.Exception.ExceptionRecord.ExceptionInformation[0] = 1 then
              begin  {是写操作产生的AV}
                WExpAddr := DbgEvent.Exception.ExceptionRecord.ExceptionInformation[1]; {WExpAddr =写操作的目标地址}
                if (WExpAddr >= PgMin) and (WExpAddr <= PgMax) then {WExpAddr在addr所在Page上}
                begin
                  DbgParam := DBG_CONTINUE;//表示我们来处理异常,让目标程序从发生异常处继续执行.
                  if(WExpAddr <> $9c66BC) then {WExpAddr不是写addr,要允许目标程序写操作}
                  begin
                    VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, OldPrt, @NewPrt);//恢复addr所在page为原来的属性(可写)

                    DbgContext.ContextFlags := CONTEXT_CONTROL;
                    hThread := OpenThread(THREAD_ALL_ACCESS, false, DbgEvent.dwThreadId);
                    //由Thread Id得到Thread handle
                    GetThreadContext(hThread, DbgContext);
                    //得到thread的Context,设置单步执行标志,让写操作完成后发生单步中断,以便将addr重新设为不可写,拦截下一次写操作.
                    DbgContext.EFlags := DbgContext.EFlags or $100; {设单步标志}
                    SetThreadContext(hThread, DbgContext);
                    //设置thread的Context

                    rm := true; {标志,表明是我们处理AV而产生的单步中断}
                  end
                  else  //{WExpAddr是写addr,进行Patch}
                  begin 
                    pPatch := DbgEvent.Exception.ExceptionRecord.ExceptionAddress;
                    //写地址操作语句地址
                    VirtualProtectEx(pi.hProcess, pPatch, 3, PAGE_READWRITE, @NewPrt);
                    //设操作语句地址为可写
                    WriteProcessMemory(pi.hProcess, pPatch, Nop, 3, nil);
                    //nop掉写地址语句
                    VirtualProtectEx(pi.hProcess, pPatch, 3, NewPrt, @NewPrt);
                    //恢复语句地址属性
                  end;
                end;
              end;
            end;{AV异常处理完毕}
            EXCEPTION_SINGLE_STEP: {发生单步中断}
            begin
              if rm then {由于处理AV产生的单步中断.}
              begin
                rm := false;
               //重置标志
                VirtualProtectEx(pi.hProcess, Pointer($9c66BC), 1, PAGE_EXECUTE_READ, @NewPrt);
               //恢复Page为不可写属性,以便拦截下次写操作
              end;
            end;
            else//不是AV中断或单步中断,由目标程序的SEH处理异常
              DbgParam := DBG_EXCEPTION_NOT_HANDLED;
          end;
      end;
      EXIT_PROCESS_DEBUG_EVENT:
      begin
        ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
        //目标程序推出,loader使命结束,退出
        Break;
      end;
    end;
    ContinueDebugEvent(DbgEvent.dwProcessId, DbgEvent.dwThreadId, DbgParam);
  end;
end;