08_长调用与短调用

前言

在上一节中,我们的实验通过修改GDT表,验证了CALL FAR指令在一致与非一致代码段中的权限检查规则。结论表明:在未利用操作系统特定机制的前提下,常规的远调用无法实现特权级的提升。

除了CS和EIP寄存器的状态更新,执行流的跳转同时也涉及堆栈状态的变化。普通的短调用(CALL)与长调用(CALL FAR)在底层硬件的压栈机制上存在区别。本节我们深入讨论这套堆栈结构的变化过程,为后续研究系统级提权机制的必要前提。

一、 短调用(Short Call):段内调用

短调用是应用层程序中最常见的函数调用方式。

  • 汇编指令CALL 立即数 / 寄存器 / 内存 (例如 call 0x401000)
  • 硬件逻辑 :短调用属于段内跳转。其目标代码与当前代码位于同一物理段,CS 寄存器保持不变,仅更新 EIP 寄存器的值。
  • 堆栈动作 :由于未发生跨段跳转,硬件自动将当前的 EIP(即CALL 指令的下一条指令地址)压入堆栈(PUSH EIP)。
  • 返回机制 :目标函数执行完毕后,配合使用 RET 指令,CPU从栈顶弹出4字节数据并赋值给EIP,从而恢复原始执行流。

二、 长调用(Long Call):同特权级段间调用

当执行流需要跨越物理段(即更新 CS 寄存器)时,需使用长调用指令。

  • 汇编指令CALL FAR CS:EIP (例如 call fword ptr [far_pointer])
  • 硬件逻辑 :即使目标代码段与当前代码段同属Ring 3(权限校验通过),只要重新加载了 CS 寄存器,CPU必须保存调用前的代码段状态。
  • 堆栈动作 :CPU 硬件会自动执行双重压栈操作:先压入调用前的 CS,再压入调用前的 EIPPUSH CS -> PUSH EIP)。
  • 返回机制 :由于堆栈中包含了 CSEIP,常规的 RET 指令无法正确平衡堆栈。目标函数必须使用 RETF (Return Far) 指令。RETF 指令会依次从堆栈中弹出 4 字节赋值给 EIP,再弹出 4 字节赋值给 CS,以恢复调用前的执行上下文。

三、 实验验证:长短调用堆栈状态对比

为验证底层硬件的压栈行为,编写了以下代码。将 CALL FAR 的目标段选择子设置为 0x1B(Windows默认的Ring 3代码段),以执行一次合法的同特权级段间跳转。实验代码如下所示:

c 复制代码
// Pm_Lab6.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"


#include <stdio.h>
#include <Windows.h>

#pragma pack(push, 1)
struct FWORD_PTR 
{
	DWORD offset;
	WORD  selector;
};
#pragma pack(pop)

__declspec(noinline) void TestShortCall()
{
	printf(" -> Inside Short Call.\n");
}

void __declspec(naked) TestLongCall()
{
	__asm
	{
		// nop指令作为特征标识
		nop
		nop
		nop
		retf
	}
}

int main()
{
	printf("[*] Starting Stack Behavior Experiment...\n");

	// 触发短调用
	TestShortCall(); 

	// 触发长调用
	FWORD_PTR far_pointer;
	far_pointer.offset = (DWORD)TestLongCall;
	far_pointer.selector = 0x1B; 

	__asm
	{
		call fword ptr [far_pointer]
	}

	printf("[*] Execution Finished Safely!\n");
	system("pause");
	return 0;
}

场景一:短调用(段内跳转)的堆栈现场

我们在OllyDbg中定位到call TestShortCall指令。记录执行前的堆栈指针状态,随后单步步入该函数,此时观察堆栈窗口的变化:

底层数据分析:由于未发生跨段跳转,CPU硬件的现场保护机制仅触发了最基础的动作:将CALL指令的下一条指令地址(即返回的EIP)压入栈顶。在目标函数末尾,常规的RET指令即可弹出该4字节数据,完美恢复执行流。

场景二:长调用(段间跳转)的堆栈现场

我们将执行流推进至跨段调用指令call far fword ptr ss:[ebp-8](长调用)。虽然目标选择子0x1B仍属Ring 3特权级,但对CPU而言,CS寄存器的重载动作已经触发。单步步入该裸函数,再次观察堆栈窗口:

底层数据分析:当段间跳转发生时,硬件强制执行了双重压栈保护(PUSH CS -> PUSH EIP)。堆栈内存中连续的8个字节记录了调用前的完整段落与偏移信息。面对长调用的堆栈结构,使用RET指令将导致堆栈失衡。程序必须通过RETF(Return Far)指令,依次弹出EIP与CS,能够正确平衡堆栈并返回。


四、 总结

  • 短调用 :段内跳转,仅保护 EIP
  • 同级长调用 :平级跨段,保护 CS + EIP
相关推荐
Redemption2 小时前
嵌软面试每日一阅----单片机知识简述(以stm32为列)
c语言·stm32·单片机·嵌入式硬件·面试·嵌入式
cch89182 小时前
PHP与C语言:从网页到内核的编程对决
c语言·开发语言·php
少司府3 小时前
C++基础入门:类和对象(上)
c语言·开发语言·c++·类和对象·访问限定符
老师用之于民3 小时前
【DAY35】ARM开发:UART 异步串行通信原理、通信标准及模块配置详解
c语言·汇编·arm开发·vscode
我叫洋洋15 小时前
[Proteus 和 stm32f103c8t6]的使用控制OLED篇]
c语言·stm32·单片机·嵌入式硬件·蓝桥杯·proteus
Book思议-15 小时前
【数据结构】栈与队列全方位对比 + C 语言完整实现
c语言·数据结构·算法··队列
IT方大同18 小时前
(实时操作系统)线程管理
c语言·开发语言·嵌入式硬件
老约家的可汗19 小时前
list 容器详解:基本介绍与常见使用
c语言·数据结构·c++·list
爱编码的小八嘎21 小时前
C语言完美演绎6-10
c语言