Board logo

Subject: 从 except ... end 跳回 try ... 的例子 [Print This Page]

Author: skyjacker    Time: 2007-5-17 15:27     Subject: 从 except ... end 跳回 try ... 的例子

//=====================================================================
// MicroTip#7 从 except ... end 跳回 try ... 的例子
// Http://www.cnpack.org
// Written by SkyJacker 2007.05.17
// QQ Discuss Group: 130970
// 欢迎讨论 Win/Delphi SEH, QQ: 6705517  MSN&EMail: HeMiaoYu@gmail.com
//=====================================================================

函数流程:
try
  ProcA  <----+
except        |
  ProcB   ----+
end;
流程描述: ProcA 发生异常后,进入异常处理函数 ProcB,然后再从 ProcB 跳回 ProcA。

下面函数演示:从异常处理函数返回到异常发生处的下一条指令。

procedure ExceptToTry;
var
  MyAddr: Cardinal;
  sTitle: string;
  sCaption: string;
begin
  sTitle := 'Test';
  sCaption := 'Info';
  try
    asm
      call @CurrAddr;
      @CurrAddr:
      pop MyAddr // 获得本条指令的地址
      xor ecx, ecx
      idiv ecx
      push 0
      push sCaption
      push sTitle
      push 0
      Call MessageBox
    end;
  except
    asm
      mov eax, [MyAddr]
      add eax, 7
      jmp eax
    end;
  end;
end;
Author: Passion    Time: 2007-5-17 16:21

解释一把?

这种情况下如果不用asm的话,用goto貌似也行?我没试过。
Author: skyjacker    Time: 2007-5-17 16:46

try
    asm
      call @CurrAddr; // Call 将下一条指令 pop MyAddr 的地址入栈
      @CurrAddr:
      pop MyAddr // 出栈。获得本条指令(pop MyAddr)的地址,存入 MyAddr
      xor ecx, ecx
      idiv ecx  // 除零错误
      push 0
      push sCaption
      push sTitle
      push 0
      Call MessageBox // 信息提示对话框
    end;
  except
    asm
      mov eax, [MyAddr] // 取 pop MyAddr 指令地址
      add eax, 7 // pop MyAddr 指令为 3 个字节, xor ecx, ecx 为 2 个字节, idiv ecx 为 2 个字节。
      jmp eax    // 跳到 MessageBox 形参入栈起始处 push 0
    end;
  end;
end;


试了一下 goto,编译通不过。
procedure TestGoTo;
var
  I, J: Integer;
label MyL;
begin
  J := 0;
  try
    I := I div J;
    MyL: MessageBox(0, 'Except', 'Info', MB_OK);
  except
    goto MyL;
  end;
end;

提示编译错误:'GOTO MyL' leads into or out of TRY statement
Author: Passion    Time: 2007-5-17 20:30

关键就是pop MyAddr,解释一下就通了。
很少用pop到某个地址的寄存器指令,都不熟悉了。
Author: zzzl    Time: 2007-5-22 19:59

再从 ProcB 跳回 ProcA

是指从ProcA的异常处开始执行还是从新?
Author: Passion    Time: 2007-5-22 20:09

这个例子应该是跳过异常部分,从下面的MessageBox处执行。
因为写了
      mov eax, [MyAddr] // 取 pop MyAddr 指令地址
      add eax, 7 // pop MyAddr 指令为 3 个字节, xor ecx, ecx 为 2 个字节, idiv ecx 为 2 个字节。

所以这个7只在这儿有用,没通用的特性。换几个语句就得换个值。
真正的跳回原处执行,得用到SEH的高级点的特性,而不是用asm规定跳回地址。用try等来使用SEH的高级特性的功能貌似VC里头就提供,而Delphi这方面省略了。
Author: skyjacker    Time: 2007-5-23 10:11

如果不利用 SEH 的特性跳回异常发生处, 通过 call,pop 来定位异常发生点还是比较方便的.

SEH 是操作系统的一个特性,
Delphi, VC 编译器级别的异常处理应该是差不多的.
VC 用 __except_handler3 来接管用户的异常处理函数,
Delphi 用 _HandleAnyException 来统一处理.
两者都是映射到由操作系统产生的异常处理结构.

再次执行异常发生处, 是操作系统给应用程序一次修复异常的机会.


//------------------------------------------------------------------------------
// 2.再次执行异常发生点的例子
// Written by SkyJacker
//------------------------------------------------------------------------------

var
  iEcx: Integer;

// 异常处理函数
function MyExceptHandle(): Integer;
begin
  MessageBox(0, 'MyExceptHandle', 'Info', MB_OK);
  iEcx := 1; // 修复异常,操作系统给程序一次修复的机会
  Result := 0; // 返回异常发生处,再次执行 idiv iEcx
end;

procedure JmpExceptionAddr;
begin
  iEcx := 0;
  asm
    lea eax, MyExceptHandle
    push eax
    push fs:[0] // 构造新 SEH 节点
    mov fs:[0], esp
    cdq
    idiv iEcx
  end;
  MessageBox(0, 'OK', 'Hello', 0);
  asm
    mov eax, [esp]
    mov eax, [eax]
    mov fs:[0], eax // 恢复原 SEH
    add esp, 8 // 堆栈平衡
  end;
end;

[ 本帖最后由 skyjacker 于 2007-5-23 10:16 编辑 ]
Author: zzzl    Time: 2007-6-3 00:52

好强,有没有从任意位置跳到任意位置的万能方法
Author: skyjacker    Time: 2007-6-6 12:40

jmp 0~2^32-1

但会有执行权限的问题.
随便跳过去也不知道执行什么代码阿.

比较有意义的是,能控制到能够预期执行的代码地址.
比如执行到自定义的 ShellCode.

[ 本帖最后由 skyjacker 于 2007-6-6 12:42 编辑 ]
Author: Passion    Time: 2007-6-6 19:14

跳转指令容易实现,关键是跳转的地址不好确定。

CnWizards的源码中有个MethodHook,就是在Method函数体地址里头写入Jmp指令,然后后面接的偏移量就是计算出来的相对偏移值,这样跳转才能正确地从旧的Method跳到新的Method里头。




Welcome to CnPack Forum (http://bbs.cnpack.org/) Powered by Discuz! 5.0.0