简体   繁体   English

为什么将“extern puts”转换为函数指针“(void(*)(char *))&puts”?

[英]Why cast “extern puts” to a function pointer “(void(*)(char*))&puts”?

I'm looking at example abo3.c from Insecure Programming and I'm not grokking the casting in the example below. 我正在查看来自不安全编程的示例abo3.c ,并且我没有在下面的示例中讨论转换。 Could someone enlighten me? 有人可以开导我吗?

int main(int argv,char **argc)   
{  
    extern system,puts;  
    void (*fn)(char*)=(void(*)(char*))&system;  
    char buf[256];  

    fn=(void(*)(char*))&puts;  
    strcpy(buf,argc[1]);  
    fn(argc[2]);  
    exit(1);  
}

So - what's with the casting for system and puts? 那么 - 系统的投射和投注是什么? They both return an int so why cast it to void? 他们都返回一个int所以为什么把它变成无效?

I'd really appreciate an explanation of the whole program to put it in perspective. 我非常感谢对整个计划的解释。

[EDIT] [编辑]
Thank you both for your input! 谢谢你们的投入!

Jonathan Leffler , there is actually a reason for the code to be 'bad'. Jonathan Leffler ,实际上代码有“坏”的原因。 It's supposed to be exploitable, overflowing buffers and function pointers etc. mishou.org has a blog post on how to exploit the above code. 它应该是可利用的,溢出缓冲区和函数指针等.mishou.org有一篇关于如何利用上述代码的博客文章。 A lot of it is still above my head. 其中很多仍然在我头上。

bta , I gather from the above blog post that casting system would somehow prevent the linker from removing it. bta ,我从上面的博客文章中得知,转换系统会以某种方式阻止链接器删除它。

One thing that is not immediately clear is that the system and puts addresses are both written to the same location, I think that might be what gera is talking about “so the linker doesn't remove it”. 有一点不能立即明确的是系统和放置地址都写在同一个位置,我想这可能是gera所说的“所以链接器不会删除它”。

While we are on the subject of function pointers, I'd like to ask a follow-up question now that the syntax is clearer. 虽然我们讨论的是函数指针的主题,但现在我想问一个后续问题,即语法更清晰。 I was looking at some more advanced examples using function pointers and stumbled upon this abomination, taken from a site hosting shellcode. 我正在使用函数指针查看一些更高级的示例,并偶然发现这个可憎的东西,取自托管shellcode的站点。

#include <stdio.h>

char shellcode[] = "some shellcode";

int main(void)
{
    fprintf(stdout,"Length: %d\n",strlen(shellcode));
    (*(void(*)()) shellcode)();
}

So the array is getting cast to a function returning void , referenced and called? 所以数组被强制转换为返回void的函数,引用并调用? That just looks nasty - so what's the purpose of the above code? 这看起来很讨厌 - 所以上面代码的目的是什么?

[/EDIT] [/编辑]

Original question 原始问题

User bta has given a correct explanation of the cast - and commented on the infelicity of casting system . 用户bta给出了演员的正确解释 - 并评论了铸造system的不正确性。

I'm going to add: 我要补充一下:

The extern line is at best weird. extern线条充其量是怪异的。 It is erroneous under strict C99 because there is no type, which makes it invalid; 在严格的C99下它是错误的,因为没有类型,这使它无效; under C89, the type will be assumed to be int . 在C89下,类型将被假定为int The line says 'there is an externally defined integer called system, and another called puts', which is not correct - there are a pair of functions with those names. 该行说'有一个外部定义的整数称为系统,另一个称为puts',这是不正确的 - 有一对带有这些名称的函数。 The code may actually 'work' because the linker might associate the functions with the supposed integers. 代码实际上可能“有效”,因为链接器可能会将函数与假定的整数相关联。 But it is not safe for a 64-bit machine where pointers are not the same size as int . 但对于指针与int大小不同的64位机器来说,这是不安全的。 Of course, the code should include the correct headers ( <stdio.h> for puts() and <stdlib.h> for system() and exit() , and <string.h> for strcpy() ). 当然,代码应该包含正确的头文件(对于system()exit() puts()<stdlib.h> <stdio.h> ,以及strcpy() <string.h> )。

The exit(1); exit(1); is bad on two separate counts. 在两个单独的计数上是不好的。

  • It indicates failure - unconditionally. 它表示失败 - 无条件。 You exit with 0 or EXIT_SUCCESS to indicate success. 您以0或EXIT_SUCCESS退出以指示成功。

  • In my view, it is better to use return at the end of main() than exit() . 在我看来,最好在main()的末尾使用return而不是exit() Not everyone necessarily agrees with me, but I do not like to see exit() as the last line of main() . 不是每个人都必须同意我,但我不希望看到exit()作为main()的最后一行。 About the only excuse for it is to avoid problems from other bad practices, such as functions registered with atexit() that depend on the continued existence of local variables defined in main() . 关于它的唯一借口是避免来自其他不良实践的问题,例如使用atexit()注册的函数,这些函数依赖于main()定义的局部变量的持续存在。


/usr/bin/gcc -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -c nasty.c
nasty.c: In function ‘main’:
nasty.c:3: warning: type defaults to ‘int’ in declaration of ‘system’
nasty.c:3: warning: type defaults to ‘int’ in declaration of ‘puts’
nasty.c:3: warning: built-in function ‘puts’ declared as non-function
nasty.c:8: warning: implicit declaration of function ‘strcpy’
nasty.c:8: warning: incompatible implicit declaration of built-in function ‘strcpy’
nasty.c:10: warning: implicit declaration of function ‘exit’
nasty.c:10: warning: incompatible implicit declaration of built-in function ‘exit’
nasty.c: At top level:
nasty.c:1: warning: unused parameter ‘argv’

Not good code! 不好的代码! I worry about a source of information that contains such code and doesn't explain all the awfulness (because the only excuse for showing such messy code is to dissect it and correct it). 我担心包含此类代码的信息来源并不能解释所有可怕性(因为显示此类混乱代码的唯一借口是剖析并更正它)。


There's another weirdness in the code: 代码中还有另一个奇怪之处:

int main(int argv,char **argc)   

That is 'correct' (it will work) but 100% aconventional. 这是'正确'(它会起作用),但100%是常规的。 The normal declaration is: 正常的声明是:

int main(int argc, char **argv)

The names are short for 'argument count' and 'argument vector', and using argc as the name for the vector (array) of strings is abnormal and downright confusing. 名称是'参数计数'和'参数向量'的缩写,并且使用argc作为字符串向量(数组)的名称是异常的并且完全令人困惑。


Having visited the site referenced, you can see that it is going through a set of graduated examples. 访问过引用的站点后,您可以看到它正在通过一组分级示例。 I'm not sure whether the author simply has a blind spot on the argc/argv issue or is deliberately messing around ( 'abo1' suggests that he is playing, but it is not helpful in my view). 我不确定作者是否只是对argc / argv问题有一个盲点,或者是故意搞乱( 'abo1'表示他正在玩,但在我看来这没有帮助)。 The examples are supposed to feed your mind, but there isn't much explanation of what they do. 这些例子可以满足你的想法,但对他们所做的事情没有多少解释。 I don't think I could recommend the site. 我认为我不能推荐这个网站。


Extension question 扩展问题

What's the cast in this code doing? 这段代码中的演员是做什么的?

#include <stdio.h>

char shellcode[] = "some shellcode";

int main(void)
{
    fprintf(stdout,"Length: %d\n",strlen(shellcode));
    (*(void(*)()) shellcode)();
}

This takes the address of the string 'shellcode' and treats it as a pointer to a function that takes an indeterminate set of arguments and returns no values and executes it with no arguments. 这将获取字符串'shellcode'的地址,并将其视为指向函数的指针,该函数接受一组不确定的参数并且不返回任何值并在没有参数的情况下执行它。 The string contains the binary assembler code for some exploit - usually running the shell - and the objective of the intruder is to get a root-privileged program to execute their shellcode and give them a command prompt, with root privileges. 该字符串包含用于某些漏洞利用的二进制汇编程序代码 - 通常运行s​​hell - 入侵者的目标是获取root权限程序来执行其shellcode并为其提供具有root权限的命令提示符。 From there, the system is theirs to own. 从那里,系统是他们拥有的。 For practicing, the first step is to get a non-root program to execute the shellcode, of course. 对于练习,第一步是获得一个非root程序来执行shellcode,当然。

Reviewing the analysis 回顾分析

The analysis at Mishou 's web site is not as authoritative as I'd like: 米首网站上的分析并不像我想的那样具有权威性:

One, this code uses the extern keyword in the C language to make the system and puts functions available. 一,此代码使用C语言中的extern关键字来使系统和函数可用。 What this does (I think) is basically references directly the location of a function defined in the (implied) header files…I get the impression that GDB is auto-magically including the header files stdlib.h for system and stdio.h for puts. 这个(我认为)基本上是直接引用(隐含的)头文件中定义的函数的位置...我得到的印象是GDB自动神奇地包括用于系统的头文件stdlib.h和用于puts的stdio.h 。 One thing that is not immediately clear is that the system and puts addresses are both written to the same location, I think that might be what gera is talking about “so the linker doesn't remove it”. 有一点不能立即明确的是系统和放置地址都写在同一个位置,我想这可能是gera所说的“所以链接器不会删除它”。

Dissecting the commentary: 剖析评论:

  1. The first sentence isn't very accurate; 第一句话不是很准确; it tells the compiler that the symbols system and puts are defined (as integers) somewhere else. 它告诉编译器符号systemputs在其他地方被定义(作为整数)。 When the code is linked, the address of puts() -the-function is known; 链接代码时, puts() -the-function的地址是已知的; the code will treat it as an integer variable, but the address of the integer variable is, in fact, the address of the function - so the cast forces the compiler to treat it as a function pointer after all. 代码将它视为整数变量,但整数变量的地址实际上是函数的地址 - 因此强制转换强制编译器将其视为函数指针。
  2. The second sentence is not fully accurate; 第二句不完全准确; the linker resolves the addresses of the external 'variables' via the function symbols system() and puts() in the C library. 链接器通过函数符号system()puts()解析外部'变量'的地址。
  3. GDB has nothing whatsoever to do the compilation or linking process. GDB没有做任何编译或链接过程。
  4. The last sentence does not make any sense at all. 最后一句话根本没有任何意义。 The addresses only get written to the same location because you have an initialization and an assignment to the same variable. 地址只会写入同一位置,因为您具有初始化和对同一变量的赋值。

This didn't motivate me to read the whole article, it must be said. 这并没有激励我阅读整篇文章,必须说。 Due diligence forces me onwards; 尽职调查迫使我前进; the explanation afterwards is better, though still not as clear as I think it could be. 之后的解释更好,但仍然没有我想象的那么清楚。 But the operation of overflowing the buffer with an overlong but carefully crafted argument string is the core of the operation. 但是,使用超长但精心设计的参数字符串溢出缓冲区的操作是该操作的核心。 The code mentions both puts() and system() so that when run in non-exploit mode, the puts() function is a known symbol (otherwise, you'd have to use dlopen() to find its address), and so that when run in exploit mode, the code has the symbol system() available for direct use. 代码提到puts()system()这样当在非漏洞利用模式下运行时, puts()函数是一个已知的符号(否则,你必须使用dlopen()来查找它的地址),所以当在漏洞利用模式下运行时,代码具有可供直接使用的符号system() Unused external references are not made available in the executable - a good thing when you realize how many symbols there are in a typical system header compared with the number used by a program that includes the header. 可执行文件中没有未使用的外部引用 - 当您意识到典型系统头中有多少符号与包含头文件的程序所使用的编号相比时,这是一件好事。

There are some neat tricks shown - though the implementation of those tricks is not shown on the specific page; 显示了一些巧妙的技巧 - 尽管特定页面上没有显示这些技巧的实现; I assume (without having verified it) that the information for getenvaddr program is available. 我假设(未经验证) getenvaddr程序的信息可用。

The abo3.c code can be written as: abo3.c代码可以写成:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)   
{  
    void (*fn)(char*) = (void(*)(char*))system;  
    char buf[256];  

    fn = (void(*)(char*))puts;  
    strcpy(buf, argv[1]);  
    fn(argv[2]);  
    exit(1);  
}

Now it compiles with only one warning with the fussy compilation options I originally used - and that's the accurate warning that 'argc' is not used. 现在它只使用我最初使用的繁琐的编译选项编译了一个警告 - 这就是没有使用'argc'的准确警告。 It is just as exploitable as the original; 它和原版一样可以利用; it is 'better' code though because it compiles cleanly. 它是“更好”的代码,因为它编译得很干净。 The indirections were unnecessary mystique, not a crucial part of making the code exploitable. 间接是不必要的神秘,而不是使代码可利用的关键部分。

Both system and puts normally return int . systemputs通常都返回int The code is casting them to a pointer that returns void , presumably because they want to ignore whatever value is returned. 代码将它们转换为返回void的指针,可能是因为它们想要忽略返回的任何值。 This should be equivalent to using (void)fn(argc[2]); 这应该等同于使用(void)fn(argc[2]); as the penultimate line if the cast didn't change the return type. 如果演员没有改变返回类型,则为倒数第二行。 Casting away the return type is sometimes done for callback functions, and this code snippet seems to be a simplistic example of a callback. 有时会为回调函数抛弃返回类型,而这段代码片段似乎是回调的一个简单示例。

Why the cast for system if it is never used is beyond me. 如果从未使用system的演员,为什么超出我的范围。 I'm assuming that there's more code that isn't shown here. 我假设这里没有显示更多的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM