5 对异常的支持(harib18e)

接下来我们要实现强制结束程序的功能,完成这个功能之后,我们就可以在真机环境下测试crack1.hrb了。

要想强制结束程序,只要在中断号0x0d中注册一个函数即可,这是因为在x86架构规范中,当应用程序试图破坏操作系统,或者试图违背操作系统的设置时,就会自动产生0x0d中断,因此该中断也被称为“异常”。

我们赶紧来写一个_asm_inthandler0d函数吧,代码和_asm_inthandler20大同小异。

本次的naskfunc.nas节选

  1. _asm_inthandler0d:
  2. STI
  3. PUSH ES
  4. PUSH DS
  5. PUSHAD
  6. MOV AX,SS
  7. CMP AX,1*8
  8. JNE .from_app
  9. ; 当操作系统活动时产生中断的情况和之前差不多
  10. MOV EAX,ESP
  11. PUSH SS ; 保存中断时的SS
  12. PUSH EAX ; 保存中断时的ESP
  13. MOV AX,SS
  14. MOV DS,AX
  15. MOV ES,AX
  16. CALL _inthandler0d
  17. ADD ESP,8
  18. POPAD
  19. POP DS
  20. POP ES
  21. ADD ESP,4 ; INT 0x0d中需要这句
  22. IRETD
  23. .from_app:
  24. ; 当应用程序活动时产生中断
  25. CLI
  26. MOV EAX,1*8
  27. MOV DS,AX ; 先仅将DS设定为操作系统用
  28. MOV ECX,[0xfe4] ; 操作系统的ESP
  29. ADD ECX,-8
  30. MOV [ECX+4],SS ; 保存产生中断时的SS
  31. MOV [ECX ],ESP ; 保存产生中断时的ESP
  32. MOV SS,AX
  33. MOV ES,AX
  34. MOV ESP,ECX
  35. STI
  36. CALL _inthandler0d
  37. CLI
  38. CMP EAX,0
  39. JNE .kill
  40. POP ECX
  41. POP EAX
  42. MOV SS,AX ; SS恢复为应用程序用
  43. MOV ESP,ECX ; ESP恢复为应用程序用
  44. POPAD
  45. POP DS
  46. POP ES
  47. ADD ESP,4 ; INT 0x0d需要这句
  48. IRETD
  49. .kill:
  50. ; 将应用程序强制结束
  51. MOV EAX,1*8 ; 操作系统用的DS/SS
  52. MOV ES,AX
  53. MOV SS,AX
  54. MOV DS,AX
  55. MOV FS,AX
  56. MOV GS,AX
  57. MOV ESP,[0xfe4] ; 强制返回到start_app时的ESP
  58. STI ; 切换完成后恢复中断请求
  59. POPAD ; 恢复事先保存的寄存器值
  60. RET

这个函数与_asm_inthandler20的主要区别在于增加了STI/CLI这样控制中断请求禁止、恢复的指令和根据inthandler0d的结果来执行强制结束应用程序的操作。在强制结束时,尽管中断处理完成了但却没有使用IRETD指令,而且还把栈强制恢复到start_app时的状态,使程序返回到cmd_app。可能大家会问,这种奇怪的做法真的没问题吗?是的,完全没问题。

然后我们又写了inthandler0d这样一个函数。

本次的console.c节选

  1. int inthandler0d(int *esp)
  2. {
  3. struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
  4. cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
  5. return 1; /*强制结束程序*/
  6. }

这里显示的信息“General Protection Exception”,翻译成中文就是“一般保护异常”,这其实是INT 0x0d的名称。“一般”就是一般性的意思,“保护”就是指对操作系统进行保护。除了一般性的异常,还有一些特殊的异常,这些异常并不是由应用程序的bug和破坏行为所引发的,而是其他种类的异常情况,特殊的的异常会由INT 0x0d以外的中断来处理。

接下来,我们将_asm_inthandler0d注册到IDT中。

本次的dsctbl.c节选

  1. void init_gdtidt(void)
  2. {
  3. (中略)
  4. /* IDT的设置*/
  5. set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32); /* 这里!*/
  6. set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
  7. set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
  8. set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
  9. set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
  10. set_gatedesc(idt + 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32);
  11. return;
  12. }

好了,大功告成。

■■■■■

我们来“make run”,并运行crack1.hrb。

5 对异常的支持(harib18e) - 图1

异常快出来!……咦?

好奇怪啊,居然没有发生异常,应该出来才对嘛……难道是程序哪里写错了吗?嗯……看上去也没什么问题啊。那我们在真机环境下试试看吧……啊,出来了!

看来,是QEMU对异常的模拟有点bug,而并不是“纸娃娃系统”的bug,大家可以放心了。