显然,这段代码试图改变堆栈,以便当main
函数返回时,程序执行不会定期返回到运行时库(这通常会终止程序),而是会跳转到保存在shellcode
数组中的代码。
1) int *ret;
main
在堆栈上定义一个变量,就在函数参数的下方。
2) ret = (int *)&ret + 2;
让ret
变量指向位于堆栈上方int *
两个int
s的 a。ret
假设这是返回地址所在的位置,程序将在main
返回时继续。
2) (*ret) = (int)shellcode;
返回地址设置为shellcode
数组内容的地址,这样返回shellcode
时会执行 ' 的内容main
。
shellcode
似乎包含可能执行系统调用来启动的机器指令/bin/sh
。我可能错了,因为我实际上并没有拆卸shellcode
。
PS:此代码依赖于机器和编译器,可能无法在所有平台上运行。
回复你的第二个问题:
如果我使用 ret=(int)&ret +2 会发生什么,为什么我们要加 2?为什么不是3或4???我认为 int 是 4 个字节,所以 2 将是 8 个字节,不是吗?
ret
被声明为int*
,因此给它分配一个int
(例如(int)&ret
)将是一个错误。至于为什么添加 2 而不是任何其他数字:显然是因为此代码假定返回地址将位于堆栈上的该位置。考虑以下:
这段代码假定当有东西被压入调用堆栈时调用堆栈向下增长(就像在英特尔处理器中确实如此)。这就是为什么要添加而不是减去数字的原因:返回地址位于比自动(本地)变量(例如ret
)更高的内存地址。
从我记忆中的英特尔组装时代开始,C 函数通常被这样调用:首先,所有参数都以相反的顺序(从右到左)压入堆栈。然后,调用该函数。因此返回地址被压入堆栈。然后,建立一个新的堆栈帧,其中包括将ebp
寄存器压入堆栈。然后,局部变量被设置在堆栈下面的所有已被压入它的堆栈上。
现在我为您的程序假设以下堆栈布局:
+-------------------------+
| function arguments | |
| (e.g. argv, argc) | | (note: the stack
+-------------------------+ <-- ss:esp + 12 | grows downward!)
| return address | |
+-------------------------+ <-- ss:esp + 8 V
| saved ebp register |
+-------------------------+ <-- ss:esp + 4 / ss:ebp - 0 (see code below)
| local variable (ret) |
+-------------------------+ <-- ss:esp + 0 / ss:ebp - 4
位于底部ret
(这是一个 32 位整数)。上面是保存的ebp
寄存器(也是 32 位宽)。上面是 32 位返回地址。(上面是main
's 的参数 --argc
和argv
-- 但这些在这里并不重要。)当函数执行时,堆栈指针指向ret
. 返回地址位于“上方”的 64 位ret
,对应于+ 2
in
ret = (int*)&ret + 2;
这是+ 2
因为ret
is a int*
,而 anint
是 32 位的,因此加 2 意味着将其设置到上面 2 × 32 位(=64 位)的内存位置(int*)&ret
......如果所有假设在上段是正确的。
游览:让我用英特尔汇编语言演示如何调用 C 函数(如果我没记错的话——我不是这个主题的专家,所以我可能错了):
// first, push all function arguments on the stack in reverse order:
push argv
push argc
// then, call the function; this will push the current execution address
// on the stack so that a return instruction can get back here:
call main
// (afterwards: clean up stack by removing the function arguments, e.g.:)
add esp, 8
在 main 内部,可能会发生以下情况:
// create a new stack frame and make room for local variables:
push ebp
mov ebp, esp
sub esp, 4
// access return address:
mov edi, ss:[ebp+4]
// access argument 'argc'
mov eax, ss:[ebp+8]
// access argument 'argv'
mov ebx, ss:[ebp+12]
// access local variable 'ret'
mov edx, ss:[ebp-4]
...
// restore stack frame and return to caller (by popping the return address)
mov esp, ebp
pop ebp
retf
另请参阅:C 中过程调用序列的描述,以获取对此主题的另一种解释。