好吧,最近几天我一直在研究进程命令行参数和环境变量,尤其是查找main函数参数被推入堆栈的方式。
到目前为止,我已经知道某个_libc_start_main()函数负责main()在实际调用函数之前根据参数设置函数所需的一切。
没有进入太多细节,我注意到,调试简单,当main程序时,main堆栈帧是不同的,我们无论是在看它radare2或gdb。
例如,让我们以这个极简的 C 程序为例:
int main (int argc, char *argv[], char *envp[])
{
}
只需调试它,无需任何附加参数:
与 GDB
在main(我无法在不运行程序的情况下转储堆栈帧)的第一条汇编指令上设置断点后,我得到的gdb是非常明智的事情,正如人们所看到的:
(gdb) x/3xw $esp
0xffffcfbc: 0xf7db7b41 0x00000001 0xffffd054
# ^ ^ ^
# PC (somewhere argc argv
# in __libc_start_main())
现在通过实际检查指出的内存区域argv:
(gdb) x/2xw 0xffffd054 # argv
0xffffd054: 0xffffd1ef 0x00000000
# ^ ^
# argv[0] argv[argc]
# (another pointer)
(gdb) x/s 0xffffd1ef # argv[0]
0xffffd1ef: "<path>/argvonstack32"
# ^
# Exepected program name
因此,基本上推送到main堆栈帧上的内容argv和envp(即使envp为了简单起见我没有显示转储)正是我们有权从调试器期望的内容,即指向的指针char(如所述)在main函数签名中)。
使用 Radare2
不设置断点,不运行程序直接查看栈帧,radare2显示不同的栈帧:
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF comment
0xffa63d10 0100 0000 1953 a6ff 0000 0000 2953 a6ff .....S......)S.. ; esp
^ ^ ^
argc argv[0] argv[1]
检查时argv[0]:
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF comment
0xffa65319 2e2f 6172 6776 6f6e 7374 6163 6b33 3200 ./argvonstack32.
0xffa65329 5348 454c 4c3d 2f62 696e 2f62 6173 6800 SHELL=/bin/bash.
这表明radare2跳过了第一个指针间接argv并将列表argv[0]...argv[argc]直接推送到main堆栈帧上。
什么解释了这种差异?
PS:正如您所看到的,我使用radare2和之间的唯一区别是我运行gdb了程序,gdb而不需要实际运行它radare2来转储main堆栈帧内存。