我有关于指针的查询,有人可以帮我解释以下内容吗?
我确实理解指针是如何工作的,但是,我不太确定如何从地址覆盖部分内存来修改程序的行为.
我将根据我的理解尽可能多地解释以下内容,随意批评并启发我的误解,继承代码块:
void f(int) ;
int main ( int argc, char ** argv ) {
int a = 1234 ;
f(a);
printf("Back to main\n") ;
}
void g() {
printf("Inside g\n") ;
}
void f (int x) {
int a[100] ;
memcpy((char*)a,(char*)g,399) ;
x = *(&x-1) ;
*(&x-1) = (int)(&a) ; // note the cast; no cast -> error
// find an index for a such that a[your_index] is the same as x
printf("About to return from f\n") ;
}
//This program, compiled with the same compiler as above, produces the following output:
//About to return from f
//Inside g
//Back to main
Run Code Online (Sandbox Code Playgroud)
好吧,根据我的理解,这就是它的方式.
该程序从程序开始编写main(),它指定一个,然后使用as变量进入f().
里面的f():
它位于大小为100的数组中.然后将内存空间从g()复制到整个数组.所以现在基本上[]是g().然后将x分配给main() - 1中原始a的地址,我假设它是main()的地址.(我不确定这个,如果我错了,请纠正我)
从这里开始,我不太确定它如何设法调用[](用g()或甚至g()覆盖的那个.它似乎结束了f()并返回main().
感谢任何能帮助我的人!
干杯!
caf*_*caf 20
从技术上讲,该代码远远超出了C标准所定义的范围,因此它可以做任何事情.它正在做出大量无法做出的假设,这些假设肯定不是普遍真实的.但是,我可以提出一个非常可能的解释,为什么你看到你做的输出:
你是正确的,你已经将函数的代码复制g()到本地数组变量占用的内存中a.
要理解下一行,您需要了解一下在常见的基于堆栈的体系结构上如何调用函数.调用函数时,参数被压入堆栈,然后返回地址被压入堆栈,执行跳转到函数的起始点.在函数内,先前的帧指针被推入堆栈,然后为局部变量建立空间.堆栈往往在内存中向下增长(从高地址到低地址),尽管在所有常见架构中都不是这种情况.
因此,当main调用函数时f(),堆栈最初看起来像这样(帧指针和堆栈指针是两个CPU寄存器,包含堆栈上的位置地址):
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
| int x (parameter) | &x
|-------------------------|
STACK POINTER -> | return address | &x - 1
|-------------------------|
| ... | (lower addresses)
Run Code Online (Sandbox Code Playgroud)
该函数序言然后保存调用函数的帧指针和堆栈指针移动到要在局部变量创造空间f().因此,当C代码f()开始执行时,堆栈现在看起来像这样:
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
| saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
| int x (parameter) | &x
|-------------------------|
| return address | &x - 1
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| a[99] | &a[99]
| a[98] | &a[98]
| ... | ...
STACK POINTER -> | a[0] | &a[0]
| ... | (lower addresses)
Run Code Online (Sandbox Code Playgroud)
什么是帧指针?它用于引用函数中的局部变量和参数.编译器知道,当f()执行时,局部变量的地址a是总是 FRAME_POINTER - 100 * sizeof(int)和参数的地址x是FRAME_POINTER + sizeof(FRAME_POINTER) + sizeof(RETURN_ADDRESS).无论堆栈空间如何分配和释放,堆栈指针如何移动,所有局部变量和参数都可以作为帧指针的固定偏移量进行访问.
无论如何,回到代码.当这一行执行时:
x = *(&x-1) ;
Run Code Online (Sandbox Code Playgroud)
它复制存储1整数尺寸下在内存比值x,进x.如果你看看我的ASCII艺术,你会看到那是返回地址.所以这实际上是这样做的:
x = RETURN_ADDRESS;
Run Code Online (Sandbox Code Playgroud)
以下行:
*(&x-1) = (int)(&a) ;
Run Code Online (Sandbox Code Playgroud)
然后将返回地址设置为数组的地址a.它真的在说:
RETURN_ADDRESS = &a;
Run Code Online (Sandbox Code Playgroud)
强制转换是必需的,因为您将返回地址视为int,而不是指针(因此实际上,此代码仅适用于int与指针大小相同的体系结构- 这不适用于64位POSIX系统,例如!).
在函数C代码f()现在完成,和函数尾声未分配的局部变量(通过移动堆栈指针返回),并恢复呼叫者的帧指针.此时,堆栈看起来像:
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
| int x (parameter) | &x
|-------------------------|
STACK POINTER -> | return address | &x - 1
|-------------------------|
| saved frame pointer |
|-------------------------|
| a[99] | &a[99]
| a[98] | &a[98]
| ... | ...
| a[0] | &a[0]
| ... | (lower addresses)
Run Code Online (Sandbox Code Playgroud)
现在函数通过跳转到RETURN_ADDRESS的值返回 - 但是我们将其设置为&a,所以它不会返回到调用它的位置,而是跳转到数组start的值a- 它现在正在从堆栈执行代码.这是您从函数中复制代码的地方g(),因此代码(显然)很愉快地运行.请注意,因为堆栈指针已经移回到此处的数组上方,所以使用相同堆栈执行的任何异步代码(如在错误时刻到达的UNIX信号)都将覆盖代码!
所以这里是g()在函数序言开始之前的堆栈现在的样子:
| ... | (higher addresses)
| char **argv (parameter) |
|-------------------------|
| int argc (parameter) |
|-------------------------|
FRAME POINTER -> | saved frame pointer |
|-------------------------|
| int a |
|-------------------------|
STACK POINTER -> | int x (parameter) |
|-------------------------|
| return address |
|-------------------------|
| saved frame pointer |
|-------------------------|
| a[99] |
| a[98] |
| ... |
| a[0] |
| ... | (lower addresses)
Run Code Online (Sandbox Code Playgroud)
为序言g()然后建立一个堆栈帧为正常,执行它,并解开它,这使帧指针和堆栈指针如上述的最后一个图所示.
现在g()返回,所以它在堆栈顶部查找返回值 - 但堆栈的顶部(堆栈指针指向的位置)实际上是x函数所在的位置f()- 这就是我们隐藏原始的位置返回值更早,所以它返回到f()调用的地方.
作为旁注,堆栈现在是不同步的main(),因为它期望堆栈指针在它调用时的位置f()(指向x存储参数的位置) - 但现在它实际上指向局部变量a.这会产生一些奇怪的效果 - 如果你从main此时调用另一个函数,那么内容a会被改变!
我希望你(和其他人)从这次讨论中学到了一些有价值的东西,但重要的是要记住这就像五点掌心爆炸心脏编程技术 - 永远不要在真实的系统中使用它.一个新的子架构,编译器甚至只是不同的编译器标志可以并且将会改变执行环境足以使这种过于巧妙的代码完全以各种令人愉快和有趣的方式失败.